按名称调用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式由纯函数组成,而
- 两个评估都终止。
按值调用具有只评估一次函数参数的优点。
如果在函数体的评估中未使用相应的参数,那么按名称调用具有不评估函数参数的优点。