将我的脚趾浸入依赖类型的水域中,我对规范的“具有静态类型长度的列表”示例进行了破解。
{-# LANGUAGE DataKinds, GADTs, KindSignatures #-}
-- a kind declaration
data Nat = Z | S Nat
data SafeList :: (Nat -> * -> *) where
Nil :: SafeList Z a
Cons :: a -> SafeList n a -> SafeList (S n) a
-- the type signature ensures that the input list has at least one element
safeHead :: SafeList (S n) a -> a
safeHead (Cons x xs) = x
这似乎有效:
ghci> :t Cons 5 (Cons 3 Nil)
Cons 5 (Cons 3 Nil) :: Num a => SafeList ('S ('S 'Z)) a
ghci> safeHead (Cons 'x' (Cons 'c' Nil))
'x'
ghci> safeHead Nil
Couldn't match type 'Z with 'S n0
Expected type: SafeList ('S n0) a0
Actual type: SafeList 'Z a0
In the first argument of `safeHead', namely `Nil'
In the expression: safeHead Nil
In an equation for `it': it = safeHead Nil
但是,为了使该数据类型真正有用,我应该能够从运行时数据构建它,而您在编译时不知道其长度。我天真的尝试:
fromList :: [a] -> SafeList n a
fromList = foldr Cons Nil
编译失败,出现类型错误:
Couldn't match type 'Z with 'S n
Expected type: a -> SafeList n a -> SafeList n a
Actual type: a -> SafeList n a -> SafeList ('S n) a
In the first argument of `foldr', namely `Cons'
In the expression: foldr Cons Nil
In an equation for `fromList': fromList = foldr Cons Nil
我明白为什么会发生这种情况:返回类型Cons
折叠的每次迭代都是不同的 - 这就是重点!但我看不到解决办法,可能是因为我对这个主题的阅读不够深入。 (我无法想象所有这些努力都被投入到一个在实践中不可能使用的类型系统中!)
那么:如何从“正常”简单类型数据构建这种依赖类型数据?
按照@luqui的建议,我能够做到fromList
编译:
data ASafeList a where
ASafeList :: SafeList n a -> ASafeList a
fromList :: [a] -> ASafeList a
fromList = foldr f (ASafeList Nil)
where f x (ASafeList xs) = ASafeList (Cons x xs)
这是我尝试打开包装ASafeList
并使用它:
getSafeHead :: [a] -> a
getSafeHead xs = case fromList xs of ASafeList ys -> safeHead ys
这会导致另一个类型错误:
Couldn't match type `n' with 'S n0
`n' is a rigid type variable bound by
a pattern with constructor
ASafeList :: forall a (n :: Nat). SafeList n a -> ASafeList a,
in a case alternative
at SafeList.hs:33:22
Expected type: SafeList ('S n0) a
Actual type: SafeList n a
In the first argument of `safeHead', namely `ys'
In the expression: safeHead ys
In a case alternative: ASafeList ys -> safeHead ys
同样,直观上来说,这将无法编译是有道理的。我可以打电话fromList
有一个空列表,所以编译器不能保证我能够调用safeHead
关于由此产生的SafeList
。这种知识的缺乏大致就是存在主义的ASafeList
捕获。
这个问题能解决吗?我觉得我可能已经走上了逻辑的死胡同。