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你当然是对的。谢谢修理。