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
相关文章推荐
- scala:函数参数的传名调用(call-by-name)和传值调用(call-by-value)
- Scala 函数传名调用(call-by-name)
- Scala 学习笔记(一)------函数传名调用(call-by-name)
- scala函数的call-by-name和call-by-value 区别
- scala 传名调用call-by-name
- vb6 functionPtr 函数指针 CallbyName CallbyAddress 虚函数 Matthew Curland的VB函数指针调用
- vb module_FunctionPtr 与FunctionPtr共同实现 CallFromDll callbyAddress 可以调用模块的函数/callbyname
- Scala中的call-by-name与call-by-value
- call by name 与 call by value in scala
- scala中的call-by-name和call-by-value
- scala中的call-by-name和call-by-value
- scala 中的def/val/lazy val/的区别,call-by-value/call-by-name
- scala中的call-by-name和call-by-value
- scala中call-by-name和call-by-value
- scala def/val/lazy val区别以及call-by-name和call-by-value
- CallByValue和CallByName区别
- 方法参数中pass by reference(传引用)和 pass by value(传值)的区别
- 函数参数的传值调用和传址调用
- 函数参数之 传常引用(passed by reference to const)替换 传值(passed by value)
- Flex反射:通过调用getDefintionByName函数动态创建按钮等控件的例子