Haskell:处理死锁的自引用列表

时间:2017-09-28 20:49:52

标签: list haskell recursion deadlock self-reference

为什么GHC允许以下内容永远阻止,是否有任何有用的理由:

list = 1 : tail list

在列表迭代器/生成器中似乎有点复杂,我们应该能够做一些更有用的事情:

  1. 返回error "Infinitely blocking list"
  2. 返回[1,1]
  3. 解释2:当进入生成器获取元素N时,我们可能会将生成器内的所有自引用限制在列表中,但以N-1结束(我们注意到{ {1}}在范围read N内并返回列表末尾)。这是一种使用范围的简单死锁检测。

    显然,这对上面的玩具示例没那么有用,但它可能允许更有用/更优雅的有限,自引用列表定义,例如:

    generate N

    请注意,任何更改都应该只影响当前会导致无限块的列表生成器,因此它们似乎是向后兼容的语言更改。

    忽略暂时进行此类更改所需的GHC复杂性,这种行为会破坏我遗漏的任何现有语言行为吗?关于这种变化的“优雅”的任何其他想法?

    另见另一个BFS示例,可以从下面获益。对我来说,这似乎比其他一些解决方案更实用/更优雅,因为我只需要定义bfsList ,而不是如何来生成它(即指定终止条件):

    primes = filter (\x -> none ((==0).mod x) primes) [2..]
    

2 个答案:

答案 0 :(得分:5)

以下是list = 1 : ⊥的指称视角。

首先,一点背景。在Haskell中,值由" definedness"部分排序,其中解决⊥(" bottom")的值比没有的值更少定义。所以

  • 的定义不如1 : ⊥
  • 1 : ⊥的定义不如1 : 2 : 3 : []

但它是一个部分订单,所以

  • 1 : ⊥ 的定义不如2 : 3 : ⊥,也不是更明确的定义。

即使第二个列表更长。 {1}仅比以1开头的列表定义得更少。我强烈建议阅读关于Haskell的denotational semantics

现在回答你的问题。看看

1 : ⊥

作为要解决的等式而不是"函数声明"。我们这样改写:

list = 1 : tail list

以这种方式查看,我们发现list = ((1 :) . tail) list 固定点

list

其中list = f list 。在Haskell语义中,通过根据上述顺序找到最小不动点来解决递归值。

找到这个的方法非常简单。如果从⊥开始,然后反复应用该函数,您将发现一个不断增加的值链。链条停止变化的点will be the least fixed point(技术上它将是链的极限,因为它可能永远不会停止变化)。

从⊥开始,

f = (1 :) . tail

我们看到⊥不是一个固定的点,因为我们没有从另一端得到。。因此,让我们再次尝试我们的结果:

f ⊥ = ((1 :) . tail) ⊥ = 1 : tail ⊥

哦,看,这是一个固定的点,我们得到了同样的东西。

这里重点是它是至少的一个。您的解决方案f (1 : tail ⊥) = ((1 :) . tail) (1 : tail ⊥) = 1 : tail (1 : tail ⊥) = 1 : tail ⊥ 也是一个固定点,因此它解决了等式:

[1,1] = 1:1:[]

但是,当然,每个以1开头的列表都是一个解决方案,我们应该如何在它们之间做出选择。但是,我们通过递归f (1:1:[]) = ((1 :) . tail) (1:1:[]) = 1 : tail (1:1:[]) = 1:1:[] 找到的那个没有比所有这些更明确,它提供的信息不是方程所需的信息,而是由语言指定的信息。

答案 1 :(得分:2)

即使list在GHCi下永远循环,使用GHC 编译的正确二进制文件检测到循环并发出错误信号。如果您编译并运行:

list = 1 : tail list
main = print list

它以错误消息终止:

Loop: <<loop>>

您的primes示例也是如此。

正如其他人所说,GHC没有检测到所有可能的循环。如果这样做,那么它将解决停机问题,这可能会使Haskell更受欢迎。

它返回错误(或&#34;卡住&#34;)而不是返回[1,1]的原因是因为表达式:

list = 1 : tail list

在Haskell语言中有明确定义的语义。这些语义为它赋值,这个值是&#34; bottom&#34; (或&#34;错误&#34;或符号_|_),正如head [1,2,3]的值为1一样。

(嗯,从技术上讲,list的价值是1 : _|_,这几乎是#34;这就是@Justin Li在评论中谈到的内容。我&#39 ; ve试图解释为什么它具有以下值。)

虽然您可能看不到使用程序或返回底部的表达式而没有看到将非底层语义分配给此类表达式的危害,因为它是&#34;向后兼容&#34;,大多数Haskell社区中的人(语言设计人员,编译器开发人员和有经验的用户)会不同意您的意见,因此不要期望他们取得很大进展。

至于你提出的具体新语义,它们不清楚。为什么list的值不等于[1]?在我看来,当我进入&#34;发电机&#34;要获得元素n = 1(零索引,所以第二个元素)并评估tail list,那么结束于元素n-1 = 0的list[1],其尾部等于{ {1}},所以我想我应该得到以下内容,对吧?

[]

为什么价值(几乎)底部

这里为什么list = 1 : tail list = 1 : tail [1] -- use list-so-far = 1 : [] = [1] 的值(差不多)是根据标准Haskell的语义(但最后请注意)。

作为参考,list的定义是:

tail

让我们尝试完全&#34;使用Haskell语义评估tail l = case l of _:xs -> xs [] -> error "ack, you dummy!"

list

并且最后的无限循环是表达式为&#34;几乎是底部&#34;。

注意:实际上有几组不同的Haskell语义,不同的计算Haskell表达式值的方法。黄金标准是@ luqui的回答中描述的指称语义。我上面使用的那些,充其量只是非正式语义的一种形式&#34;在Haskell报告中描述,但他们已经足够好以获得正确的答案。