按名称调用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 OderskyMartin 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式由纯函数组成,而
  • 两个评估都终止。

按值调用具有只评估一次函数参数的优点。

如果在函数体的评估中未使用相应的参数,那么按名称调用具有不评估函数参数的优点。

在这里输入图像描述