一种将Haskell的任何类型概括为任意多种类型的方法?
我正在创建一个回合制游戏。我想定义一个数据类型,从许多可能的类型中编码一种类型。这是一个励志的例子:
我已经Turn使用 GADT定义了一个类型,所以每个值的类型都Turn a说明了它的值。
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
现在我可以写出这样的类型decideTravel :: GameState -> Turn Travel,非常有表现力而且很好。
当我想返回多种可能的转弯类型之一时,就会出现问题。我想编写类似于以下的函数:
-- OneOf taking two types
decideFightingTurn :: GameState -> OneOf (Turn Attack) (Turn Flee)
-- OneOf takes three types
decideTurn :: GameState -> OneOf (Turn Attack) (Turn Travel) (Turn Quit)
此OneOf数据类型带有“多种可能类型中的一种类型”的概念。问题在于定义此数据类型,我需要以某种方式处理类型级别的类型列表。
到目前为止,我有两个低于标准的解决方案:
解决方案 1:创建包装总和类型
只需为每个Turn a构造函数创建一个具有构造函数的新类型:
data AnyTurn
= TravelTurn (Turn Travel)
| TravelAttack (Turn Attack)
| TravelFlee (Turn Flee)
| TravelQuit (Turn Quit)
这并没有以我想要的方式帮助我。现在我必须AnyTurn对无效输入类型的所有情况进行模式匹配并考虑在内。此外,类型级别信息被AnyTurn类型掩盖了,因为它无法指示在类型级别实际上哪些特定转弯是可能的。
解决方案 2:为不同数量的类型创建“Either”类型
这个解决方案给了我我想要的类型级别,但使用起来很麻烦。基本上Either为任意数量的组合创建一个-like 类型,如下所示:
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.
这在类型级别传达了我想要的内容,因此我现在可以将前面的示例编写为:
decideFightingTurn :: GameState -> OneOf2 (Turn Travel) (Turn Flee)
decideTurn :: GameState -> OneOf3 (Turn Attack) (Turn Travel) (Turn Quit)
这是有效的,但是只用一种 type 来表达它会很好OneOf。有没有办法OneOf在 Haskell 中编写通用类型?
回答
像这样,我猜:
data OneOf as where
ThisOne :: a -> OneOf (a : as)
Later :: OneOf as -> OneOf (a : as)
然后你可以写,例如:
decideFightingTurn :: GameState -> OneOf [Turn Travel, Turn Flee]
您可能还想:
type family Map f xs where
Map f '[] = '[]
Map f (x:xs) = f x : Map f xs
通过这种方式,您可以编写如下内容:
decideFightingTurn :: GameState -> OneOf (Map Turn [Travel, Flee])
或者,Map如果您认为自己会一直这样做,则可以将其构建到 GADT 中:
data OneOfF f as where
ThisOneF :: f a -> OneOfF f (a : as)
LaterF :: OneOfF f as -> OneOfF f (a : as)
进而:
decideFightingTurn :: GameState -> OneOfF Turn [Travel, Flee]
如果这成为一个问题,可以采取各种措施来提高效率;我会尝试的第一件事是使用二进制索引而不是这里所示的一元索引。