我理解按名称调用和按值调用的基本概念,我也研究了一些例子。但是,我不清楚何时使用call-by-name。什么是真实世界的场景,其中call-by-name相对于其他呼叫类型具有显着的优势或性能提升?在设计方法时选择呼叫类型的正确思维方法应该是什么?
答案 0 :(得分:16)
有很多地方可以通过名字来提高性能甚至是正确性。
简单的性能示例:日志记录。想象一下这样的界面:
trait Logger {
def info(msg: => String)
def warn(msg: => String)
def error(msg: => String)
}
然后像这样使用:
logger.info("Time spent on X: " + computeTimeSpent)
如果info
方法没有做任何事情(因为,例如,日志记录级别配置为高于此值),则永远不会调用computeTimeSpent
,从而节省时间。对于记录器来说,这种情况发生了很多,人们常常看到字符串操作,相对于记录的任务来说,这可能很昂贵。
正确性示例:逻辑运算符。
您可能已经看过这样的代码:
if (ref != null && ref.isSomething)
假设您声明&&
这样的方法:
trait Boolean {
def &&(other: Boolean): Boolean
}
然后,只要ref
为null
,您就会收到错误,因为isSomething
会在null
引用上调用&&
,然后才会传递给trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
。因此,实际声明是:
{{1}}
所以人们可能真的想知道什么时候使用call-by-value。事实上,在Haskell编程语言中,一切都类似于按名称调用的方式(类似但不相同)。
有充分的理由不使用call-by-name:它更慢,它创建了更多的类(意味着程序需要更长的时间来加载),它消耗更多的内存,并且它有很多不同,许多人有很难推理它
答案 1 :(得分:6)
按名称调用表示在访问时计算值,而使用值调用时,首先计算值,然后传递给方法。
要看到差异,请考虑这个例子(完全非功能性编程只有副作用;))。假设您要创建一个测量某些操作所需时间的函数。您可以使用按名称进行操作:
def measure(action: => Unit) = {
println("Starting to measure time")
val startTime = System.nanoTime
action
val endTime = System.nanoTime
println("Operation took "+(endTime-startTime)+" ns")
}
measure {
println("Will now sleep a little")
Thread.sleep(1000)
}
您将获得结果(YMMV):
Starting to measure time
Will now sleep a little
Operation took 1000167919 ns
但是,如果您只将measure
的签名更改为measure(action: Unit)
,那么它会使用pass-by-value,结果将是:
Will now sleep a little
Starting to measure time
Operation took 1760 ns
正如您所看到的,action
在measure
开始之前进行评估,并且由于在调用方法之前已经执行了操作,因此经过的时间也接近于0.
这里,按名称传递允许实现方法的预期行为。在某些情况下,它不会影响正确性,但会影响性能,例如在日志框架中,如果不使用结果,则可能根本不需要评估复杂表达式。
答案 2 :(得分:4)
可以解释的简单方法是
call-by-value函数计算传入表达式的值 在调用函数之前,每次都访问相同的值 时间。但是,call-by-name函数会重新计算传入的内容 每次访问时都表达式的值。
我一直认为这个术语不必要地混淆。一个函数可以有多个参数,这些参数的名称调用与按值调用状态不同。因此,不是函数是按名称调用或按值调用,而是它的每个参数都可以是按名称传递或按值传递。此外,“名字叫”与名字无关。 => Int是与Int不同的类型;它是“没有参数的函数,它将生成一个Int”而不仅仅是Int。一旦获得了一流的功能,您就不需要发明按名称命名的术语来描述它。
答案 3 :(得分:0)
如果在函数中多次使用call-by-name参数,则会多次评估该参数。
由于传入的参数应该是每个函数编程的纯函数调用,因此被调用函数中的每个求值将始终生成相同的结果。因此,按名称呼叫比传统的按值调用更浪费。
答案 4 :(得分:0)