为什么大多数编程语言对传递给函数的参数使用急切求值?
在大多数编程语言中,传递给函数的参数会在函数使用它们之前进行求值,也就是说,它们会立即求值。
对我来说,似乎只有在函数使用参数时(即惰性地)对参数进行求值才更有意义。这对我来说更有意义,因为它似乎会带来性能优势:为什么要评估根本不需要的东西?
此外,假设您想要实现一个 if
函数,该函数接受一个布尔值,以及一个在布尔值为 true 时返回的对象,以及在布尔值为 false 时返回的另一个对象
object if(bool condition, object valueIfTrue, object valueIfFalse) {
if(condition) return valueIfTrue;
return valueIfFalse;
}
:参数时,即使函数始终只需要其中一个对象,也始终会计算两个对象,这充其量会产生一些不必要的开销,而最坏的情况会导致无限循环。
也就是说,由于大多数编程语言都使用函数参数的急切求值,我认为通常这样做一定是有原因的。我忽略了热切评估的一些重大好处,是否只是因为以这种方式实现语言更容易,这只是传统,还是什么?
In most programming languages, arguments passed to a function are evaluated before the function uses them, that is, they are evaluated eagerly.
To me, it seems like it would make much more sense to evaluate the arguments only once the function uses them, that is, lazily. This makes more sense to me because it seems like it would have a performance benefit: why evaluate things that are never even needed?
Moreover, suppose you wanted to implement an if
function that accepts a boolean, and an object to return if the boolean is true, and another object to return if the boolean is false:
object if(bool condition, object valueIfTrue, object valueIfFalse) {
if(condition) return valueIfTrue;
return valueIfFalse;
}
In a language that eagerly evaluates arguments, both objects are always evaluated even though the function will always only need one of them, which, at best, incurs a slight unecessary overhead, and, at worst, causes an infinite loop.
That said, since most programming languages use eager evaluation of function arguments, I assume there must be a reason why it's usually done that way. Is there some big benefit of eager evaluation here that I'm overlooking, is it just because it was easier to implement languages that way, is it just tradition, or what?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我发现急切评估有几个原因,这两个原因都很重要:
惰性求值可以是一个强大的工具,但它并不是没有代价的。纯函数式语言往往会避免问题#1,因为它们(一般来说)没有副作用,但有时仍然会遇到问题#2。允许延迟求值的语言(LISP 宏是其中的一种形式,尽管与延迟求值不同)可以两全其美,但代价是程序员需要付出更多的努力。
There are a couple reasons I've seen for eager evaluation, both of which are important:
Lazy evaluation can be a powerful tool, but it's not without it's costs. Purely functional languages tend to avoid problem #1 because they don't have side effects (in general), but are still bitten by problem #2 at times. Languages that allow delayed evaluation (LISP macros are a form of this, though not the same as lazy evaluation) can have the best of both worlds, but at the cost of more effort on the programmer's part.
选项 1 - 将所有参数加载到寄存器中,调用函数
选项 2 - 加载第一个参数,评估是否有必要,等待 CPU 管道清除,获取下一个参数,评估是否有必要...然后将所需参数加载到寄存器中,执行带有额外逻辑的函数来标记哪些寄存器正在使用。
当您等待查看正在执行的代码路径时,“if”已经会导致性能停滞(通过分支预测稍微节省)
Option 1 - load all arguements into registers, call function
Option 2 - load first argument, evaluate if it's necessary, wait for CPU pipeline to clear, get next argument, evaluate if it's necessary .... then load needed paramters into registers, execute function with extra logic to mark which registers are in use.
An 'if' is already going to cause a performance holdup anyway while you wait to see which code path you are executing (slightly saved by branch prediction)
为了使惰性求值发挥作用,需要在某处添加额外的代码和数据来跟踪表达式是否已被求值。在某些情况下,这比急切的评估成本更高。确定表达式是否可以从惰性计算中受益可能需要对程序如何工作有非常高水平的了解;编译器和/或解释器当然不会有这种知识。
此外,如果函数或表达式有副作用,则惰性求值策略可能会使程序的行为方式违反直觉且难以调试。这在函数式编程语言中当然不是问题,因为函数式编程语言在设计上就没有副作用。事实上,惰性求值是大多数(如果不是全部)函数式编程语言的默认策略。
话虽如此,没有什么可以阻止您在不同的地方使用这两种策略。如果在重要的程序中使用混合方法,我不会感到惊讶。
In order for lazy evaluation to work there needs to be extra code and data somewhere to keep track of whether an expression has been evaluated. In some cases this would be more costly than eager evaluation. Determining whether an expression can benefit from lazy evaluation may require a very high-level knowledge of how the program works; the compiler and/or interpreter certainly won't have this kind of knowledge.
Also, if the function or expression have side effects, the lazy evaluation strategy might make programs behave in ways that are counterintuitive and hard to debug. This is of course not a problem in functional programming languages where there are no side effects by design. In fact, lazy evaluation is the default strategy for most, if not all, functional programming languages.
That being said, there's nothing that prevents you from using both strategies in different places. I wouldn't be surprised if a hybrid approach is used in non-trivial programs.
除了已经提供的出色答案之外,惰性评估还存在另一个实际问题。如果您有一系列表达式,仅在“使用”最后一个表达式时才延迟计算,那么识别性能瓶颈可能会变得相当困难。
Apart from the excellent answers already provided, there's another practical problem with lazy evaluation. If you have a chain of expressions only lazily evaluated when the last one is "used", it can become quite hard to identify performance bottlenecks.
早在白垩纪时期,就有许多语言做到了这一点。例如,斯诺博尔。 ALGOL 68 具有“按名称调用”功能,可以实现类似的功能。 C(及其许多派生语言)在一种非常具体的情况下执行此操作,将其描述为“短路”布尔表达式。一般来说,它几乎总是比它的支持能力更多的混乱和错误的来源。
Back in the Cretaceous Period, there were a number of languages that did this. SNOBOL, for example. ALGOL 68 had a "call by name" capability, which did something like this. And C (as well as its many derivatives) does it in one very specific situation, which it describes as "short-circuiting" a boolean expression. In general, it is almost always a source of more confusion and bugs than it is of enabling power.