scala中的by-name parameter详解

[转载自 Gossip@caterpillar]

个人翻译为简体中文, 供自己学习使用

scala中的by-name parameter介绍:

到目前為止,所定义的带参数的函数,必须先根据参数的表达式计算出值,然后才能调用该函数。例如:

def sum(a: Int, b: Int) = a + b

println(sum(1 + 2, 3 + 4))       // 显示10

在调用函数sum之前,1+2和3+4都会先被计算出结果,然后以sum(3, 7)的方式来调用sum函数,a 和 b 这两个参数称为传值参数(By-value paramenter)。

考虑另一种情况, 如果你想开发以下函数:

def unless(cond: Boolean, func: () => Any) = {
    if(!cond) {       
         func()   
    }
}

unless(false, () => println("XD"))
unless(true,  () => println("Orz"))

这个函数的作用是:除非cond为true, 否则就执行传入的函数字面量。所传入的函数字面量是无参的函数,不过 () => 仍然不能省略,也就是说你不能够写成以下形式:

unless(false, println("XD"))   // 该形式编译时错误

如果你想要省略()=>, 则可以这样定义该函数:

def unless(cond: Boolean, expr: => Any) = {   
    if(!cond) {
         expr   
    }
}

unless(false, println("XD"))
unless(true,  println("Orz"))

在这个定义下, expr的参数类型是 =>Any , 称之为传名参数 (By-name parameter)。
可以看到, 在调用,unless函数时, 直接写了println("XD")这个表达式,省略了()=>部分。
事实上, 省略()=>并不是这个例子的重点, 重点在于println("XD")表达式,不会被马上执行,真正执行是在cond参数为false时,也就是unless函数中if结果为true时。

传名参数的命名由此而来, 献给予所给定的参数表达式一个名称,然后以这个名称代替参数表达式的执行结果来调用该函数,真正执行该表达式的位置则是在你所定义的函数内部。

需要注意的是,传名参数并不是一个函数字面量,它只是一个表达式的别名。所以你不能按照下面的写法来用:

def unless(cond: Boolean, expr: => Any) = {
    if(!cond) {
        expr()   // 不能有括號
    }
}

对于scala中短路的与&&和短路或 || 其实都是方法的名称, 那它们是如何实现短路作用的呢?其实就是用传名参数实现的。以下是模拟短路与 && 的函数:

def and(c1: Boolean, c2: => Boolean) = {
    if(c1) c2 else c1
}

println(and(5 > 3, 10 > 3))  // true
println(and(5 > 3, 10 < 3))  // false
println(and(1 > 3, 10 > 3))  // false

以下這個範例可以證明上面的and函式確實有捷徑運算的作用:

def and(c1: Boolean, c2: => Boolean) = {   
    if(c1) c2 else c1
}
println(and(5 > 3, {print("run.. "); 10 > 3}))  // 顯示 run.. false
println(and(1 > 3, {print("run.. "); 10 > 3}))  // 顯示 false

由于第一个and函数被调用时,5>3成立,所以必须要检测第二个表达式, 因而会显示run...的信息。而第二个and函数调用时,1>3为false,所以就不需要检测第二个表达式了,直接返回false,所以不会显示run...的信息。

在scala中,没有until的功能,也就是除非条件成立,否则就反复执行一些功能,一下是模拟until功能的函数:

def until(cond: => Boolean, expr: => Unit) {
    if(!cond) {      
       expr       
        until(cond, expr)   
    }
}

var count = 10
until(count == 0, {   
   println(count)   
   count -= 1
})

如果将第一个参数改为cond:Boolean,那么until函数将永远不会停止, 因为count == 0会先运算为false,在传递给until函数。所以until函数中的!cond将永远为true,所以一直跑下去。直到堆栈溢出为止。

事实上, 如果使用scala中的currying特性, 可以让unless和util函数看起来就像语言的内置控制结构一样。这也是scala支持扩展性的一个方式。

(完)

你可能感兴趣的:(scala)