scala中的call-by-name和call-by-value

scala中的call-by-name和call-by-value

var/def/val/lazy val

  • def:类似于每一次重新赋值,如果是用def定义函数,则是每一次重新获得一个函数,做call-by-name操作。
  • val:获得一次,并立即执行,且在生命周期内不能再被修改,使用的是call-by-value操作。
  • var:在生命周期内可以被再次赋值
  • lazy val:惰性执行,也就是赋值(绑定)的时候先不会执行,等到需要的时候再执行

例子

scala> def f = {println("hello"); 1.0}
f: Double

scala> f
hello
res3: Double = 1.0

scala> f
hello
res4: Double = 1.0

scala> f
hello
res5: Double = 1.0

这里使用的是def,则每次使用的时候相当于获取一个新的函数。
体现是,每一次使用f的时候,都会获得一个{ println(“hello”); 1.0},其返回值为1,但是中间过程会打印出hello,这是在赋值(绑定)的时候才会打印出来的。

scala> val f = { println("hello"); 1.0}
hello
f: Double = 1.0

scala> f
res6: Double = 1.0

scala> f
res7: Double = 1.0

这里使用val,则只有一次绑定,所以后面不再打印出hello。如果觉得迷糊,可以继续看下去。

scala> lazy val f = { println("hello"); 1.0}
f: Double = <lazy>

scala> f
hello
res8: Double = 1.0

scala> f
res9: Double = 1.0

这里使用的是lazy val,即为惰性求值的。可以看到在进行绑定的时候并没有打印出hello,也看不到其值,是因为只是定义了这个值。

在第二次进行使用的时候则会真正去计算这个值(类似于val),第一次会打印出hello,之后就不再打印出了

函数定义中的call-by-name和call-by-value

看了上面的讲解还觉得迷糊的,就是不太理解call-by-name和call-by-value。

scala的call by name 和call by value最大的区别就是:

call-by-name在调用的时候会重新根据name做计算,而call-by-value预先计算,然后保留计算值后一直使用这个value。

纯函数例子[1]

一个Stack Overflow的例子。

def something() = {
  println("calling something")
  1 // return value
}

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

这是因为在调用函数之前,call-by-value会先计算传入的表达式的值,因此每次都访问相同的值,且也不会再次调用。

但是,call-by-name,每次应用的时候,都会重新计算传入的表达式的值。

不纯函数的例子[2]

上的都是纯函数,下面写一个非纯函数可能会更加明显。

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

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)
    }
}
/*
每天算一算,酒鬼还剩9块钱!  
每天算一算,酒鬼还剩8块钱!  
每天算一算,酒鬼还剩7块钱!  
每天算一算,酒鬼还剩6块钱!  
每天算一算,酒鬼还剩5块钱!  
只算第一天,酒鬼还剩4块钱!  
只算第一天,酒鬼还剩4块钱!  
只算第一天,酒鬼还剩4块钱!  
只算第一天,酒鬼还剩4块钱!  
只算第一天,酒鬼还剩4块钱!
*/  

可以看到,酒鬼最初5天每天都会数一下口袋里的软妹币(call-by-name),得到了每天喝酒花钱之后剩下的软妹币数量。

他觉得麻烦,于是想出了一个聪明的方法,在第六天的时候,他将口袋里还剩下的余额数写在了墙上(call-by-value),以后每天看一下墙上的数字,就知道自己还剩多少钱了。

死循环例子

我们可以写出一个函数递归的”循环”。

如果在call-by-name下会停止,并不表示着在call-by-value下会停止。
但是,如果call-by-value停止了,call-by-name一定会停止。

// 这是一个死循环
 def loop: Boolean = loop

 // 用val定义时会做call-by-value,以下语句会block住
 val x = loop 

 // 用def定义时,是做的call-by-name。故以下语句暂时不会执行,在用到y的时候才做evaluation
 def y = loop

两者的比较

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

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

object Main {

    def useOrNotUse(flag: Boolean, x: Int, y: => Int) = {
        if (flag) {
            x
        }
        else {
            x + y
        }
    }

    def main(args: Array[String]) = {
        val flag = true
        println(useOrNotUse(flag, 1, 2))
        val flag = false
        println(useOrNotUse(flag, 1, 2))
    }
}  

参考资料

  1. scala中的var,val,immutable,mutable理解小结
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值