假设:
newtype PlayerHandle = PlayerHandle Int deriving (Show)
newtype MinionHandle = MinionHandle Int deriving (Show)
newtype WeaponHandle = WeaponHandle Int deriving (Show)
在以下代码中,我希望handle
完全三种类型中的一种:PlayerHandle
,MinionHandle
和WeaponHandle
。这可以在Haskell中做到吗?
data Effect where
WithEach :: (??? handle) => [handle] -> (handle -> Effect) -> Effect -- Want `handle' to be under closed set of types.
以下是太乏味了:
data Effect' where
WithEachPlayer :: [PlayerHandle] -> (PlayerHandle -> Effect) -> Effect
WithEachMinion :: [MinionHandle] -> (MinionHandle -> Effect) -> Effect
WithEachWeapon :: [WeaponHandle] -> (WeaponHandle -> Effect) -> Effect
编辑:
ØrjanJohansen建议使用封闭式家庭,这确实让我更接近我想要的东西。我使用它们的问题是我似乎无法写下以下内容:
type family IsHandle h :: Constraint where
IsHandle (PlayerHandle) = ()
IsHandle (MinionHandle) = ()
IsHandle (WeaponHandle) = ()
data Effect where
WithEach :: (IsHandle handle) => [handle] -> (handle -> Effect) -> Effect
enactEffect :: Effect -> IO ()
enactEffect (WithEach handles cont) = forM_ handles $ \handle -> do
print handle -- Eeek! Can't deduce Show, despite all cases being instances of Show.
enactEffect $ cont handle
这里GHC抱怨它无法推断句柄是Show
的实例。由于各种原因,在Show
构造函数中移动WithEach
约束,我犹豫要解决这个问题。这些包括模块化和可扩展性。像封闭数据家族这样的东西会解决这个问题(因为我知道类型族映射不是单射的......即使是封闭的问题也是问题吗?)
答案 0 :(得分:9)
我认为您可以使用closed 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
等作为超级班级。{/ li>
IsHandle'
作为一个类,在这种情况下,防止添加更多类型的唯一方法就是不导出IsHandle'
。最后一个的一个优点是它可以严重减少为此所需的扩展数量:
{-# LANGUAGE GADTs, ConstraintKinds #-}
class IsHandle' h
instance IsHandle' (PlayerHandle)
instance IsHandle' (MinionHandle)
instance IsHandle' (WeaponHandle)
type IsHandle h = (IsHandle' h, Show h)
答案 1 :(得分:7)
以下是基于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
答案 2 :(得分:4)
在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
答案 3 :(得分:2)
除非您想要对类型进行复杂的操作,否则我会使用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
答案 4 :(得分:2)
我使用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
答案 5 :(得分:1)
感谢所有解决方案的人。它们都有助于各种用例。对于我的用例,事实证明将句柄类型组合成一个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]