如何将列表延迟转换为这种类型?

时间:2019-11-11 13:49:23

标签: haskell lazy-evaluation dependent-type existential-type continuation-passing

考虑这种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),因此它不会返回任何内容,直到遍历整个列表为止。

关于如何执行此操作,我没有其他想法。这有可能吗?如果是这样,怎么办?如果没有,为什么不呢?

1 个答案:

答案 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]

当然,以上是脆弱的。它(在一定程度上)依赖于一些较低级别的知识。如果这不仅仅是好奇心练习,我宁愿回过头来重新思考导致您这样做的原始问题。

但是,就其价值而言,这种“通过挥手隐藏丑陋”的技术在较低级别的库中相对较普遍。