按名称调用vs按Scala中的值调用,需要说明
据我所知,在斯卡拉,一个函数也可能被称为
- 按价值或
- 按名字
例如,给定以下声明,我们是否知道该函数将如何被调用?
宣言:
def f (x:Int, y:Int) = x; 呼叫
 f (1,2) f (23+55,5) f (12+3, 44*11) 
请问有什么规定?
你所给的例子只是使用按值来调用,所以我会给出一个新的,简单的例子来说明差异。
 首先,我们假设我们有一个副作用函数。 这个函数打印出来然后返回一个Int 。 
 def something() = { println("calling something") 1 // return value } 
 现在我们要定义两个接受Int参数的函数,除了一个采用值调用方式( x: Int )而另一个采用按名称调用方式( x: => Int )。 
 def callByValue(x: Int) = { println("x1=" + x) println("x2=" + x) } def callByName(x: => Int) = { println("x1=" + x) println("x2=" + x) } 
现在当我们用副作用函数调用它们时会发生什么?
 scala> callByValue(something()) calling something x1=1 x2=1 scala> callByName(something()) calling something x1=1 calling something x2=1 
 所以你可以看到,在按值调用版本中,传入的函数调用( something() )的副作用只发生一次。 但是,在名称版本中,副作用发生了两次。 
这是因为在调用函数之前,按值函数计算传入的expression式的值,因此每次都访问相同的值。 但是,每次访问时,按名称函数都会重新计算传入的expression式的值。
以下是Martin Odersky的一个例子:
 def test (x:Int, y: Int)= x*x 
我们要检查评估策略,并确定在这些条件下哪个更快(更less的步骤):
 test (2,3) 
 按值调用:testing(2,3) – > 2 * 2  – > 4 
 按名称呼叫:testing(2,3) – > 2 * 2  – > 4 
 这里的结果是以相同的步数达到的。 
 test (3+4,8) 
 按值调用:testing(7,8) – > 7 * 7  – > 49 
  (3 + 4) (3 + 4)→7 (3 + 4)→7 * 7→49 
 这里按价值来电比较快。 
 test (7,2*4) 
 按值调用:testing(7,8) – > 7 * 7  – > 49 
 按名称:7 * 7  – > 49 
 这里按名称呼叫更快 
 test (3+4, 2*4) 
 按值调用:testing(7,2 * 4) – >testing(7,8) – > 7 * 7  – > 49 
  (3 + 4) (3 + 4)→7 (3 + 4)→7 * 7→49 
 结果是在相同的步骤内达成的。 
在你的例子中,所有的参数在函数调用之前都会被计算,因为你只是通过值来定义它们。 如果你想按名字定义你的参数,你应该传递一个代码块:
 def f(x: => Int, y:Int) = x 
 这样,参数x在函数中被调用之前不会被评估。 
这里的这个小post也很好地解释了这一点 。
我会尝试用一个简单的用例来解释,而不仅仅是提供一个例子
想象一下,你想build立一个“唠叨的应用程序” ,这将使纳格自每次上次唠叨以来每一次。
检查以下实现:
 object main { def main(args: Array[String]) { def onTime(time: Long) { while(time != time) println("Time to Nag!") println("no nags for you!") } def onRealtime(time: => Long) { while(time != time) println("Realtime Nagging executed!") } onTime(System.nanoTime()) onRealtime(System.nanoTime()) } } 
在上面的实现中,匕首只有在通过名字传递的时候才起作用,原因在于,当传值的时候它会被重新使用,因此这个值不会被重新评估,而通过名字的时候这个值会被重新评估时间variables被访问
为了在上面的评论中迭代@ Ben的观点,我认为最好把“call-by-name”看作是语法糖。 parsing器只是将expression式包装在匿名函数中,以便在稍后使用它们时可以调用它们。
实际上,而不是定义
 def callByName(x: => Int) = { println("x1=" + x) println("x2=" + x) } 
并运行:
 scala> callByName(something()) calling something x1=1 calling something x2=1 
你也可以写:
 def callAlsoByName(x: () => Int) = { println("x1=" + x()) println("x2=" + x()) } 
并按如下方式运行,效果相同:
 callAlsoByName(() => {something()}) calling something x1=1 calling something x2=1 
通常,函数的参数是按值参数; 也就是说,参数的值是在传递给函数之前确定的。 但是如果我们需要编写一个函数来接受一个expression式,那么我们不想在函数内部调用它之前,我们不想对它进行求值呢? 在这种情况下,Scala提供了按名称参数。
名称机制将代码块传递给被调用者,每次被调用者访问参数时,代码块将被执行并计算值。
 object Test { def main(args: Array[String]) { delayed(time()); } def time() = { println("Getting time in nano seconds") System.nanoTime } def delayed( t: => Long ) = { println("In delayed method") println("Param: " + t) t } } 
1. C:/> scalac Test.scala 2.斯卡拉testing 3.延迟的方法 4.纳秒时间 5.参数:81303808765843 6.以纳秒为单位的时间
 正如我所假设的那样,上面讨论call-by-value函数只是将值传递给函数。 根据Martin Odersky ( Martin Odersky这是斯卡拉之后的评估策略,在function评估中起着重要的作用。 但是,让名称更简单。 它就像一个传递函数作为参数的方法也被称为Higher-Order-Functions 。 当方法访问传递参数的值时,调用传递函数的实现。 如下: 
根据@dhg示例,首先创build方法如下:
 def something() = { println("calling something") 1 // return value } 
 这个函数包含一个println语句并返回一个整数值。 创build一个call-by-name参数的函数: 
 def callByName(x: => Int) = { println("x1=" + x) println("x2=" + x) } 
 这个函数参数定义了一个匿名函数,它们返回一个整数值。 在这个x包含一个函数的定义,它有0参数,但返回int值,而我们的something函数包含相同的签名。 当我们调用函数时,我们将函数作为parameter passing给callByName 。 但是在call-by-value的情况下,它只将整数值传递给函数。 我们调用如下函数: 
 scala> callByName(something()) calling something x1=1 calling something x2=1 
 在这个我们的方法中调用了两次,因为当我们在callByName方法中访问x的值时,它调用了something方法的定义。 
参数通常是按值传递的,这意味着它们将在被replace之前在函数体中进行评估。
定义函数时,可以强制使用双箭头按名称调用参数。
 // first parameter will be call by value, second call by name, using `=>` def returnOne(x: Int, y: => Int): Int = 1 // to demonstrate the benefits of call by name, create an infinite recursion def loop(x: Int): Int = loop(x) // will return one, since `loop(2)` is passed by name so no evaluated returnOne(2, loop(2)) // will not terminate, since loop(2) will evaluate. returnOne(loop(2), 2) // -> returnOne(loop(2), 2) -> returnOne(loop(2), 2) -> ... 
在互联网上,这个问题已经有很多奇妙的答案。 我将编写一个关于这个主题的几个解释和例子的汇编,以防万一有人觉得有帮助
介绍
按价值划拨(CBV)
通常,函数的参数是按值调用的参数; 也就是说,在函数本身被评估之前,参数从左到右被评估以确定它们的值
 def first(a: Int, b: Int): Int = a first(3 + 4, 5 + 6) // will be reduced to first(7, 5 + 6), then first(7, 11), and then 7 
按名称(CBN)
但是如果我们需要编写一个函数来接受作为参数的expression式,那么在我们的函数中调用这个expression式之前我们不需要进行求值呢? 在这种情况下,Scala提供了按名称参数。 这意味着参数被传递到函数中,并且它的估值是在replace之后进行的
 def first1(a: Int, b: => Int): Int = a first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7 
逐个名称机制将代码块传递给调用,并且每次调用访问参数时,都会执行代码块并计算值。 在下面的例子中,延迟打印显示该方法已被input的消息。 接下来,延迟打印一个消息的值。 最后,延迟回报't':
  object Demo { def main(args: Array[String]) { delayed(time()); } def time() = { println("Getting time in nano seconds") System.nanoTime } def delayed( t: => Long ) = { println("In delayed method") println("Param: " + t) } } 
在延迟的方法
获得纳秒的时间
参数:2027245119786400
每种情况的优点和缺点
CBN: +更经常终止*检查下面的终止* *如果在函数体的评估中未使用相应的参数,则具有函数参数不被评估的优点 – 更慢,它创build更多的类(意味着程序需要加载时间更长),并消耗更多的内存。
CBV: +它通常比CBN指数级更有效率,因为它避免了这种重复重新计算参数的expression式。 它只评估每个函数的参数一次+它具有命令式的效果和副作用,因为当评估expression式的时候,你倾向于知道得更好。 – 在参数评估过程中可能会导致一个循环*上述终止以下的检查*
如果终止不能保证呢?
如果expression式e的CBV评估结束,那么e的CBN评估也结束 – 另一个方向是不正确的
非终止的例子
 def first(x:Int, y:Int)=x 
首先考虑expression式(1,循环)
CBN:first(1,loop)→1 CBV:first(1,loop)→减less该expression式的参数。 由于它是一个循环,所以它无限地减less了参数。 它不会终止
差异在每个案件的行为
我们来定义一个方法testing
 Def test(x:Int, y:Int) = x * x //for call-by-value Def test(x: => Int, y: => Int) = x * x //for call-by-name 
案例1testing(2,3)
 test(2,3) → 2*2 → 4 
由于我们从已经评估过的参数开始,所以对于按值调用和按名称调用的步骤是相同的
Case2testing(3 + 4,8)
 call-by-value: test(3+4,8) → test(7,8) → 7 * 7 → 49 call-by-name: (3+4)*(3+4) → 7 * (3+4) → 7 * 7 → 49 
在这种情况下,按值执行的步骤较less
Case3testing(7,2 * 4)
 call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49 call-by-name: (7)*(7) → 49 
我们避免了第二个参数的不必要的计算
Case4testing(3 + 4,2 * 4)
 call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49 call-by-name: (3+4)*(3+4) → 7*(3+4) → 7*7 → 49 
不同的方法
首先,我们假设我们有一个副作用函数。 这个函数打印出来然后返回一个Int。
 def something() = { println("calling something") 1 // return value } 
现在我们要定义两个接受Int参数的函数,除了一个采用值调用方式(x:Int)而另一个采用按名称调用方式(x: => Int)。
 def callByValue(x: Int) = { println("x1=" + x) println("x2=" + x) } def callByName(x: => Int) = { println("x1=" + x) println("x2=" + x) } 
现在当我们用副作用函数调用它们时会发生什么?
 scala> callByValue(something()) calling something x1=1 x2=1 scala> callByName(something()) calling something x1=1 calling something x2=1 
所以你可以看到,在按值调用版本中,传入的函数调用(something())的副作用只发生一次。 但是,在名称版本中,副作用发生了两次。
这是因为在调用函数之前,按值函数计算传入的expression式的值,因此每次都访问相同的值。 但是,每次访问时,按名称函数都会重新计算传入的expression式的值。
示例哪里更好地使用呼叫名字
从: https : //stackoverflow.com/a/19036068/1773841
简单的性能示例:日志。
让我们设想一个像这样的界面:
 trait Logger { def info(msg: => String) def warn(msg: => String) def error(msg: => String) } 
然后像这样使用:
 logger.info("Time spent on X: " + computeTimeSpent) 
如果info方法没有做任何事情(因为日志级别被configuration为高于这个值),那么computeTimeSpent将永远不会被调用,从而节省时间。 logging器会发生这种情况,在这种情况下,人们经常看到string操作,相对于正在logging的任务来说,这可能是昂贵的
正确的例子:逻辑运算符。
你可能看过这样的代码:
 if (ref != null && ref.isSomething) 
想象一下,你会声明像这样的&&方法:
 trait Boolean { def &&(other: Boolean): Boolean } 
那么,每当ref为null时,你将会得到一个错误,因为isSomething在被传递给&&之前会在nullreference上被调用。 出于这个原因,实际的声明是:
 trait Boolean { def &&(other: => Boolean): Boolean = if (this) this else other } 
按价值调用是一般用例,这里解释了许多答案。
按名称传递代码块给调用者,每次调用者访问参数时,代码块被执行并且计算值。
我将尝试使用下面的用例更简单地演示名称调用
例1:
按名称调用的简单示例/用例在函数下面,以函数作为参数并给出时间。
  /** * Executes some code block and prints to stdout the time taken to execute the block for interactive testing and debugging. * / def time[T](f: => T): T = { val start = System.nanoTime() val ret = f val end = System.nanoTime() println(s"Time taken: ${(end - start) / 1000 / 1000} ms") ret } 
例2:
  apache spark(使用scala)通过名称调用方式使用日志logging请参阅Logging下面的方法中是否logginglog.isInfoEnabled 特性 。 
 protected def logInfo(msg: => String) { if (log.isInfoEnabled) log.info(msg) } 
  CallByName在使用时被调用,并且在遇到语句时调用callByValue 。 
例如:-
 我有一个无限循环,即如果你执行这个函数,我们将永远不会得到scala提示符。 
 scala> def loop(x:Int) :Int = loop(x-1) loop: (x: Int)Int 
 一个callByName函数将上面的loop方法作为一个参数,并且它不会在其内部使用。 
 scala> def callByName(x:Int,y: => Int)=x callByName: (x: Int, y: => Int)Int 
 在执行callByName方法时,我们没有发现任何问题(我们得到了scala提示符),因为我们没有在callByName函数中使用循环函数的callByName 。 
 scala> callByName(1,loop(10)) res1: Int = 1 scala> 
 一个callByValue函数将上面的loop方法作为一个参数作为一个参数,因为函数内部或者expression式在执行外部函数之前被评估,在那里通过loop函数recursion的执行,我们从来没有得到scala提示。 
 scala> def callByValue(x:Int,y:Int) = x callByValue: (x: Int, y: Int)Int scala> callByValue(1,loop(1)) 
我不认为这里的所有答案都有正确的理由:
在按值调用时,参数只计算一次:
 def f(x : Int, y :Int) = x // following the substitution model f(12 + 3, 4 * 11) f(15, 4194304) 15 
 你可以在上面看到,所有的参数都被评估,不pipe需求是不是,通常call-by-value可以很快但并不总是这样。 
 如果评估策略是call-by-name那么分解就是: 
 f(12 + 3, 4 * 11) 12 + 3 15 
 正如你上面看到的,我们从来不需要评估4 * 11 ,因此保存了一些有时候可能有益的计算。 
看到这个:
  object NameVsVal extends App { def mul(x: Int, y: => Int) : Int = { println("mul") x * y } def add(x: Int, y: Int): Int = { println("add") x + y } println(mul(3, add(2, 1))) } 
  y:=> Int是按名称调用的。 按名称传递的是add(2,1)。 这将被懒惰地评估。 所以在控制台上的输出将是“多”,其次是“添加”,虽然添加似乎被称为第一。 按名称调用就像传递一个函数指针一样。 
 现在从y:=> Int改为y:Int。 控制台将显示“添加”,然后显示“mul”! 通常的评估方式。 
根据马丁Odersky :
只要满足以下条件,两种策略都会降低到最终值:
- 简化的expression式由纯函数组成,而
- 两个评估都终止。
按值调用具有只评估一次函数参数的优点。
如果在函数体的评估中未使用相应的参数,那么按名称调用具有不评估函数参数的优点。
 