I am creating a turn based game. I want to define a datatype that encodes one type out of many possible types. Here is the motivating example:
I have defined a Turn
type using GADTs, so the type of each value of Turn a
says something about it's value.
data Travel
data Attack
data Flee
data Quit
data Turn a where
Travel :: Location -> Turn Travel
Attack :: Int -> Turn Attack
Flee :: Turn Flee
Quit :: Turn Quit
Now I can write types like this decideTravel :: GameState -> Turn Travel
, very expressive and nice.
The problem arises when I want to return one of multiple possible turn types. I want to write functions similar to the following:
-- OneOf taking two types
decideFightingTurn :: GameState -> OneOf (Turn Attack) (Turn Flee)
-- OneOf takes three types
decideTurn :: GameState -> OneOf (Turn Attack) (Turn Travel) (Turn Quit)
Where this OneOf
datatype carries the notion of "one type out of many possible types". The problem lies in defining this datatype, I need to somehow handle a list of types at the type level.
There are two subpar solutions I have as of now:
Solution 1: Create a wrapper sum type
Simply create a new type that has a constructor for each Turn a
constructor:
data AnyTurn
= TravelTurn (Turn Travel)
| TravelAttack (Turn Attack)
| TravelFlee (Turn Flee)
| TravelQuit (Turn Quit)
This doesn't help me in the way I want it to. Now I have to pattern match all cases of AnyTurn
and account for invalid input types. Additionally the type level information is obscured by the AnyTurn
type, since it fails to indicate which specific turns are actually possible at the type level.
Solution 2: Create "Either" types for different numbers of types
This solution gives me what I want at the type level, but is cumbersome to use. Basically create an Either
-like type for any number of combinations, like so:
data OneOf2 a b
= OneOf2A a
| OneOf2B b
data OneOf3 a b c
= OneOf3A a
| OneOf3B b
| OneOf3C c
-- etc, for as many as are needed.
This conveys what I want at the type level, so I can now write the previous examples as:
decideFightingTurn :: GameState -> OneOf2 (Turn Travel) (Turn Flee)
decideTurn :: GameState -> OneOf3 (Turn Attack) (Turn Travel) (Turn Quit)
This works, however it would be nice to express this with just one type OneOf
. Is there any way to write the generalized OneOf
type in Haskell?
()
s by making it a constraint. For example,type family Elem x xs where Elem x [] = False; Elem x (x:xs) = True; Elem x (_:xs) = Elem x xs
and thendata AnyTurn allowed where AnyTurn :: Elem x allowed ~ True => Turn x -> AnyTurn
. – Sprinkler