8

我了解按名称调用和按值调用的基本概念,并且我还研究了一些示例。但是,我不太清楚何时使用按名称调用。与其他呼叫类型相比,按名称呼叫将具有显着优势或性能提升的真实场景是什么?在设计方法时选择调用类型的正确思路应该是什么?

4

5 回答 5

19

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

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

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) other else this
}

所以人们实际上可能想知道何时使用按值调用。事实上,在 Haskell 编程语言中,一切的工作方式都类似于按名称调用的工作方式(类似,但不一样)。

不使用 call-by-name 有很好的理由:它更慢,它创建更多的类(意味着程序需要更长的加载时间),它消耗更多的内存,而且它的不同之处足以让许多人难以理解它。

于 2013-09-26T18:37:06.300 回答
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

但是,如果您仅更改 to 的签名measuremeasure(action: Unit)使其使用按值传递,则结果将是:

Will now sleep a little
Starting to measure time
Operation took 1760 ns

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

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

于 2013-09-26T19:19:02.723 回答
5

可能解释的简单方法是

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

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

于 2013-09-26T18:10:26.107 回答
0

当在函数中多次使用按名称调用参数时,该参数会被多次计算。

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

于 2017-01-29T22:57:29.507 回答
0

对于按名称调用的日志记录用例来说很好: https ://www.tutorialspoint.com/scala/functions_call_by_name.htm#:~:text=For%20this%20circumstance%2C%20Scala%20offers,and%20the% 20value%20is%20calculated

于 2020-11-20T10:55:06.747 回答