【问题标题】:How do I modify a Haskell list without entering an infinite loop?如何在不进入无限循环的情况下修改 Haskell 列表?
【发布时间】:2017-07-20 15:09:18
【问题描述】:

我正在用 Haskell 编写一段代码,其中有一行代码是这样的:

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

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

newlist = addElement oldlist elem

一切正常。但是,如果我写

mylist = addElement mylist elem

然后在调用终止后尝试对 mylist 执行任何操作(确实如此),我进入一个无限循环,如果我理解正确,这是由于 Haskell 的惰性评估或类似的东西(mylist如果我做对了,会扩展到addElement (addElement ... elem) elem?)。

这对于我的特定实现当然是不利的,因为就我的目的而言,我现在每次需要向列表中添加元素时都必须创建新列表。那么如何制作一个按我想要的方式工作的元素添加功能呢?

【问题讨论】:

  • mylist = addElement mylist elem 这不是作业,而是等式。 Haskell 没有更新。在上下文中展示您的尝试。您是如何尝试构建 mylist 的?
  • 嗯,我基本上是先运行mylist = [],然后,比如说mylist = addElement mylist 3。第二次调用相当于mylist = mylist ++ [3]。例如,如果我在 ghci 中键入 id mylist,则什么也不会发生,我会陷入困境。那么我是否应该认为在 Haskell 中不可能有一个函数 addElement 最终会导致 id mylist 导致 [3]
  • 不,请显示您尝试构建 mylist 的整个函数。
  • 如果你不需要按特定顺序构建列表,那么在前面添加一个元素就是解决方案(这是一个 O(1) 操作)。即使您需要这个特定的顺序,以相反的顺序构建列表并最终将其反转一次可能会更便宜。

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


【解决方案1】:

首先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)。

【讨论】:

  • 感谢您的详尽回答,我认为您的第一段和最后一段最能回答我的问题。因此,我必须为每个“新列表”引入新变量这一事实基本上是我在 Haskell 中必须忍受的限制,我(或其他任何人)对此无能为力。这足以让我接受。
  • @A.Sh:确实,虽然这不是真正的限制:您可以编写任何程序而无需重新定义变量。此外,我个人认为有这样的限制是有用的,因为你不必在过程中对变量改变值进行推理。在我看来,声明式语言往往更容易调试。
  • @WillemVanOnsem 是的,既然你这么说。我对函数式(和声明式)编程还是很陌生,所以我想我的想法仍然受到我之前做过的所有命令式编程的偏见。
猜你喜欢
  • 1970-01-01
  • 2017-10-06
  • 2013-06-12
  • 2011-11-02
  • 1970-01-01
  • 2012-09-25
  • 1970-01-01
  • 2015-12-01
  • 2019-10-02
相关资源
最近更新 更多