了解Haskell中的类型级函数

时间:2020-08-05 03:16:08

标签: haskell type-level-computation

我试图了解以下代码中的PlusTimes函数如何工作。我不明白的是:

  1. 您如何在ghci中调用这些函数以对矢量进行运算?
  2. 为什么它们必须在类型级别上进行操作?他们使用哪种类型的手机?
  3. 他们如何评估结果?

对于plus,我们有Plus (S m) n = S (Plus m n),但如何评估(Plus m n)。同样,(Times n m)是如何评估的?

{-# LANGUAGE DataKinds, KindSignatures, TypeFamilies, GADTs #-}
{-# LANGUAGE UndecidableInstances, StandaloneDeriving #-}

module Vector where

-- Natural numbers, values will be promoted to types
data Nat
  = Z     -- Zero
  | S Nat -- Successor (+1)

data Vec (a :: *) :: Nat -> * where
  -- Nil has zero length
  Nil :: Vec a Z
  -- Cons has length of the tail + 1
  Cons :: a -> Vec a n -> Vec a (S n)

deriving instance Show a => Show (Vec a n)

-- head :: [a] -> a
hd :: Vec a (S n) -> a
hd (Cons x xs) = x

-- tail :: [a] -> [a]
tl :: Vec a (S n) -> Vec a n
tl (Cons x xs) = xs

-- map :: (a -> b) -> [a] -> [b]
vMap :: (a -> b) -> Vec a n -> Vec b n
vMap f Nil = Nil
vMap f (Cons x xs) = Cons (f x) (vMap f xs)

-- (++) :: [a] -> [a] -> [a]
vAppend :: Vec a n -> Vec a m -> Vec a (Plus n m)
vAppend Nil         xs = xs
vAppend (Cons y ys) xs = Cons y (vAppend ys xs)

-- Type-level addition
type family Plus (x :: Nat) (y :: Nat) :: Nat where
  Plus Z     n = n
  Plus (S m) n = S (Plus m n)


-- concat :: [[a]] -> [a]
vConcat :: Vec (Vec a n) m -> Vec a (Times m n)
vConcat Nil = Nil
vConcat (Cons xs xss) = xs `vAppend` vConcat xss

-- Type-level multiplication
type family Times (x :: Nat) (y :: Nat) :: Nat where
  Times Z     m = Z
  Times (S n) m = Plus m (Times n m)

vFilter :: (a -> Bool) -> Vec a n -> [a]
vFilter p Nil = []
vFilter p (Cons x xs)
  | p x = x : vFilter p xs
  | otherwise = vFilter p xs

1 个答案:

答案 0 :(得分:1)

让我首先为Cons定义一个更方便的同义词:

infixr 5 #:
(#:) :: a -> Vec a n -> Vec a (S n)
(#:) = Cons

您如何在ghci中调用这些函数以对矢量进行运算?

您可以像其他任何值级函数一样从GHCi调用值级函数。首先,这将调用任何必要的类型级别的计算,然后像运行其他Haskell代码一样运行经过类型检查的代码。

*Vector> :set -XDataKinds
*Vector> let v = 4 #: 9 #: 13 #:Nil

*Vector> :t v
v :: Num a => Vec a ('S ('S ('S 'Z)))
*Vector> v
Cons 4 (Cons 9 (Cons 13 Nil))

*Vector> let w = 7 #: 8 #: 6 #:Nil

*Vector> :t vAppend v w
vAppend v w :: Num a => Vec a ('S ('S ('S ('S ('S ('S 'Z))))))
*Vector> vAppend v w
Cons 4 (Cons 9 (Cons 13 (Cons 7 (Cons 8 (Cons 6 Nil)))))

要单独将类型族评估为类型级别的函数,请使用GHCi的:kind!命令:

*Vector> :kind! Plus ('S 'Z) ('S ('S ('S 'Z)))
Plus ('S 'Z) ('S ('S ('S 'Z))) :: Nat
= 'S ('S ('S ('S 'Z)))

为什么它们必须在类型级别上进行操作?他们使用哪种类型?

在此示例中,让它们在类型级别上运行的目的是编译器应捕获任何长度不匹配的错误,而不是导致运行时错误。例如,您可能想编写一个函数,接受两个应该具有相同长度的列表。这是不安全的:

foo :: [a] -> [a] -> String

但这完全表明长度必须相同:

foo' :: Vec a n -> Vec a n -> String

一个具体的例子是邮政编码。 Prelude.zip确实允许使用不同长度的列表,但这意味着将较长的列表基本上修剪为较短的列表,这可能会导致意外的行为。使用Vector版本

vZip :: Vec a n -> Vec b n -> Vec (a,b) n
vZip Nil Nil = Nil
vZip (Cons x xs) (Cons y ys) = Cons (x,y) $ vZip xs ys

这不可能发生:

*Vector> vZip v w
Cons (4,7) (Cons (9,8) (Cons (13,6) Nil))

*Vector> vZip (vAppend v v) w
<interactive>:22:7: error:
    • Couldn't match type ‘'S ('S ('S 'Z))’ with ‘'Z’
      Expected type: Vec a ('S ('S ('S 'Z)))
        Actual type: Vec a (Plus ('S ('S ('S 'Z))) ('S ('S ('S 'Z))))
    • In the first argument of ‘vZip’, namely ‘(vAppend v v)’
      In the expression: vZip (vAppend v v) w
      In an equation for ‘it’: it = vZip (vAppend v v) w

请注意,即使此表达式是在大程序中的某个深处编写的,您也会在编译时立即得到错误,而不是稍后的运行时问题。


他们如何评估结果?

这取决于情况,您实际上不需要关心,但是重要的一点是,编译器会清除所有可能出错的内容,并立即向您显示错误消息。如果所有类型的计算都可以证明是正确的,则编译器会将与这些类型相对应的代码硬连线到您的程序中,然后擦除类型本身,并且运行时的工作原理基本上类似于具有简单旧列表的Haskell程序。


不幸的是,有时当代码实际上是正确且安全的,但编译器无法证明它时,它也会给您一条错误消息。
相关问题