惰性求值什么时候没用?
延迟执行几乎总是有好处的。但在某些情况下,当出现问题时,您需要“获取”(在 Nhibernate 中)来急切地获取它。
您知道在实际情况下,懒惰的评估会反噬您吗……?
Delay execution is almost always a boon. But then there are cases when it’s a problem and you resort to “fetch” (in Nhibernate) to eager fetch it.
Do you know practical situations when lazy evaluation can bite you back…?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
您无法使用惰性求值对常量空间中的输入数据进行缩减(例如折叠),因为每个缩减步骤的延迟求值都会导致线性空间复杂度。您必须强制评估每个缩减步骤的结果,以保持空间使用量恒定。
例如,在 Haskell 中对文件进行哈希处理。你可能是出于好意,懒洋洋地逐块读取输入文件,将每个块添加到摘要中,但在你背后,Haskell 实际上为添加到摘要中的每个块进行了重击,将整个文件留在内存中这些重击直到实际评估结果摘要。哎哟!
请参阅此处的最后评论:Haskell 惰性 I/O 和关闭文件
You cannot do a reduction (eg. a fold) on input data in constant space with lazy evaluation, as the delayed evaluation of each reduction step results in linear space complexity. You must instead force-evaluate the result of each reduction step to keep the space usage constant.
For example, hashing a file in Haskell. You might be well meaning and read the input file lazily chunk-by-chunk, adding each chunk to the digest, but behind your back Haskell is actually making a thunk for every chunk you add to the digest, leaving the entire file in memory in these thunks until the resulting digest is actually evaluated. Ouch!
See last comment here: Haskell lazy I/O and closing files
在性能至关重要并且必须始终评估值的情况下,延迟评估没有用。在这些情况下,您最好只评估该值并完成它,因为惰性评估的开销将被浪费。
Lazy evaluation is not useful in situations where performance is critical and a value must always be evaluated. In these cases you are better off just evaluating the value and being done with it, because the overhead of lazy evaluation will be wasted.
当评估可能产生副作用时,惰性评估就没有用了。这是唯一的原因,也是为什么只有纯函数式语言才有它。如果表达式可能有副作用,并且必须按一定顺序发生,那么您就不能拥有它。
除此之外,惰性求值只会提高性能,这是它的主要目标。这就是为什么有些语言禁止副作用,为了获得这种妥协的惰性评估,它的另一个好处是控制结构可以是常规函数。
Lazy evaluation is not useful when evaluation can have side-effects. That's the only reason and that's why only purely functional languages have it. If expressions can have side-effects which must occur in a certain order you can't have it.
Apart from that, lazy evaluation only gains performance, that's it's primary goal. And that's why some languages forbid side-effects, to gain lazy evaluation for that compromise, another nice effect of it is that control structures can be regular functions.
懒惰导致奇怪问题的一个例子(今天发生在我身上,在 Haskell 中):
编译时会抛出以下错误:执行:
我认为它会做什么:
打开文件 foo.txt,读取内容,然后再次关闭。然后打开它进行写入,写入内容,然后再次关闭。
它实际上做了什么:
“啊,一些内容,等真正需要的时候我可能会看。”然后打开“foo.txt”进行写入。开始编写内容...好吧,现在我们需要内容。打开 foo.txt 进行阅读 - 嘭!
我知道修复起来很简单,但是如果您不知道在哪里查找,就很难找到。
One example for lazyness causing weird problems (happened to me today, in Haskell):
This throws the following error when compiled & executed:
What I thought it would do:
Open file foo.txt, read content, close it again. Then open it for writing, write the content, and close it again.
What it actually did:
"Ah, some content. I'll probably read it later when we actually need it." Then open "foo.txt" for writing. Start writing content... ok, now we need the content. Open foo.txt for reading - bam!
I know it's trivial to fix, but it's hard to find if you don't know where to look.
延迟加载资源涉及每次加载的请求者和源之间的来回行程。对于 NHibernate,这意味着从应用程序到数据库(通常位于不同的服务器上)。
每次行程通常都会产生相关开销(对于 NHibernate 或任何其他数据库查询来说肯定存在)。
如果您知道您将需要全部或大部分数据,那么您最好一次性提取它,并且只产生一次开销。
一个典型的例子是,当您需要拉回对象列表以填充组合框时(通常这些对象是配置对象)。每次将列表成员添加到组合框时,延迟加载都会返回到数据库。由于您将整个列表放入组合框中,因此您会因延迟获取每个对象而产生大量额外的开销。
Lazy loading resources involves a trip back and forth between the requester and the source for each load. In the case of NHibernate, this means a from the application to the database (which is often on a different server).
There is often overhead associated with each trip (there certainly is for NHibernate or any other DB query).
If you know that you will need all or a substantial portion of the data, you are better off pulling it in one go and only incurring the overhead once.
A classic example is when you need to pull back a list of objects to populate a combo box (often these will be configuration objects). Lazy loading would go back to the database each time you added a list member to the combo box. Since you're putting the entire list into the combo box, you would incur lots of extra overhead to lazy fetch each object.
这也可能是程序的用户体验问题。当应用程序加载期间屏幕上显示横幅时,人们会很乐意等待 5 秒,但他们不喜欢在文本框中输入内容时必须等待 0.25 秒。如果急切地加载所有数据所需的时间不是那么长,您可以考虑在工作流程中人们接受延迟的某个时刻(例如应用程序加载、窗口弹出、按钮按下)执行此操作。
It can also be a problem with the user experience of your program. People will happily wait for 5 seconds when a banner is displayed on the screen during app loading, but they despise to have to wait 0.25 seconds when they're typing in something in a textbox. If the amount of time it takes to load all your data eagerly is not that long, you may consider doing it at some point in the workflow where people accept a delay (such as app loading, window pop up, button presses).
当您不想存储值而只想使用它时,惰性评估没有用。但这取决于惰性求值器的实现。有些系统(如 Haskell)可以判断某个值是否会再次使用。其他一些则不能并且可能导致泄漏。
Lazy evaluation is not useful when you don't want to store value, just use it. But this depends on the implementation of the lazy evaluator. Some systems (like Haskell) can tell if a value will be used again. Some others cannot and can cause leaks.