何时使用call-by-name和call-by-value?

nau*_*ine 8 scala

我理解按名称和按值调用的基本概念,我也研究了一些例子.但是,我不清楚何时使用call-by-name.什么是真实世界的场景,其中call-by-name相对于其他呼叫类型具有显着的优势或性能提升?在设计方法时选择呼叫类型的正确思维方法应该是什么?

Dan*_*ral 17

有很多地方可以通过名字来获得性能甚至是正确性.

简单的性能示例:日志记录 想象一下这样的界面:

trait Logger {
  def info(msg: => String)
  def warn(msg: => String)
  def error(msg: => String)
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用:

logger.info("Time spent on X: " + computeTimeSpent)
Run Code Online (Sandbox Code Playgroud)

如果该info方法没有做任何事情(因为,例如,日志级别配置为高于此值),则computeTimeSpent永远不会被调用,从而节省时间.对于记录器来说,这种情况发生了很多,其中人们常常看到字符串操作,相对于记录的任务而言,这可能很昂贵.

正确性示例:逻辑运算符.

您可能已经看过这样的代码:

if (ref != null && ref.isSomething)
Run Code Online (Sandbox Code Playgroud)

假设您声明了这样的&&方法:

trait Boolean {
  def &&(other: Boolean): Boolean
}
Run Code Online (Sandbox Code Playgroud)

那么,只要refnull,你会得到一个错误,因为isSomething将在一个被称为null引用传递给前&&.因此,实际的声明是:

trait Boolean {
  def &&(other: => Boolean): Boolean =
    if (this) this else other
}
Run Code Online (Sandbox Code Playgroud)

所以人们可能真的想知道什么时候使用call-by-value.实际上,在Haskell编程语言中,一切都类似于按名称调用的方式(类似但不相同).

有充分的理由不使用call-by-name:它更慢,它创建了更多的类(意味着程序需要更长的时间加载),它消耗更多的内存,并且它有很多不同,很多人都有很难推理它.


Mic*_*ski 6

按名称调用表示在访问时计算值,而使用值调用时,首先计算值,然后将值传递给方法.

要看到差异,请考虑这个示例(完全非功能性编程,只有副作用;)).假设您要创建一个测量某些操作所需时间的函数.您可以使用call-by-name来完成:

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)
}
Run Code Online (Sandbox Code Playgroud)

你会得到结果(YMMV):

Starting to measure time
Will now sleep a little
Operation took 1000167919 ns
Run Code Online (Sandbox Code Playgroud)

但是,如果你只改变的签名measuremeasure(action: Unit)所以它使用传递的价值,其结果将是:

Will now sleep a little
Starting to measure time
Operation took 1760 ns
Run Code Online (Sandbox Code Playgroud)

如您所见,在开始action之前评估,measure并且由于在调用方法之前已经运行的操作,经过的时间也接近于0.

这里,按名称传递允许实现方法的预期行为.在某些情况下,它不会影响正确性,但会影响性能,例如在日志框架中,如果不使用结果,则根本不需要对复杂表达式进行求值.


Xen*_*lin 5

可以解释的简单方法是

按值调用函数在调用函数之前计算传入表达式的值,因此每次访问相同的值。但是,每次访问时,按名称调用函数都会重新计算传入表达式的值。

我一直认为这个术语是不必要的混乱。一个函数可以有多个参数,它们的按名称调用与按值调用状态不同。所以不是函数是按名称调用或按值调用,而是它的每个参数可能是按名称传递或按值传递。此外,“按名称呼叫”与名称无关。=> Int 是与 Int 不同的类型;它是“将生成 Int 的无参数函数” vs 只是 Int。一旦您获得了一流的功能,您就无需发明按名称调用的术语来描述这一点。