如何在不进入无限循环的情况下修改Haskell列表?

时间:2017-03-01 10:26:54

标签: debugging haskell recursion infinite-loop lazy-evaluation

我正在Haskell中编写一段代码,我有一行代码如下:

addElement :: [a] -> a -> [a]
addElement list elem = list ++ [elem]

我需要(或者至少我认为)这样的函数,以便在我实现的图形数据结构的顶点列表中添加新顶点。现在,我可以按如下方式调用此函数

newlist = addElement oldlist elem

一切都很好。但是,如果我写

mylist = addElement mylist elem

然后在调用终止后尝试用mylist做任何事情(确实如此),我进入一个无限循环,如果我理解正确,这是由于对Haskell的懒惰评估或类似的事情({{1如果我做对了,我会扩展到mylist

这对我的特定实现当然不好,因为为了我的目的,我现在每次需要向列表添加元素时都必须创建新列表。那么如何使元素添加功能按照我想要的方式工作呢?

1 个答案:

答案 0 :(得分:3)

首先,mylist = addElement mylist elem是一个等式,不是作业。它是评估一次:因为Haskell是一种声明性语言,你不能改变一个变量:一旦你给它一个值,它将始终具有该值。

因此,您的等式将导致:

mylist = addElement mylist elem
       = addElement (addElement mylist elem) elem
       = addElement (... (addElement mylist elem) ...) elem

你明白了。

尽管如此,每次都不需要构建一个完整的新列表:您只需使用(h:t) 附加到头部

addElement :: [a] -> a -> [a]
addElement t h = (h:t)

这将构建一个新的"在 O(1)中列出,将旧列表重新用作尾部。如前所述,元素将被添加到前面。

解决此问题的另一种方法是使用 差异列表 。这里的列表表示为:

type DiffList a = a -> [a]

并且空列表是:

emptyDiffList :: DiffList a
emptyDiffList = \x -> x

在这种情况下,你差异列表用于:

groundDiffList :: DiffList a -> [a]
groundDiffList x = x []

您可以使用以下内容向末尾添加元素

addElement :: DiffList a -> a -> DiffList a
addElement l el = \x -> l (el:x)

尽管如此,你总是需要为"新列表创建一个新变量":你不能一下子给mylist另一个值(你可以当然使用递归但在这种情况下,这些是技术上两个不同的变量:调用者mylist被调用者mylist