【问题标题】:When is lazy evaluation not useful?什么时候惰性评估没有用?
【发布时间】:2009-08-30 17:00:14
【问题描述】:

延迟执行几乎总是一个好处。但是在某些情况下,它是一个问题,你诉诸“获取”(在 Nhibernate 中)来急切地获取它。

你知道惰性求值会反噬你的实际情况吗?

【问题讨论】:

    标签: orm design-patterns lazy-evaluation eager


    【解决方案1】:

    您不能使用惰性求值对恒定空间中的输入数据进行归约(例如折叠),因为每个归约步骤的延迟求值会导致线性空间复杂度。您必须改为强制评估每个缩减步骤的结果,以保持空间使用量不变。

    例如,在 Haskell 中对文件进行哈希处理。您可能是善意的,并且懒洋洋地逐块读取输入文件,将每个块添加到摘要中,但在您背后,Haskell 实际上是为您添加到摘要中的每个块创建一个 thunk,将整个文件留在内存中这些 thunk 直到实际评估结果摘要。哎哟!

    在此处查看最后一条评论:Haskell lazy I/O and closing files

    【讨论】:

      【解决方案2】:

      惰性评估在性能至关重要且必须始终评估值的情况下没有用。在这些情况下,您最好只评估值并完成它,因为惰性评估的开销将被浪费。

      【讨论】:

      • 不正确。如果您必须评估答案,那么惰性评估的额外开销会产生无益的成本。
      • vy32 是正确的。例如,如果您有一个显示在移动设备列表中的对象列表,则视图将在列表项出现在屏幕上时创建。如果列表中显示的某些值必须延迟加载,则列表会卡顿和滞后,因为新的列表项无法足够快地填充。
      • 没有错。它只是不是普遍有用的。例如,展开循环是提高性能的一种方式,但这并不意味着展开循环总能提高性能。
      • @Ken,你是对的 --- 我有点苛刻。 Lajla 的评论并没有错,但它离题了。也就是说,惰性求值是一种获得性能的方法,但并非在所有情况下——实际上,惰性求值在很多情况下都无济于事。
      • @vy32 - 他不是这么说的。他举了一个有时会带来性能提升的例子。
      【解决方案3】:

      当评估可能有副作用时,惰性评估是没有用的。这是唯一的原因,这就是为什么只有纯函数式语言才有它。如果表达式可以具有必须以特定顺序发生的副作用,那么您就不能拥有它。

      除此之外,惰性评估只会提高性能,这是它的主要目标。这就是为什么有些语言禁止副作用,为了获得对这种妥协的惰性评估,它的另一个好处是控制结构可以是常规函数。

      【讨论】:

        【解决方案4】:

        懒惰导致奇怪问题的一个例子(今天发生在我身上,在 Haskell 中):

        import System.IO
        
        main = do
            content <- readFile "foo.txt"
            writeFile "foo.txt" content 
        

        编译和执行时会抛出以下错误:

        foo.txt: openFile: resource busy (file is locked)
        

        我认为它会做什么: 打开文件 foo.txt,读取内容,再次关闭。然后打开写,写内容,再关闭。

        它实际上做了什么: “啊,有些内容,以后有需要的时候我会看的。”然后打开“foo.txt”进行写作。开始写内容...好的,现在我们需要内容。打开 foo.txt 阅读 - bam!

        我知道修复起来很简单,但如果你不知道去哪里找,就很难找到。

        【讨论】:

        • 这是一个专门由惰性 I/O 引起的问题,而不是一般的惰性求值。惰性 I/O 实际上是非常危险的,并且违背了函数式编程的精神,因为它会给应该是纯函数的函数带来副作用(即,评估字符串会导致从磁盘读取数据 - 副作用!),导致就像这个问题,还有这个问题:stackoverflow.com/questions/2981582/… 但是你当然可以在没有惰性 I/O 的情况下进行惰性求值,事实上,这似乎是 Haskell 当前的方向。
        【解决方案5】:

        延迟加载资源涉及每次加载的请求者和源之间的往返行程。对于 NHibernate,这意味着从应用程序到数据库(通常在不同的服务器上)。

        每次旅行通常都会产生开销(NHibernate 或任何其他数据库查询肯定会产生开销)。

        如果您知道您将需要全部或大部分数据,您最好一次性提取它并且只产生一次开销。

        一个典型的例子是当您需要拉回一个对象列表来填充组合框时(通常这些将是配置对象)。每次将列表成员添加到组合框中时,延迟加载都会返回到数据库。由于您将整个列表放入组合框中,因此延迟获取每个对象会产生大量额外开销。

        【讨论】:

          【解决方案6】:

          您的程序的用户体验也可能存在问题。人们会很乐意在应用加载过程中在屏幕上显示横幅时等待 5 秒,但他们讨厌在文本框中输入内容时必须等待 0.25 秒。如果急切地加载所有数据所需的时间不是那么长,您可以考虑在工作流中人们接受延迟的某个时间点(例如应用程序加载、窗口弹出、按钮按下)执行此操作。

          【讨论】:

            【解决方案7】:

            当您不想存储价值时,惰性评估没有用,只需使用它即可。但这取决于惰性求值器的实现。一些系统(比如 Haskell)可以判断一个值是否会被再次使用。其他一些不能也可能导致泄漏。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-01-10
              • 2014-02-08
              • 2013-02-01
              • 2021-05-15
              • 2011-10-21
              • 2013-03-11
              • 2017-01-18
              相关资源
              最近更新 更多