分享

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



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






引言

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



  1. package com.doggie  
  2.   
  3. object Add {  
  4.   def addByName(a: Int, b: => Int) = a + b   
  5.   def addByValue(a: Int, b: Int) = a + b   
  6. }
复制代码


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

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


  1.   addByName(2, 2 + 2)  
  2. ->2 + (2 + 2)  
  3. ->2 + 4  
  4. ->6  
  5.   
  6.   addByValue(2, 2 + 2)  
  7. ->addByValue(2, 4)  
  8. ->2 + 4  
  9. ->6
复制代码


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

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




酒鬼喝酒

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

代码如下:



  1. package com.doggie
  2. object Drunkard {  
  3.   //最开始拥有的软妹币  
  4.   var money = 10  
  5.   //每天喝掉一个软妹币  
  6.   def drink: Unit = {  
  7.     money -= 1  
  8.   }  
  9.   //数钱时要算上被喝掉的软妹币  
  10.   def count: Int = {  
  11.     drink  
  12.     money  
  13.   }  
  14.   //每天都数钱  
  15.   def printByName(x: => Int): Unit = {  
  16.     for(i <- 0 until 5)  
  17.       println("每天算一算,酒鬼还剩" + x + "块钱!")  
  18.   }  
  19.   //第一天数一下记墙上,以后每天看墙上的余额  
  20.   def printByValue(x: Int): Unit = {  
  21.     for(i <- 0 until 5)  
  22.       println("只算第一天,酒鬼还剩" + x + "块钱!")  
  23.   }  
  24.    
  25.   def main(args: Array[String]) = {  
  26.     printByName(count)  
  27.     printByValue(count)  
  28.   }  
  29. }  
复制代码


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


  1. 每天算一算,酒鬼还剩9块钱!  
  2. 每天算一算,酒鬼还剩8块钱!  
  3. 每天算一算,酒鬼还剩7块钱!  
  4. 每天算一算,酒鬼还剩6块钱!  
  5. 每天算一算,酒鬼还剩5块钱!  
  6. 只算第一天,酒鬼还剩4块钱!  
  7. 只算第一天,酒鬼还剩4块钱!  
  8. 只算第一天,酒鬼还剩4块钱!  
  9. 只算第一天,酒鬼还剩4块钱!  
  10. 只算第一天,酒鬼还剩4块钱!
复制代码


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

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



两者的比较

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

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

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

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


  1. package com.doggie  
  2.   
  3. object WhyAlwaysMe {  
  4.   var flag: Boolean = true  
  5.   def useOrNotUse(x: Int, y: => Int) = {  
  6.     flag match{  
  7.       case true => x  
  8.       case false => x + y  
  9.     }  
  10.   }  
  11.   def main(args: Array[String]) =   
  12.   {  
  13.     println(useOrNotUse(1, 2))  
  14.     flag = false  
  15.     println(useOrNotUse(1, 2))  
  16.   }  
  17. }  
复制代码



参考:

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://blog.csdn.net/asongoficeandfire/article/details/21889375


欢迎加入about云群90371779322273151432264021 ,云计算爱好者群,亦可关注about云腾讯认证空间||关注本站微信

已有(3)人评论

跳转到指定楼层
355815741 发表于 2014-12-27 13:36:08
学习了,谢谢分享~
回复

使用道具 举报

小南3707 发表于 2014-12-28 09:54:56
不错~                     
回复

使用道具 举报

czwanglei 发表于 2017-4-24 20:40:06
感觉很多都是抄别人的文章
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条