SO是一场狗屎秀。感谢您的搭车。
我认为您可以使用 lined constraint 类型族精确地获得语法:
{-# LANGUAGE TypeFamilies, ConstraintKinds, GADTs #-}
import GHC.Exts (Constraint)
newtype PlayerHandle = PlayerHandle Int
newtype MinionHandle = MinionHandle Int
newtype WeaponHandle = WeaponHandle Int
type family IsHandle h :: Constraint where
IsHandle (PlayerHandle) = ()
IsHandle (MinionHandle) = ()
IsHandle (WeaponHandle) = ()
data Effect where
WithEach :: (IsHandle handle) => [handle] -> (handle -> Effect) -> Effect
编辑:另一次尝试,包括
Show
:
{-# LANGUAGE TypeFamilies, ConstraintKinds, GADTs,
UndecidableInstances #-}
import GHC.Exts (Constraint)
import Control.Monad (forM_)
newtype PlayerHandle = PlayerHandle Int
newtype MinionHandle = MinionHandle Int
newtype WeaponHandle = WeaponHandle Int
type family IsHandle' h :: Constraint where
IsHandle' (PlayerHandle) = ()
IsHandle' (MinionHandle) = ()
IsHandle' (WeaponHandle) = ()
type IsHandle h = (IsHandle' h, Show h)
data Effect where
WithEach :: (IsHandle handle) => [handle] -> (handle -> Effect) -> Effect
-- Assume my each (IsHandle a) already is an instance of a class I want to use, such as (Show).
enactEffect :: Effect -> IO ()
enactEffect (WithEach handles cont) = forM_ handles $ \handle -> do
print handle -- (*)
enactEffect $ cont handle
我不太明白如何避免拥有两个不同的类、类型或系列,并获得您似乎想要的 API,而不需要在另一个模块中添加其他类型。我也不知道有什么方法可以让生成的
IsHandle
约束自动继承这三种类型共有的所有类,而无需您将它们列出某处。
但我认为根据您的需求/风格,还有一些与我的上一个类似的选项:
IsHandle
设为一个类,其中 IsHandle'
和 Show
等作为超类。IsHandle'
创建为一个类,在这种情况下,防止添加更多类型的唯一预防措施就是不导出 IsHandle'
。最后一个的优点是它可以大大减少所需的扩展数量:
{-# LANGUAGE GADTs, ConstraintKinds #-}
class IsHandle' h
instance IsHandle' (PlayerHandle)
instance IsHandle' (MinionHandle)
instance IsHandle' (WeaponHandle)
type IsHandle h = (IsHandle' h, Show h)
这是一个基于 GADT 的解决方案:
{-# LANGUAGE GADTs, RankNTypes #-}
{-# OPTIONS -Wall #-}
module GADThandle where
import Control.Monad
newtype PlayerHandle = PlayerHandle Int deriving (Show)
newtype MinionHandle = MinionHandle Int deriving (Show)
newtype WeaponHandle = WeaponHandle Int deriving (Show)
data HandleW a where
WPlayer :: HandleW PlayerHandle
WMinion :: HandleW MinionHandle
WWeapon :: HandleW WeaponHandle
handlewShow :: HandleW a -> (Show a => b) -> b
handlewShow WPlayer x = x
handlewShow WMinion x = x
handlewShow WWeapon x = x
data Effect where
WithEach :: HandleW handle -> [handle] -> (handle -> Effect) -> Effect
enactEffect :: Effect -> IO ()
enactEffect (WithEach handlew handles cont) = handlewShow handlew $
forM_ handles $ \handle -> do
print handle
enactEffect $ cont handle
这里的想法是使用类型见证者
HandleW a
,证明a
是您的三种类型之一。然后,“引理”handlewShow
证明,如果 HandleW a
成立,则 a
一定是 Show
可用类型。
也可以将上面的代码推广到任意类型的类。下面的引理证明,如果您的三种类型
c T
中的每一种都有 T
,并且您知道 HandleW a
成立,那么 c a
也必须成立。您可以通过选择 c = Show
来获取之前的引理。
handlewC :: (c PlayerHandle, c MinionHandle, c WeaponHandle) =>
HandleW a -> Proxy c -> (c a => b) -> b
handlewC WPlayer Proxy x = x
handlewC WMinion Proxy x = x
handlewC WWeapon Proxy x = x
enactEffect' :: Effect -> IO ()
enactEffect' (WithEach handlew handles cont) = handlewC handlew (Proxy :: Proxy Show) $
forM_ handles $ \handle -> do
print handle
enactEffect' $ cont handle
向您的
Handle
类型添加一个类型参数,并使用 DataKinds
将其值限制为三个之一,因此:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE GADTs #-}
import Control.Monad
data Entity = Player | Minion | Weapon
newtype Handle (e :: Entity) = Handle Int
deriving (Eq, Ord, Read, Show)
data Effect where
WithEach :: [Handle e] -> (Handle e -> Effect) -> Effect
enactEffect :: Effect -> IO ()
enactEffect (WithEach handles cont) = forM_ handles $ \handle -> do
print handle
enactEffect $ cont handle
除非您想对类型做一些复杂的事情,否则我会使用
class
: 采用简单的解决方案
{-# LANGUAGE GADTs #-}
import Control.Monad
newtype PlayerHandle = PlayerHandle Int deriving (Show)
newtype MinionHandle = MinionHandle Int deriving (Show)
newtype WeaponHandle = WeaponHandle Int deriving (Show)
class (Show h) => Handle h
instance Handle PlayerHandle
instance Handle MinionHandle
instance Handle WeaponHandle
data Effect where
WithEach :: (Handle handle) => [handle] -> (handle -> Effect) -> Effect
enactEffect :: Effect -> IO ()
enactEffect (WithEach handles cont) = forM_ handles $ \handle -> do
print handle
enactEffect $ cont handle
我会使用 GADT:
{-# LANGUAGE KindSignatures, GADTs, RankNTypes, DataKinds #-}
data K = Player | Minion | Weapon
deriving (Eq, Show)
newtype PlayerHandle = PlayerHandle Int deriving (Show)
newtype MinionHandle = MinionHandle Int deriving (Show)
newtype WeaponHandle = WeaponHandle Int deriving (Show)
-- Plain ADT might be enough
-- see below
data Handle (k :: K) where
PlayerHandle' :: PlayerHandle -> Handle Player
MinionHandle' :: MinionHandle -> Handle Minion
WeaponHandle' :: WeaponHandle -> Handle Weapon
data SomeHandle where
SomeHandle :: Handle k -> SomeHandle
data Effect where
WithEach :: (SomeHandle -> IO ()) -> Effect
printEffect :: Effect
printEffect = WithEach f
where f (SomeHandle h) = g h
g :: Handle k -> IO ()
g (PlayerHandle' p) = putStrLn $ "player :" ++ show p
g (MinionHandle' p) = putStrLn $ "minion :" ++ show p
g (WeaponHandle' p) = putStrLn $ "weapon :" ++ show p
-- GADTs are useful, if you want to have maps preserving handle kind:
data HandleMap where
-- HandleMap have to handle all `k`, yet cannot change it!
HandleMap :: (forall k. Handle k -> Handle k) -> HandleMap
zeroWeaponHandle :: HandleMap
zeroWeaponHandle = HandleMap f
where f :: forall k. Handle k -> Handle k
f (PlayerHandle' h) = PlayerHandle' h
f (MinionHandle' h) = MinionHandle' h
f (WeaponHandle' _) = WeaponHandle' $ WeaponHandle 0
感谢所有提供解决方案的人。它们都对各种用例都有帮助。对于我的用例,事实证明,将句柄类型放入单个 GADT 解决了我的问题。
这是我为感兴趣的人得出的解决方案:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
data Player
data Minion
data Weapon
data Handle a where
PlayerHandle :: Int -> Handle Player
MinionHandle :: Int -> Handle Minion
WeaponHandle :: Int -> Handle Weapon
data Effect where
WithEach :: [Handle h] -> (Handle h -> Effect) -> Effect
PrintSecret :: Handle h -> Effect
-------------------------------------------------------------------------------
-- Pretend the below code is a separate file that imports the above data types
-------------------------------------------------------------------------------
class ObtainSecret a where
obtainSecret :: a -> String
instance ObtainSecret (Handle a) where
obtainSecret = \case
PlayerHandle n -> "Player" ++ show n
MinionHandle n -> "Minion" ++ show n
WeaponHandle n -> "Weapon" ++ show n
enactEffect :: Effect -> IO ()
enactEffect = \case
WithEach handles continuation -> mapM_ (enactEffect . continuation) handles
PrintSecret handle -> putStrLn (obtainSecret handle)
createEffect :: [Handle h] -> Effect
createEffect handles = WithEach handles PrintSecret
main :: IO ()
main = do
enactEffect $ createEffect $ map PlayerHandle [0..2]
enactEffect $ createEffect $ map MinionHandle [3..5]
enactEffect $ createEffect $ map WeaponHandle [6..9]