我想为 Data.Map 创建一个特殊的智能构造函数,对键/值对关系的类型有一定的约束。这是我试图表达的约束:
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, DataKinds #-}
data Field = Speed | Name | ID
data Value = VFloat Float | VString ByteString | VInt Int
class Pair f b | f -> b where
toPair :: f -> b -> (f, b)
toPair = (,)
instance Pair Speed (VFloat f)
instance Pair ID (VInt i)
对于每个字段,只有一种类型的值应该与之关联。就我而言,将Speed
字段映射到ByteString
. 一个Speed
字段应该唯一地映射到一个Float
但我收到以下类型错误:
Kind mis-match
The first argument of `Pair' should have kind `*',
but `VInt' has kind `Value'
In the instance declaration for `Pair Speed (VFloat f)'
使用-XKindSignatures
:
class Pair (f :: Field) (b :: Value) | f -> b where
toPair :: f -> b -> (f, b)
toPair = (,)
Kind mis-match
Expected kind `OpenKind', but `f' has kind `Field'
In the type `f -> b -> (f, b)'
In the class declaration for `Pair'
我明白为什么我会得到 Kind 不匹配,但是我该如何表达这个约束,以便toPair
在不匹配的Field
和Value
.
#haskell 建议我使用 a GADT
,但我还没有弄清楚。
这样做的目标是能够写
type Record = Map Field Value
mkRecord :: [Field] -> [Value] -> Record
mkRecord = (fromList .) . zipWith toPair
这样我就可以Map
在尊重键/值不变量的地方制作安全的 s。
所以这应该类型检查
test1 = mkRecord [Speed, ID] [VFloat 1.0, VInt 2]
但这应该是编译时错误
test2 = mkRecord [Speed] [VInt 1]
编辑:
我开始认为我的具体要求是不可能的。使用我原来的例子
data Foo = FooInt | FooFloat
data Bar = BarInt Int | BarFloat Float
为了在Foo
and上强制执行约束,必须有某种方法在类型级别Bar
区分 a FooInt
and并且类似地 for 。因此我需要两个 GADTFooFloat
Bar
data Foo :: * -> * where
FooInt :: Foo Int
FooFloat :: Foo Float
data Bar :: * -> * where
BarInt :: Int -> Bar Int
BarFloat :: Float -> Bar Float
现在我可以编写一个实例Pair
,仅当Foo
和Bar
都被标记为相同类型时才成立
instance Pair (Foo a) (Bar a)
我有我想要的属性
test1 = toPair FooInt (BarInt 1) -- type-checks
test2 = toPair FooInt (BarFloat 1) -- no instance for Pair (Foo Int) (Bar Float)
但我失去了写作的能力,xs = [FooInt, FooFloat]
因为那需要一个异构的列表。此外,如果我尝试使用Map
同义词type FooBar = Map (Foo ?) (Bar ?)
,我会坚持使用Map
只有Int
类型或只有Float
类型,这不是我想要的。它看起来相当绝望,除非有一些我不知道的强大的类型类魔法。