sunshine_junge 发表于 2014-12-27 11:30:16

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



问题导读:
1.Scala函数参数传值调用?
2.Scala函数参数传名调用?



static/image/hrline/4.gif


引言

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) = {
    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) =   
{
    println(useOrNotUse(1, 2))
    flag = false
    println(useOrNotUse(1, 2))
}
}


参考:http://stackoverflow.com/questions/13337338/call-by-name-vs-call-by-value-in-scala-clarification-neededhttp://www.cnblogs.com/nixil/archive/2012/05/31/2528068.html




引用:http://blog.csdn.net/asongoficeandfire/article/details/21889375


355815741 发表于 2014-12-27 13:36:08

学习了,谢谢分享~

小南3707 发表于 2014-12-28 09:54:56

不错~                     

czwanglei 发表于 2017-4-24 20:40:06

感觉很多都是抄别人的文章
页: [1]
查看完整版本: Scala从零开始:函数参数的传名调用(call-by-name)和传值调用(call-by-value)