关于懒惰评估:这个Haskell示例是否有效地展示了懒惰?

Does this Haskell example effectively demonstrate laziness?

我是哈斯克尔的新手,我正在为我的编程语言课写一篇论文。我想用一些示例代码来演示haskell的懒惰,但是我不确定我看到的是否实际上是懒惰。

1
doubleMe xs = [x*2 | x <- xs]

在GHCI中:

1
2
3
let xs = [1..10]
import Debug.Trace
trace (show lst) doubleMe (trace (show lst) doubleMe (trace (show lst) doubleMe(lst)))

输出:

1
2
3
4
[1,2,3,4,5,6,7,8,9,10]
[1,2,3,4,5,6,7,8,9,10]
[1,2,3,4,5,6,7,8,9,10]
[8,16,24,32,40,48,56,64,72,80]

谢谢你的时间和帮助!


你在这里使用trace并不是特别有洞察力,或者实际上根本没有。你所要做的就是在评估的四个不同点上打印出同一个列表,这并不能告诉你程序的实际状态。这里实际发生的是,在计算甚至开始之前(当结果列表被请求为弱头正常形式时),trace在每个加倍步骤中都被强制执行。这与使用完全严格评估的语言所得到的结果基本相同。

你可以做些像

1
2
3
4
5
6
7
8
Prelude Debug.Trace> let doubleLsTracing xs = [trace("{Now doubling"++show x++"}")$ x*2 | x<-xs]
Prelude Debug.Trace> take 5 $ doubleLsTracing [1 .. 10]
{Now doubling 1}
{Now doubling 2}
{Now doubling 3}
{Now doubling 4}
{Now doubling 5}
[2,4,6,8,10]

在这里,您可以看到只有5个数字是双倍的,因为只请求了5个结果;尽管给出了doubleLsTracing的列表中有10个条目。

注意,trace通常不是监视"执行流"的好工具,它只是一个允许"查看"局部变量以查看某些函数中发生了什么的黑客。


无限的溪流总是一个很好的例子。如果没有特殊的结构,就不能用其他语言获得它们——但是在haskell中,它们是非常自然的。

一个例子是斐波那契流:

1
2
3
fib = 0 : 1 : zipWith (+) fib (tail fib)

take 10 fib => [0,1,1,2,3,5,8,13,21,34]

另一种方法是使用试算除法获得素数流:

1
2
3
4
primes = sieve [2..]
    where sieve (x:xs) = x : filter (not . (== 0) . (`mod` x)) (sieve xs)

take 10 primes => [2,3,5,7,11,13,17,19,23,29]

此外,在Haskell中实现回溯非常简单,使您能够根据需要轻松获得解决方案列表:

http://rosettacode.org/wiki/n-queens haskell问题

下面是一个更复杂的示例,展示了如何实现min:

懒惰的评估和时间复杂性

它基本上展示了如何使用haskell的laziness来获得一个非常优雅的minimum函数定义(在元素列表中找到最小值):

1
minimum = head . sort

你可以用人为的例子来证明哈斯克尔的懒惰。但是,我认为展示懒惰如何帮助您开发解决常见问题的方案要比其他语言具有更大的模块性要好得多。


懒惰的主要点是不需要计算的值——所以为了证明这一点,您必须显示出没有被评估的东西。您的示例并不是演示懒惰的最佳方法,因为最终会计算所有值。

以下是一个小例子,作为起点:

1
2
3
4
someValueThatNeverTerminates = undefined -- for example, a divide-by-zero error

main = do (greeting, _) = ("Hello terminating world!", someValueThatNeverTerminates)
          putStrLn greeting

这句话立刻打招呼——如果不是懒惰的话,整个事情就会半途而废。


简短的回答是"不"。Leftaroundabout在他的回答中很好地解释了这一点。

我的建议是:

  • 阅读并理解懒惰评估的定义。
  • 写一个函数,其中一个参数可以发散,一个不能在你最喜欢的严格(非懒惰)语言(C,Python,Java)中工作的例子。例如,Sumiffirstargisnonzero(x,y),如果x,则返回x+y!=0,否则为0。
  • 对于奖励点,如果没有使用haskell的内置if-then-else语法,请定义自己的函数if-then-else,并解释为什么用惰性语言编写新的控制流结构很容易。
  • 这应该比尝试围绕无限的数据流或打结技巧更容易。