【问题标题】:ZipWith without evaluating elements more than once in HaskellZipWith 无需在 Haskell 中多次评估元素
【发布时间】:2016-03-04 19:33:44
【问题描述】:

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

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

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

我知道像斐波那契数列的规范创建这样的列表,let fib = 0:1:zipWith (+) fib $ tail fib 只评估元素一次;但这是因为列表是在自身上定义的,而 cat 不是。

如果这是一个愚蠢的问题,我深表歉意,但我的大脑最近并没有全速运转。如果知道有问题的具体列表会有用,那么我将非常乐意提供它。谢谢。

【问题讨论】:

    标签: haskell


    【解决方案1】:

    我认为cat 是通过两次评估dog 的每个元素创建的,这是否正确

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

    我知道像斐波那契数列的规范创建这样的列表 [...] 只评估元素一次;但这是因为列表是在自身上定义的,而 cat 不是。

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

    也就是说,在您使用了列表的前十个元素之后,这些元素将实际存在于内存中,并且这些元素的任何后续使用都将简单地从内存中读取它们,而不是再次计算它们。

    【讨论】:

    • 太棒了。显然,我在惰性评估方面有很多要学的。我读了一些关于“thunks”的文章,但我显然需要读更多。感谢您提供翔实的回答。
    • @basketballfan22 一件很棒的事情是它适用于大多数数据结构,包括自定义数据结构。一件蹩脚的事情是它实际上不适用于函数。
    【解决方案2】:

    @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 是不安全的,永远不应在最终程序中使用,它仅用于调试)

    【讨论】:

    • 谢谢!我一定会利用这一点。我曾多次尝试通过“Haskell 的眼睛”来解析一些程序。这将更快更准确。
    猜你喜欢
    • 2017-07-31
    • 1970-01-01
    • 2012-02-16
    • 1970-01-01
    • 2013-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多