Scala 何时使用按名称调用和按值调用?

Scala 何时使用按名称调用和按值调用?,scala,Scala,我理解按名称调用和按值调用的基本概念,并且我还研究了一些示例。然而,我不太清楚什么时候应该使用直呼姓名。与其他呼叫类型相比,按名称呼叫具有显著优势或性能提升的真实场景是什么?在设计方法时,选择调用类型的正确思维方式应该是什么?简单的解释方法是 按值调用函数计算传入表达式的值 因此,在调用函数之前,每次都会访问相同的值 时间但是,按名称调用函数会重新计算传入的 每次访问表达式时,它的值 我一直认为这个术语是不必要的混淆。一个函数可以有多个参数,它们的按名称调用与按值调用状态不同。因此,函数不是按名

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

简单的解释方法是

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


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

有很多地方,名称调用可能会获得性能甚至正确性

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

trait Logger {
  def info(msg: => String)
  def warn(msg: => String)
  def error(msg: => String)
}
logger.info("Time spent on X: " + computeTimeSpent)
if (ref != null && ref.isSomething)
trait Boolean {
  def &&(other: Boolean): Boolean
}
然后像这样使用:

trait Logger {
  def info(msg: => String)
  def warn(msg: => String)
  def error(msg: => String)
}
logger.info("Time spent on X: " + computeTimeSpent)
if (ref != null && ref.isSomething)
trait Boolean {
  def &&(other: Boolean): Boolean
}
如果
info
方法没有做任何事情(例如,因为日志记录级别被配置为高于此级别),则不会调用
computeTimeWasted
,从而节省时间。这种情况在Logger中经常发生,在Logger中,经常会看到字符串操作,相对于正在记录的任务来说,这可能是非常昂贵的

正确性示例:逻辑运算符

您可能见过这样的代码:

trait Logger {
  def info(msg: => String)
  def warn(msg: => String)
  def error(msg: => String)
}
logger.info("Time spent on X: " + computeTimeSpent)
if (ref != null && ref.isSomething)
trait Boolean {
  def &&(other: Boolean): Boolean
}
假设您这样声明了
&&
方法:

trait Logger {
  def info(msg: => String)
  def warn(msg: => String)
  def error(msg: => String)
}
logger.info("Time spent on X: " + computeTimeSpent)
if (ref != null && ref.isSomething)
trait Boolean {
  def &&(other: Boolean): Boolean
}
然后,无论何时
ref
null
,您都会得到一个错误,因为
isSomething
将在传递到
之前对
null
引用进行调用。因此,实际申报为:

trait Boolean {
  def &&(other: => Boolean): Boolean =
    if (this) other else this
}
因此,人们可能真的想知道何时使用按值调用。事实上,在Haskell编程语言中,一切工作方式都与按名称调用的工作方式类似(类似,但不相同)


不使用“按名称调用”有很好的理由:它速度较慢,创建的类更多(意味着加载程序需要更长时间),占用的内存也更多,而且差异很大,许多人对此难以推理。

按名称调用意味着在访问该值时对其进行评估,使用“按值调用”时,首先计算值,然后将其传递给方法

查看差异,考虑此示例(完全无功能编程,仅具有副作用;))。假设您想创建一个函数,用于测量某个操作所需的时间。您可以通过“按姓名呼叫”完成此操作:

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):

但是,如果您仅将
measure
的签名更改为
measure(action:Unit)
以便它使用传递值,则结果将是:

Will now sleep a little
Starting to measure time
Operation took 1760 ns
如您所见,
操作
度量
开始之前进行评估,而且由于在调用方法之前操作已经运行,因此经过的时间接近于0


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

当函数中多次使用“按名称调用”参数时,会多次计算该参数


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

Nice将按名称调用的日志用例放在这里:
.

您是否考虑了一个示例?上面的描述中有一个轻微的更正:在该示例中,即使日志级别设置为高于信息级别,也会调用信息记录器内部调用的函数。只有记录器消息不会被打印。@Sangeeta否,该函数将不会被调用。这就是重点。“这里,按值传递允许实现方法的预期行为。”更确切地说,“这里,按名称传递允许实现方法的预期行为。”不是吗?(或者实际上我没有掌握预期的功能?@monteiro你当然是对的。谢谢修理。