这个Haskell示例是否有效地展示了懒惰?

时间:2012-12-10 20:31:54

标签: haskell lazy-evaluation

我是Haskell的新手,我正在为我的Programming Languages课写一篇论文。我想用一些示例代码演示Haskell的懒惰,但我不确定我所看到的是否实际上是懒惰。

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

在ghci:

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

输出:

[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]

感谢您的时间和帮助!

4 个答案:

答案 0 :(得分:7)

您在此使用trace并不是特别有见地,或者实际上根本没有。您所做的就是在评估中的四个不同点打印出相同的列表,这不会告诉您有关程序实际状态的任何信息。这里实际发生的是,在计算甚至开始之前(当结果列表被请求为弱头正常形式时),trace在每个加倍步骤中被强制执行。这与完全严格评估的语言几乎相同。

要看到一些懒惰,你可以做类似

的事情
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]

您可以看到只有五个数字加倍,因为只请求了五个结果;即使给出doubleLsTracing的列表有10个条目。

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

答案 1 :(得分:5)

无限流总是一个很好的例子。如果没有特殊结构,你无法用其他语言获得它们 - 但在Haskell中它们非常自然。

一个例子是斐波那契流:

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

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

另一个是使用试验分割方法获得素数流:

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_problem#Haskell

一个更复杂的例子,显示了如何实现min是在这里找到的:

Lazy Evaluation and Time Complexity

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

minimum = head . sort

你可以通过人为的例子展示Haskell的懒惰。但我认为,更好地展示懒惰如何帮助您为常见问题开发解决方案,这些问题比其他语言具有更高的模块性。

答案 2 :(得分:2)

简短的回答是,“不”。 leftaroundabout在他的回答中解释得非常好。

我的建议是:

  1. 阅读并理解lazy evaluation
  2. 的定义
  3. 编写一个函数,其中一个参数可以分歧,不能以您喜欢的严格(非懒惰)语言(C,python,Java)工作的示例。例如, sumIfFirstArgIsNonZero (x,y),如果x!= 0则返回x + y,否则返回0。
  4. 对于奖励积分,定义您自己的函数 ifThenElse ,它不使用Haskell的内置if-then-else语法,并解释为什么在惰性语言中编写新的控制流结构很容易。
  5. 这应该比试图围绕无限数据流或结合技巧更容易。

答案 3 :(得分:2)

懒惰的主要原因是不计算不需要的值 - 因此为了证明这一点,您必须显示正在评估的事物而不是。您的示例不是最好的证明懒惰,因为最终计算所有值。

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

someValueThatNeverTerminates = undefined -- for example, a divide-by-zero error

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

这马上打招呼 - 如果它不是懒惰的话,整个事情就会中途破裂。