根据我的理解,在Scala中,函数也可以被调用
传递或 的名字
例如,给定以下声明,我们是否知道函数将如何被调用?
声明:
def f (x:Int, y:Int) = x;
Call
f (1,2)
f (23+55,5)
f (12+3, 44*11)
请问规则是什么?
根据我的理解,在Scala中,函数也可以被调用
传递或 的名字
例如,给定以下声明,我们是否知道函数将如何被调用?
声明:
def f (x:Int, y:Int) = x;
Call
f (1,2)
f (23+55,5)
f (12+3, 44*11)
请问规则是什么?
当前回答
在按值调用中,表达式的值是在函数调用时预先计算的,并且该特定值作为参数传递给相应的函数。相同的值将在整个函数中使用。
而在名称调用中,表达式本身作为参数传递给函数,并且仅在调用特定参数时在函数内部计算。
Scala中按名称调用和按值调用之间的区别可以通过下面的例子更好地理解:
代码片段
object CallbyExample extends App {
// function definition of call by value
def CallbyValue(x: Long): Unit = {
println("The current system time via CBV: " + x);
println("The current system time via CBV " + x);
}
// function definition of call by name
def CallbyName(x: => Long): Unit = {
println("The current system time via CBN: " + x);
println("The current system time via CBN: " + x);
}
// function call
CallbyValue(System.nanoTime());
println("\n")
CallbyName(System.nanoTime());
}
输出
The current system time via CBV: 1153969332591521
The current system time via CBV 1153969332591521
The current system time via CBN: 1153969336749571
The current system time via CBN: 1153969336856589
在上面的代码片段中,对于函数调用CallbyValue(system . nanotime()),系统纳米时间是预先计算的,并且预先计算的值已将参数传递给函数调用。
但是在CallbyName(System.nanoTime())函数调用中,表达式“System.nanoTime())”本身作为参数传递给函数调用,当在函数内部使用该参数时,将计算该表达式的值。
注意CallbyName函数的函数定义,其中有一个=>符号分隔参数x及其数据类型。这里的特定符号表明该函数是按名称调用类型的。
换句话说,按值调用的函数实参在进入函数之前求值一次,而按名称调用的函数实参仅在需要时才在函数内部求值。
希望这能有所帮助!
其他回答
我将尝试通过一个简单的用例来解释,而不仅仅是提供一个示例
想象一下,你想要创建一个“唠叨应用程序”,每当你被唠叨时,它就会唠叨你。
检查以下实现:
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())
}
}
在上述实现中,nagger只在通过名称传递时才会工作 原因是,当通过值传递时,它将被重用,因此值将不会被重新计算,而当通过名称传递时,值将在每次访问变量时重新计算
下面是Martin Odersky的一个例子:
def test (x:Int, y: Int)= x*x
我们希望检查评估策略,并确定在这些条件下哪个更快(步骤更少):
test (2,3)
按值调用:test(2,3) -> 2*2 -> 4 按名称调用:test(2,3) -> 2*2 -> 4 这里的结果是用相同数量的步骤达到的。
test (3+4,8)
按值调用:test (7,8) -> 7*7 -> 49 按名字呼叫:(3+4)(3+4)-> 7(3+4)-> 7*7 ->49 这里按值调用更快。
test (7,2*4)
按值调用:test(7,8) -> 7*7 -> 49 按名字呼叫:7 * 7 -> 49 在这里叫名字比较快
test (3+4, 2*4)
按值调用:test(7,2*4) -> test(7,8) -> 7*7 -> 49 按名字呼叫:(3+4)(3+4)-> 7(3+4)-> 7*7 -> 49 结果是在相同的步骤中达到的。
在互联网上已经有很多关于这个问题的精彩答案。我将把我收集到的关于这个主题的一些解释和例子汇编在一起,以防有人会觉得有用
介绍
cb v值()都未
通常,函数的形参是按值调用形参;也就是说,在计算函数本身之前,从左到右计算参数以确定它们的值
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
call-by-name (CBN)
但是,如果我们需要编写一个函数,该函数接受一个表达式作为参数,直到在函数中调用它时才计算该表达式,该怎么办?对于这种情况,Scala提供了名称调用参数。这意味着参数被原原本本地传递到函数中,并且它的赋值是在替换之后进行的
def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7
名称调用机制将一个代码块传递给调用,每次调用访问参数时,都会执行该代码块并计算该值。在下面的示例中,delayed会打印一条消息,表明方法已被输入。接下来,delayed打印带有其值的消息。最后,延迟返回' 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: +更频繁地终止*在终止下面检查* +的优点是,如果函数体的计算中没有使用相应的形参,则不对函数实参进行计算 -它更慢,它创建了更多的类(意味着程序需要更长的时间来加载),它消耗更多的内存。
CBV: +它通常比CBN更有效,因为它避免了名称调用所需要的参数表达式的重复重新计算。它只计算每个函数参数一次 +它在命令式效果和副作用方面表现得更好,因为您往往更清楚何时计算表达式。 -它可能会导致一个循环在参数评估*检查下面的终止*
如果不能保证终止怎么办?
-如果表达式e的CBV求值终止,则表达式e的CBN求值也终止 -另一个方向是不对的
Non-termination例子
def first(x:Int, y:Int)=x
首先考虑表达式(1,循环)
CBN:第一次(1,循环)→1 CBV: first(1,loop)→该表达式的约简参数。因为一个是循环,它可以无限地减少参数。它不会终止
每种情况下行为的差异
让我们定义一个方法测试
Def test(x:Int, y:Int) = x * x //for call-by-value
Def test(x: => Int, y: => Int) = x * x //for call-by-name
Case1测试(2、3)
test(2,3) → 2*2 → 4
由于我们从已经求值的参数开始,因此按值调用和按名称调用的步数是相同的
例2测试(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
在这种情况下,按值调用执行较少的步骤
Case3检验(7,2 *4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49
我们避免了对第二个参数进行不必要的计算
案例4测试(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())的副作用只发生了一次。然而,在叫名字的版本中,副作用发生了两次。
这是因为按值调用函数在调用函数之前会计算传入表达式的值,因此每次都会访问相同的值。但是,每次访问传入表达式时,名称调用函数都会重新计算传入表达式的值。
最好使用名字调用的例子
来自: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方法什么也不做(因为日志级别被配置为更高),那么computeTimeSpent将永远不会被调用,从而节省时间。这种情况在日志记录器中经常发生,其中人们经常看到字符串操作,相对于被记录的任务来说,它可能是昂贵的。
正确性示例:逻辑运算符。
你可能见过这样的代码:
if (ref != null && ref.isSomething)
想象你像这样声明&&方法:
trait Boolean {
def &&(other: Boolean): Boolean
}
然后,当ref为null时,你就会得到一个错误,因为isSomething在被传递给&&之前会在null引用上被调用。因此,实际的声明是:
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
为了重复@Ben在上面评论中的观点,我认为最好将“按名字称呼”视为语法糖。解析器只是将表达式包装在匿名函数中,以便稍后在使用它们时调用它们。
实际上,不是定义
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
在你的例子中,所有的参数都将在函数中调用之前被求值,因为你只是通过值定义它们。 如果你想通过名称定义参数,你应该传递一个代码块:
def f(x: => Int, y:Int) = x
这样,在函数中调用参数x之前,将不会计算参数x。
这篇小文章也很好地解释了这一点。