考虑这种Vect
类型:
{-# LANGUAGE GADTs, DataKinds, KindSignatures, RankNTypes #-}
import Data.Kind (Type)
data Nat = Zero | Succ Nat
data Vect :: Nat -> Type -> Type where
Nil :: Vect 'Zero a
Cons :: a -> Vect n a -> Vect ('Succ n) a
我想编写一个函数,该函数接受一个列表并将其懒惰地转换为这种类型。由于长度显然是未知的,因此我们需要使用一个存在的。我第一次尝试使用CPS和2级类型来模拟一个:
listToVect1 :: [a] -> (forall n. Vect n a -> b) -> b
listToVect1 [] f = f Nil
listToVect1 (x:xs) f = listToVect1 xs (f . Cons x)
但是,这并不懒。这只是一个折叠(尽管写为显式递归,因为实际上使用foldl
会让人难以置信),所以f
会一直运行到列表末尾才开始运行。>
我想不出以右关联方式进行CPS的方法,所以我再次尝试了,这次使用包装类型:
data ArbitraryVect a = forall n. ArbitraryVect (Vect n a)
nil :: ArbitraryVect a
nil = ArbitraryVect Nil
cons :: a -> ArbitraryVect a -> ArbitraryVect a
cons x (ArbitraryVect xs) = ArbitraryVect (Cons x xs)
listToVect2 :: [a] -> ArbitraryVect a
listToVect2 = foldr cons nil
此尝试的问题是我必须使用data
而不是newtype
(否则我会得到A newtype constructor cannot have existential type variables
),并且我需要在{{1}中严格进行模式匹配}(或者我得到cons
),因此它不会返回任何内容,直到遍历整个列表为止。
关于如何执行此操作,我没有其他想法。这有可能吗?如果是这样,怎么办?如果没有,为什么不呢?
答案 0 :(得分:6)
这将是一个不令人满意的答案,但是我还是要发布它。
从技术上讲,我认为这是不可能的,因为结果的开头应包含类型n
,并且不能延迟构造类型。它们有点“短暂”,在运行时并不真正存在,但是在逻辑上 必须是众所周知的。
(尽管我不确定100%是否正确)
但是,如果您确实需要这样做,则可以作弊。请注意,多态延续实际上对n
一无所知。关于n
唯一可以发现的是它是Zero
还是Succ
,但即使在运行时也没有进行任何编码。相反,它是从模式匹配期间从Vect
匹配的任何构造函数中推断出来的。这意味着在运行时,我们实际上不必为n
传递正确的类型。当然,如果编译器无法证明类型正确,则会在编译过程中感到焦虑,但是我们可以说服它使用unsafeCoerce
关闭:
listToVect1 :: [a] -> (forall n. Vect n a -> b) -> b
listToVect1 xs f = f $ go xs
where
go :: [a] -> Vect Zero a
go [] = Nil
go (x:xs) = unsafeCoerce $ Cons x (go xs)
此处,go
的第二行构造了一个Vect (Succ Zero) a
,但是随后将其n
组件擦除为Zero
。这发生在每一步,因此最终结果始终是Vect Zero a
。然后将此结果传递给延续,这是更明智的选择,因为它不在乎。
当延续在以后尝试与Vect
的构造函数匹配时,它可以正常工作,因为构造函数已按照正确的顺序正确实例化,反映了向量的正确形状,并且通过扩展,可以正确地理解n
的形状。
这可行,尝试一下:
vectLen :: Vect n a -> Int
vectLen Nil = 0
vectLen (Cons _ xs) = 1 + vectLen xs
toList :: Vect n a -> [a]
toList Nil = []
toList (Cons a xs) = a : toList xs
main :: IO ()
main = do
print $ listToVect1 [1,2,3] vectLen -- prints 3
print $ listToVect1 [] vectLen -- prints 0
print $ listToVect1 [1,2,3,4,5] vectLen -- prints 5
print $ listToVect1 [1,2,3] toList -- prints [1,2,3]
print $ listToVect1 ([] :: [Int]) toList -- prints []
print $ listToVect1 [1,2,3,4,5] toList -- prints [1,2,3,5]
当然,以上是脆弱的。它(在一定程度上)依赖于一些较低级别的知识。如果这不仅仅是好奇心练习,我宁愿回过头来重新思考导致您这样做的原始问题。
但是,就其价值而言,这种“通过挥手隐藏丑陋”的技术在较低级别的库中相对较普遍。