Given:
newtype PlayerHandle = PlayerHandle Int deriving (Show)
newtype MinionHandle = MinionHandle Int deriving (Show)
newtype WeaponHandle = WeaponHandle Int deriving (Show)
In the following code, I would like handle
to be exactly one of three types: PlayerHandle
, MinionHandle
, and WeaponHandle
. Is this possible to do in Haskell?
data Effect where
WithEach :: (??? handle) => [handle] -> (handle -> Effect) -> Effect -- Want `handle' to be under closed set of types.
The following is too tedious:
data Effect' where
WithEachPlayer :: [PlayerHandle] -> (PlayerHandle -> Effect) -> Effect
WithEachMinion :: [MinionHandle] -> (MinionHandle -> Effect) -> Effect
WithEachWeapon :: [WeaponHandle] -> (WeaponHandle -> Effect) -> Effect
EDIT:
Ørjan Johansen has proposed using closed type families, which indeed gets me a step closer to what I want. The issue I'm having using them is that I can't seem to write the following:
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
Here GHC complains that it cannot deduce that the handle is an instance of Show
. I am hesitant to solve this by moving the Show
constraint in the WithEach
constructor for various reasons. These include modularity and scalability. Would something like a closed data family solve this (as I know type family mappings are not injective... Is that the problem even with closed ones?)
Thanks for all the solutions guys. They all are helpful for various use cases. For my use case, it turned out that making the handle types into a single GADT solved my problem.
Here's my derived solution for those interested:
{-# 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]