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

时间:2013-09-26 18:04:20

标签: scala

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

5 个答案:

答案 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
}

然后,只要refnull,您就会收到错误,因为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

正如您所看到的,actionmeasure开始之前进行评估,并且由于在调用方法之前已经执行了操作,因此经过的时间也接近于0.

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

答案 2 :(得分:4)

可以解释的简单方法是

  

call-by-value函数计算传入表达式的值   在调用函数之前,每次都访问相同的值   时间。但是,call-by-name函数会重新计算传入的内容   每次访问时都表达式的值。

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

答案 3 :(得分:0)

如果在函数中多次使用call-by-name参数,则会多次评估该参数。

由于传入的参数应该是每个函数编程的纯函数调用,因此被调用函数中的每个求值将始终生成相同的结果。因此,按名称呼叫比传统的按值调用更浪费。

答案 4 :(得分:0)