如何使用泛型类型注释来描述递归数据类型?

时间:2018-02-25 18:57:56

标签: haskell

这是功能:

comboGraph :: [a] -> Int -> [b]
comboGraph _ 0 = []
comboGraph [] _ = []
comboGraph (x:xs) n =
    (buildEdges x xs) : comboGraph xs n
    where   buildEdges h t = (h, comboGraph t (n-1))

Ths函数接收类型a的列表,一个数字,并返回类型b的列表。但是,正如您所看到的,类型b实际上是递归类型 - 它将是[(a, [(a, b1)])]的行。当我尝试编译时,我收到此错误:

• Couldn't match type ‘b’ with ‘(a, [b0])’
  ‘b’ is a rigid type variable bound by
    the type signature for:
      comboGraph :: forall a b. [a] -> Int -> [(a, [b])]
    at xxx.hs:15:15
  Expected type: [(a, [b])]
    Actual type: [(a, [(a, [b0])])]
• In the expression: (buildEdges x xs) : comboGraph xs n
  In an equation for ‘comboGraph’:
      comboGraph (x : xs) n
        = (buildEdges x xs) : comboGraph xs n
        where
            buildEdges h t = (h, comboGraph t (n - 1))

如何正确注释此功能?

2 个答案:

答案 0 :(得分:5)

为了使问题更加明显,让我们在定义的最后一个案例中替换buildEdges的定义:

comboGraph (x:xs) n =
    (x, comboGraph xs (n-1)) : comboGraph xs n

comboGraph的结果应该是一个列表,但其元素是一对,其中也有comboGraph个结果(即相同类型的列表)。正如您所说的类型错误所说,这不起作用 - 就像您想要一个有两个尾巴的列表一样。该修复程序正在切换到反映您尝试执行的操作的不同数据结构:

-- Feel free to substitute better names.
data Combo a = Empty | Node a (Combo a) (Combo a)
    deriving (Eq, Ord, Show)

Empty涵盖了用于生成空列表的基本案例,而Node为您希望在递归案例中组合的每个事物都有一个适当类型的字段。 comboGraph然后成为:

comboGraph :: [a] -> Int -> Combo a
comboGraph _ 0 = Empty
comboGraph [] _ = Empty
comboGraph (x:xs) n = Node x (comboGraph xs (n-1)) (comboGraph xs n)

(注意Combo实际上是一个二叉树,在节点上有值。)

答案 1 :(得分:4)

我喜欢其他答案,我认为你应该使用它。但它使得一些推理飞跃需要一些直觉,并且如果不用机械方式做几次就很难获得这种直觉。因此,在这个答案中,我将展示如何从一个失败的定义开始,如你所拥有的那样,"转动曲柄",并机械地获得一个有效的解决方案。以下技术可应用于任何无限类型错误。

你有以下条款(稍微转述):

comboGraph (x:xs) n =
    (x, comboGraph xs (n-1)) : {- ... -}

只是做一些直截了当的类型推理推理,我们可以看到comboGraph采用某种类型的列表(来自它在x:xs上匹配模式的事实)和一个数字(来自它的事实)减去一个)。让我们为列表元素挑选一个具体的(单态的但尚未知道的)类型a,看看我们可以推断出它返回的内容。

好吧,它清楚地返回一个里面有元组的列表。而元组的第一部分只是a。那第二部分呢?元组的第二部分是...... comboGraph返回的类型。因此,comboGraph会返回满足等式的类型t

t = [(a, t)]

这个等式的唯一解决方案是[(a, [(a, [(a, [(a, ...)])])])]。这种无限类型在Haskell中并不存在。但是有一个标准的技巧可以非常接近:通过引入一个newtype来使用(类型级别)递归。我们正在为t求解,但Haskell类型必须以大写字母开头,因此我们将这个等式命名为T

newtype T a = T [(a, T a)] deriving Show

现在我们完全拥有T a ~ [(a, T a)],但我们确实有同构现象:即\(T xs) -> xs :: T a -> [(a, T a)]T :: [(a, T a)] -> T a是反转的。所以现在我们可以通过利用这个同构来编写你的comboGraph定义。让我们来命名同构的另一半:

unT :: T a -> [(a, T a)]
unT (T xs) = xs

所以:

comboGraph (x:xs) n =
    T ((x, comboGraph xs (n-1)) : unT (comboGraph xs n))

基本案例必须包含在T中,当然:

comboGraph _ 0 = T []
comboGraph [] _ = T []

在ghci中尝试:

> comboGraph "abc" 3
T [('a',T [('b',T [('c',T [])]),('c',T [])]),('b',T [('c',T [])]),('c',T [])]