标签归档:lambda

scala雾中风景(1): lambda表达式的缩写

scala中的函数是可以像普通变量一样来传递的,也允许方法的参数是函数类型。运行时传递一个函数变量,或者用lambda表达式描述的一段匿名函数。比如:

// 普通函数
scala> def foo(s:String) = { println(s) }

// 高阶函数
scala> def hf(f:String=>Unit) = f("higher")

// 把普通函数赋值给变量f
scala> val f:String=>Unit = foo

// 上面的函数类型String=>Unit 也可以写为Function1[String,Unit]
scala>  val f:Function1[String,Unit] = foo

// 把变量 f 当作参数传递给高级函数hf
scala> hf(f)
higher

现在看看以lambda的形式

scala> hf((s:String)=>println(s))
higher

这里使用lambda表达式 (s:String) => println(s) 作为匿名函数传入的,有趣的是,它可以简化:

scala> hf(s=>println(s)) // 第1个简化版本
higher

scala> hf(println(_)) // 第2个简化版本
higher

scala> hf(println _) // 第3个简化版本
higher

scala> hf(println) // 第4个简化版本
higher

上面的简化版本里前面的都好理解。第1个版本省略了s的类型,这是因为在定义hf时已经声明了参数类型为String=>Unit,编译器会把lambda表达式中的入参和出参按声明的类型来对待。

第2个版本则是采用了占位符的方式。它的形式为,对于所有的 x=>g(x) 都可以用占位符的形式写为:g(_),相当于省去了lambda表达式的入参和箭头部分,然后用占位符表示入参。

第3个简化版本是第2个版本的变种,是函数式语言里的一个习俗,scala继承了这一风格:当只有一个参数时,函数的小括号可写可不写。f(x) 可以写为 f x

//2014.9月更正,上面中划线部分是错误的说法,单独调用函数时小括号部分不可省略,单个参数的函数其小括号只有在 Object func param这种形式下可省略,比如 a equals b

第3个版本和第2个版本都是部分应用函数(partial applied function)的写法,当只有一个参数时,这两个写法是等价的。

让人困惑的是第4个版本!为什么连参数都可以省略?这还是一个lambda表达式么?是所有的lambda都可以缩写成这种形式么?

第4个版本的背后其实是编译器支持lambda的“eta转换”(可以参考之前的文章scala中的eta-conversion)。
简单的说就是对于lambda表达式中只有一个参数,并且箭头右边的逻辑是对入参执行一个函数:即 x => f(x)
则可以简写为f

所以第4个版本的简化就是eta转换,根据上面的说法,并不是所有的lambda表达式都可以缩写成这个样子的。

小结:lambda表达式的简化有时让人觉得云里雾里,但每个简化形式背后都有规则或习俗。eta-conversion是最容易迷惑的一种形式,它让表达式看上去不像是lambda表达式。