逸言

Magic Scala(1): Call by Name

| Comments

在Scala中,调用函数有两种形式:Call by value(按值调用)和call by name(按名称调用)。若是call by value,会先计算参数的值,然后再传递给被调用的函数;若是call by name,参数会到实际使用的时候才计算。例如:

val logEnable = false

def log(msg: String) =
    if (logEnable) println(msg)

val MSG = "programing is running"

log(MSG + 1 / 0)

此时的log函数是call by value。因此在调用log函数时,会先计算传入的参数,此时会计算MSG + 1/0。由于表达式中有0作为被除数,因此会抛出异常:

Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Main$.main(scala-script352098905369979205.scala:16)
        at Main.main(scala-script352098905369979205.scala)
exit value is 1
Program exited.

如果修改log的定义为:

def log(msg: => String) =
     if (logEnable) println(msg)

当调用log函数:log(MSG + 1/0)时,它首先并不会计算MSG + 1/0表达式,而是先执行log的函数体,即判断logEnable的值。此时logEnable值为false,此时就不会执行该分支println(msg)。既然不会执行println,就不会计算MSG + 1/0。因此就不会抛出异常。

再看另外一个例子。首先定义一个函数:

def getOneWhatever():Int = {
     println("calling getOneWhatever")
     1
}

然后,再定义两个函数,分别用call by value和call by name的方式:

def callByValue(x: Int) = {
     println("x1=" + x)
     println("x2=" + x)
}

def callByName(x: => Int) = {
     println("x1=" + x)
     println("x2=" + x)
}

如果执行callByValue(getOneWhatever()),则结果为:

calling getOneWhatever
x1=1
x2=1

若执行callByName(getOneWhatever()),则结果为:

calling getOneWhatever
x1=1
calling getOneWhatever
x2=1

注意看二者的区别,采用by name的方式,getOneWhatever函数被执行了两次,这是因为在callByName函数中,传入的参数被调用了两次。

Comments