List Haskell:处理死锁的自引用列表
GHC是否有任何有用的理由允许以下内容永久阻止:List Haskell:处理死锁的自引用列表,list,haskell,recursion,deadlock,self-reference,List,Haskell,Recursion,Deadlock,Self Reference,GHC是否有任何有用的理由允许以下内容永久阻止: list = 1 : tail list 在列表迭代器/生成器中,似乎有点复杂,我们应该能够做一些更有用的事情: 返回错误“无限阻塞列表” 返回[1,1] 解释2:当进入生成器以获取元素N时,我们可能会在生成器内进行所有自引用,仅限于列表,但以N-1结尾(我们注意到范围内的读取N并返回列表结尾)。这是一种使用作用域的简单死锁检测 显然,这对于上面的玩具示例没有多大用处,但它可能允许更有用/优雅的有限、自引用列表定义,例如: primes = f
list = 1 : tail list
在列表迭代器/生成器中,似乎有点复杂,我们应该能够做一些更有用的事情:
错误“无限阻塞列表”
[1,1]
N
时,我们可能会在生成器内进行所有自引用,仅限于列表,但以N-1
结尾(我们注意到范围内的读取N
并返回列表结尾)。这是一种使用作用域的简单死锁检测
显然,这对于上面的玩具示例没有多大用处,但它可能允许更有用/优雅的有限、自引用列表定义,例如:
primes = filter (\x -> none ((==0).mod x) primes) [2..]
请注意,这两种更改都只会影响当前将导致无限块的列表生成器,因此它们看起来向后兼容语言更改
暂时忽略进行这种更改所需的GHC复杂性,这种行为会破坏我所缺少的任何现有语言行为吗?关于这一变化的“优雅”还有其他想法吗
另请参见下面的另一个BFS示例。对我来说,这似乎比其他一些解决方案更实用/优雅,因为我只需要定义bfsList是什么,而不是如何生成它(即指定终止条件):
即使list
永远在GHCi下循环,使用GHC编译的适当二进制文件也会检测到循环并发出错误信号。如果编译并运行:
list = 1 : tail list
main = print list
它以错误消息终止:
Loop: <<loop>>
在Haskell语言中具有定义良好的语义。这些语义赋予它一个值,这个值是“底部”(或“错误”或符号\u124;
),就像head[1,2,3]
的值是1
一样
(从技术上讲,list
的值是1:| |
,几乎是“最底层”。这就是@Justin Li在他的评论中所说的。我试图在下面解释为什么它有这个值。)
虽然您可能看不到返回bottom的程序或表达式的使用,也看不到在“向后兼容”的基础上为此类表达式指定非bottom语义的危害,但Haskell社区的大多数人(语言设计师、编译器开发人员和有经验的用户)都会不同意您的观点,因此,不要期望与他们一起取得多大进展
至于你提出的具体的新语义,它们还不清楚。为什么列表的值不等于[1]
?在我看来,当我输入“生成器”以获取元素n=1(零索引,因此第二个元素)并计算尾部列表时,那么以元素n-1=0结尾的列表是[1]
,尾部等于[]
,所以我认为应该得到以下结果,对吗
list = 1 : tail list
= 1 : tail [1] -- use list-so-far
= 1 : []
= [1]
为什么该值(几乎)处于底部
根据标准Haskell的语义,list
的值(几乎)位于底部(但请参见末尾的注释)
作为参考,tail
的定义实际上是:
tail l = case l of _:xs -> xs
[] -> error "ack, you dummy!"
让我们尝试使用Haskell语义“完全”评估list
:
-- evaluating `list` using definition of `list`
list = 1 : tail list
-- evaluating `tail list` using definition of `tail`
list = 1 : case list of _:xs -> xs
...
-- evaluating case construct requires matching `list` to
-- a pattern, this requires evaluation of `list` using its defn
list = 1 : case (1 : tail list) of _:xs -> xs
...
-- case pattern match succeeds
list = 1 : let xs = tail list in xs -- just to be clear
= 1 : tail list
-- awesome, now all we need to do is evaluate:
list = 1 : tail list
-- ummm, Houston, we have a problem
最后的无限循环就是表达式“几乎是底部”的原因
注意:实际上有几个不同的Haskell语义集,计算Haskell表达式值的不同方法。黄金标准是@luqui回答中描述的指称语义。我在上面使用的那些,充其量是Haskell报告中描述的“非正式语义”的一种形式,但它们足够好,可以得到正确的答案。下面是一个关于list=1:⊥代码>
首先,一点背景。在Haskell中,值按“定义”部分排序,其中值包含&bot;(“底部”)的定义比没有的定义少。所以
⊥代码>的定义小于1:⊥代码>
1:⊥代码>的定义少于1:2:3:[]
但这是部分订单,所以
1:⊥代码>的定义不少于2:3:⊥代码>,也没有更多的定义李>
即使第二个列表更长<代码>1:⊥代码>的定义仅比以1开头的列表少。我强烈推荐阅读哈斯克尔的著作
现在谈谈你的问题。看
list = 1 : tail list
作为要求解的方程,而不是“函数声明”。我们这样重写它:
list = ((1 :) . tail) list
从这个角度来看,我们看到list
是一个固定点
其中f=(1:)。尾部
。在Haskell语义中,递归值是通过根据上述顺序找到最小不动点来解决的
找到这个的方法很简单。如果你从⊥, 然后反复应用这个函数,你会发现一个不断增加的价值链。链停止变化的点(从技术上讲,这是链的极限,因为它可能永远不会停止变化)
从⊥,
f ⊥ = ((1 :) . tail) ⊥ = 1 : tail ⊥
我们看到了⊥ 已经不是一个固定点了,因为我们没有得到⊥ 从另一端出去。那么,让我们用我们得到的东西再试一次:
f (1 : tail ⊥) = ((1 :) . tail) (1 : tail ⊥)
= 1 : tail (1 : tail ⊥)
= 1 : tail ⊥
哦,看,这是一个固定点,我们得到的东西和我们放进去的一样
这里重要的一点是,这是最起码的一个。您的解决方案[1,1]=1:1:[]
也是一个固定点,因此它解决了以下等式:
f (1:1:[]) = ((1 :) . tail) (1:1:[])
= 1 : tail (1:1:[])
= 1:1:[]
但当然,每个以1开头的列表都是一个解决方案,我们不清楚应该如何在它们之间进行选择。然而,我们通过递归1找到的:⊥代码>的定义比它们都少,它提供的信息不超过公式所需的信息,这是语言指定的信息。虽然这可能是simp
f ⊥ = ((1 :) . tail) ⊥ = 1 : tail ⊥
f (1 : tail ⊥) = ((1 :) . tail) (1 : tail ⊥)
= 1 : tail (1 : tail ⊥)
= 1 : tail ⊥
f (1:1:[]) = ((1 :) . tail) (1:1:[])
= 1 : tail (1:1:[])
= 1:1:[]