繁体   English   中英

Haskell,无限列表上的文件夹

[英]Haskell, foldr on infinite list

我在阅读LYAH时遇到问题。

这是我对无限列表上的文件夹的想法:

foldr (:) [] [1..] = 1:2:...:**∞:[]**

我认为GHCi在评估∞:[]之前不知道它是列表。

但是GHCI确实知道。

因此,我认为它可以识别文件夹(:) [] [无限列表] = [无限列表本身]。

Prelude> [1..10] == (take 10 $ foldr (:) [] [1..])
True
Prelude> [1..] == (foldr (:) [] [1..])
Interrupted.

但是事实并非如此。

我想知道在评估∞:[]之前,当GHCi识别为[1 ..]时实际会发生什么。

只是在评估之前键入推断?

我想知道GHCi在评估∞:[]之前认识到[1..]时会发生什么。

GHCI 承认,这是[1...]这是只有评价的结果。

foldr实现为:

foldr _ z [] = z
foldr f z (x:xs) = f x (foldr f z xs)

如果你喜欢写东西foldr (:) [] [1..]然后Haskell 没有 evalautes这(直接),它只是要计算出店。

现在假设您要print (take 3 (foldr (:) [] [1..]))该列表,然后Haskell被迫对其进行评估,它将通过计算:

take 3 (foldr (:) [] [1..])
-> take 3 ((:) 1 (foldr (:) [] [2..]))
-> (:) 1 (take 2 (foldr (:) [] [2..]))
-> (:) 1 (take 2 ((:) 2 (foldr (:) [] [3..]))
-> (:) 1 ((:) 2 (take 1 (foldr (:) [] [3..])))
-> (:) 1 ((:) 2 (take 1 ((:) 3 (foldr (:) [] [4..]))))
-> (:) 1 ((:) 2 ((:) 3 (take 0 (foldr (:) [] [4..]))))
-> (:) 1 ((:) 2 ((:) 3 [])

因此它派生[1, 2, 3] ,并且由于Haskell的惰性,它对什么是文件foldr (:) [] [4..]并不感兴趣。 即使该列表最终将停止,也不会对其进行评估。

如果计算类似[1..] = foldr (:) [] [1..] ,则Haskell将检查列表是否相等,列表相等定义为:

[] == [] = True
(x:xs) == (y:ys) = x == y && xs == ys
[] == (_:_) = False
(_:_) == [] = False

所以Haskell是被迫放松右边的列表foldr ,但它会继续这样做,直到发现不相等的物品,或列表中的一个到达终点。 但是由于每次元素都是相等的,并且两个列表都不会结束,所以它将永远不会结束,因为它将像下面这样评估它:

   (==) [1..] (foldr (:) [] [1..])
-> (==) ((:) 1 [2..])  ((:) 1 (foldr (:) [] [2..]))

它看到两者相等,因此递归调用:

-> (==) ((:) 1 [2..])  ((:) 1 (foldr (:) [] [2..]))
-> (==) [2..] foldr (:) [] [2..])
-> (==) ((:) 2 [3..])  ((:) 2 (foldr (:) [] [3..]))
-> (==) [3..] foldr (:) [] [3..])
-> ...

但是正如您所看到的,它永远不会停止评估。 Haskell不知道 foldr (:) [] [1..]等于[1..] ,它的目的是求值,并且由于相等性迫使它求值整个列表,因此它会陷入无限循环。

是的,有可能在编译器中添加某种模式,例如将foldr (:) [] x替换为x ,因此将来Haskell编译器可能会针对这些返回True ,但这不能解决问题。 从根本上说 ,如果Haskell可以针对任何类型的函数(在这里(:)派生此类事物,那么它将解决一个不确定的问题,因此是不可能的。

Ghc(至少在理论上)不知道有限列表和无限列表之间的区别。 通过计算列表的长度可以知道列表是有限的。 如果您尝试找到一个无限列表的长度,那么您的时间将会很糟糕,因为您的程序将永远不会终止。

这个问题实际上是关于惰性评估的。 在像C或python这样的严格语言中,您需要在每一步都知道某物的全部价值。 如果要添加列表中的元素,则在开始之前已经需要知道列表中有什么以及有多少东西。

Haskell中的所有数据具有以下形式:

  1. 基本的众所周知的东西,例如一个int(不一定是整数)
  2. 数据类型构造函数及其参数,例如TrueLeft 7(,) 5 'f' (与(5,'f') )或(:) 3 []

但是在Haskell中,价值有两种“形状”

  • 已知且经过充分评估(例如在C或python中)
  • 尚未评估-函数“ thunk”在您调用时将以更评估的方式返回值

在Haskell中,有一个称为弱头范式的概念,其中:

  • 任何原语都经过充分评估
  • 对于具有构造函数的任何事物,构造函数都是已知的,并且可能尚未评估参数。

让我们看一下文件foldr (:) [] [1..]的评估过程。 首先定义文件foldr

foldr f a [] = a
foldr f a (x:xs) = f x (foldr xs)

现在什么是文件foldr (:) [] [1..]

foldr (:) [] [1..]

看来这只是一个笨拙。 我们对此一无所知。 因此,让我们将其评估为WHNF。 首先,我们需要将参数[1..] (实际上是enumFrom 1 )转换为WHNF,以便可以对其进行模式匹配:

foldr (:) [] (1:[2..])

现在我们可以评估文件夹:

(:) 1 (foldr [] [2..])
1 : (foldr [] [2..])

因此,我们计算了列表的第一个元素,而不必查看其整个无限长度。 同样,我们可以计算出第二个元素,依此类推。

那么,如果我们执行[1..] == [1..]什么? 好,列表的==的定义是(省略了三种情况)

(x:xs) == (y:ys) = x == y && xs == ys

因此,尝试减少到​​WHNF,我们得到:

[1..] == [1..]
(1 == 1) && ([2..] == [2..])
True && ([2..] == [2..])
[2..] == [2..]
... and so on

因此,我们一直坚持下去,永远都不会找到可以用来对结果进行模式匹配(即检查)的构造函数。

需要注意的是,我们可以抵消True && ...因为定义&&不看它的第二个参数:

True && x = x
False && _ = False

如果我们用完整的四向真值表定义&& ,则程序可能比上述速度更快(如果编译器没有做任何聪明的事情)用完内存(相反,您将没有耐心(或宇宙射线命中)您的ram并使程序返回False

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM