您的位置:首页 > 产品设计 > UI/UE

Scala从零开始:函数参数的传名调用(call-by-name)和传值调用(call-by-value)

2014-03-23 22:58 513 查看

引言

Scala的解释器在解析函数参数(function arguments)时有两种方式:先计算参数表达式的值(reduce the arguments),再应用到函数内部;或者是将未计算的参数表达式直接应用到函数内部。前者叫做传值调用(call-by-value),后者叫做传名调用(call-by-name)。

package com.doggie

object Add {
def addByName(a: Int, b: => Int) = a + b
def addByValue(a: Int, b: Int) = a + b
}


addByName是传名调用,addByValue是传值调用。语法上可以看出,使用传名调用时,在参数名称和参数类型中间有一个=》符号。

以a为2,b为2 + 2为例,他们在Scala解释器进行参数规约(reduction)时的顺序分别是这样的:

 

addByName(2, 2 + 2)
->2 + (2 + 2)
->2 + 4
->6

addByValue(2, 2 + 2)
->addByValue(2, 4)
->2 + 4
->6


可以看出,在进入函数内部前,传值调用方式就已经将参数表达式的值计算完毕,而传名调用是在函数内部进行参数表达式的值计算的。

这就造成了一种现象,每次使用传名调用时,解释器都会计算一次表达式的值。对于有副作用(side-effect)的参数来说,这无疑造成了两种调用方式结果的不同。

酒鬼喝酒

举一个例子,假设有一只酒鬼,他最初有十元钱,每天喝酒都会花掉一元钱。设他有一个技能是数自己的钱,返回每天他口袋里钱的最新数目。

代码如下:

package com.doggie

object Drunkard {
//最开始拥有的软妹币
var money = 10
//每天喝掉一个软妹币
def drink: Unit = {
money -= 1
}
//数钱时要算上被喝掉的软妹币
def count: Int = {
drink
money
}
//每天都数钱
def printByName(x: => Int): Unit = {
for(i <- 0 until 5)
println("每天算一算,酒鬼还剩" + x + "块钱!")
}
//第一天数一下记墙上,以后每天看墙上的余额
def printByValue(x: Int): Unit = {
for(i <- 0 until 5)
println("只算第一天,酒鬼还剩" + x + "块钱!")
}

def main(args: Array[String]) = {
printByName(count)
printByValue(count)
}
}

 

我们使用成员变量money来表示酒鬼剩下的软妹币数量,每次发动drink技能就消耗一枚软妹币,在count中要计算因为drink消费掉的钱。我们定义了两种计算方式,printByName是传名调用,printByValue是传值调用。查看程序输出:

每天算一算,酒鬼还剩9块钱!
每天算一算,酒鬼还剩8块钱!
每天算一算,酒鬼还剩7块钱!
每天算一算,酒鬼还剩6块钱!
每天算一算,酒鬼还剩5块钱!
只算第一天,酒鬼还剩4块钱!
只算第一天,酒鬼还剩4块钱!
只算第一天,酒鬼还剩4块钱!
只算第一天,酒鬼还剩4块钱!
只算第一天,酒鬼还剩4块钱!


可以看到,酒鬼最初5天每天都会数一下口袋里的软妹币(call-by-name),得到了每天喝酒花钱之后剩下的软妹币数量,钱越来越少,他深感不能再这么堕落下去了。于是想出了一个聪明的方法,在第六天他将口袋里还剩下的余额数写在了墙上,以后每天看一下墙上的数字(call-by-value),就知道自己还剩多少钱了-___________________-

怎么样,这个酒鬼够不够聪明?

 

两者的比较

传值调用在进入函数体之前就对参数表达式进行了计算,这避免了函数内部多次使用参数时重复计算其值,在一定程度上提高了效率。

但是传名调用的一个优势在于,如果参数在函数体内部没有被使用到,那么它就不用计算参数表达式的值了。在这种情况下,传名调用的效率会高一点。

讲到这里,有些同学不开心了:你这不是耍我么?函数体内部不使用参数,干嘛还要传进去?

别着急,这里有一个例子:

package com.doggie

object WhyAlwaysMe {
var flag: Boolean = true
def useOrNotUse(x: Int, y: => Int) = {
flag match{
case true => x
case false => x + y
}
}
def main(args: Array[String]) =
{
println(useOrNotUse(1, 2))
flag = false
println(useOrNotUse(1, 2))
}
}

 

You got it?

 

参考:

http://stackoverflow.com/questions/13337338/call-by-name-vs-call-by-value-in-scala-clarification-needed

http://www.cnblogs.com/nixil/archive/2012/05/31/2528068.html

http://www.scala-lang.org/docu/files/ScalaByExample.pdf

 

声明:本文为原创,禁止用于任何商业目的,转载请注明出处:http://blog.csdn.net/asongoficeandfire/article/details/21889375
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息