ZipWith在Haskell中不多次评估元素

时间:2016-03-04 19:33:44

标签: haskell

我有一个无限列表,dog(不是实际名称),其元素是由一个有点慢的函数生成的,所以我试图避免不止一次生成相同的元素。问题是我想要以下新列表cat

let cat = zipWith (++) dog $ tail dog

我是否认为通过评估cat的每个元素(来自dogdog)然后连接这两个元素来创建tail dog是正确的?如果是这样,有没有办法可以让Haskell“意识到”dog的元素与tail dog的元素完全相同,只是将一个向左移动,以便我可以“传递值” tail dog的前一个元素对dog的当前元素的影响?也就是说,因为我知道dog的第i个元素等于tail dog的第(i - 1)个元素,所以我希望我的程序能够重新使用(i - 1)tail dog的元素,而不是重新计算它。

我知道像Fibonacci序列的规范创建的列表,let fib = 0:1:zipWith (+) fib $ tail fib只评估元素一次;但这是因为列表是自己定义的,cat不是。

如果这是一个愚蠢的问题,我道歉,但我的大脑最近还没有在所有气瓶上开火。如果知道有问题的具体列表会有用,那么我将非常乐意提供它。感谢。

2 个答案:

答案 0 :(得分:3)

  

我是否相信通过评估cat的每个元素两次来创建dog

不,列表中的每个元素(或更一般地说:每个变量和代数数据类型或记录的每个元素)最多只会被评估一次。

  

我知道像Fibonacci序列的规范创建这样的列表只评估元素一次;但那是因为列表是自己定义的,而cat则不是。

不,那不是原因。这是因为懒惰列表与您可能从其他语言中了解的生成器概念不同。惰性列表是一种实际的数据结构,一旦被(部分)评估,就会存在(部分)存储在内存中。

也就是说,在你使用了列表的前十个元素后,这些元素实际上将存在于内存中,这些元素的后续使用只会从内存中读取它们而不是再次计算它们。

答案 1 :(得分:1)

@ sepp2k是正确的,但与此同时,有时能够直接验证这些东西是有用的....(这里显而易见,但在稍微复杂的情况下,它并非如此全部清除)。

您可以随时使用(不安全)跟踪功能来观察程序,如此....

import Debug.Trace

main :: IO ()
main = do
  let dog = listFrom 0
      cat = zipWith (++) dog $ tail dog

  print $ take 10 cat

listFrom::Int->[String]
listFrom x = trace ("in listFrom: " ++ show x) $ 
    show x:listFrom (x+1)

这将显示每个元素的计算次数(后跟程序的输出)....

in listFrom: 0
in listFrom: 1
in listFrom: 2
in listFrom: 3
in listFrom: 4
in listFrom: 5
in listFrom: 6
in listFrom: 7
in listFrom: 8
in listFrom: 9
in listFrom: 10
["01","12","23","34","45","56","67","78","89","910"]

正如预期的那样,它只创建一个项目....更有趣的是,你可以看到(因为懒惰),如果你不使用创建的列表,列表中没有项目被创建...例如,改变

print $ take 10 cat

putStrLn "Not using cat"

并且没有打印出来

> runProgram
Not using cat

(请记住,trace是不安全的,不应该在最终程序中使用,它只用于调试)