之前写过与eta-conversion有关的两篇blog,参考:
scala中的eta-conversion
scala雾中风景(1): lambda表达式的缩写
前几天看了一下shapeless作者的blog,他提到func _
即把func方法转成对象也被成为eta-expansion
,对比eta-conversion
正好是反过来的过程:
把 x => func(x) 简化为 func _ 或 func 的过程称为 eta-conversion
把 func 或 func _ 展开为 x => func(x) 的过程为 eta-expansion
让我想起之前blog里把eta-conversion
说成只适用于只有一个参数的函数是不对的。
今天老高转了一篇blog也提到eta-expansion
,看了一下他提到了场景更复杂一些,涉及在方法重载的情况下编译器在类型推导时怎么选择的。写这篇blog不讨论复杂的场景,只对以前的两篇blog做个纠正和补充。
之前我在了解eta-conversion
的时候,google的第一个结果就是haskell里的http://www.haskell.org/haskellwiki/Eta_conversion
看到它的描述,函数:
\x -> abs x //相当于scala里 x => abs(x)
经过eta转换后等价于 abs
,因为这个例子里列举的函数参数只有一个,我误以为eta-conversion
就只是针对只有一个参数的函数lambda表达式的缩写。
另外,Haskell里的函数默认就是柯里化的,即带有多个参数的函数最终都可转换为只有一个函数的高阶函数。参考http://learnyouahaskell.com/higher-order-functions,所以即使eta-conversion
在haskell里只针对带有一个参数的函数也说得过去。
Every function in Haskell officially only takes one parameter…
eta-conversion
并非Haskell发明的,而是在lambda演算里定义的: http://en.wikipedia.org/wiki/Lambda_calculus
大意上,当一个函数 newFunc
定义为: x => oldFunc(x)
时,newFunc(x)
与 oldFunc(x)
行为上完全等价,所以newFunc
也可以写为oldFunc
在scala里,eta-conversion
对函数的参数个数并不限定:
scala> def hf(f: (Int,Int)=>Unit) {
print("ok")
}
scala> def foo(x:Int, y:Int) {
print("donothing")
}
scala> hf(foo _) //把 (x,y) => foo(x, y) 简化为 foo _
ok
scala> hf(foo) //把 (x,y) => foo(x, y) 简化为 foo
ok
上面2种简化写法称为 eta-conversion
,编译器会把它们还原成函数对象称为eta-expansion
早先我们通过一些书或资料了解部分应用函数(partial function)时,讲述例子里的”_”符合时都只是说是“占位符”,基本不会提到这背后与eta转换有什么关系。还原一下在hf方法里的 foo _
与 foo
:
foo _ 这里的占位符相当于所有的参数,而不是单个参数,即 (x,y) => foo(x,y) 里的x,y都被"_"替代
对于 foo _
这种写法,编译器会严格按照部分应用函数来对待,把foo方法封装成一个函数对象。而对于后边不带下划线foo
这种写法,编译器还要看上下文,判断究竟是对其调用求值,还是进行eta转换。比如这种情况:
scala> def foo() = 200
// f缺乏类型信息,推导时优先尝试对foo求值, evaluation
scala> val f = foo
f: Int = 200
// f缺乏类型信息,但明确了把foo当函数对象对待, eta-expansion
scala> val f = foo _
f: () => Int = <function0>
// f声明了类型,且f的类型与foo的返回类型不匹配,尝试eta-expansion
scala> val f: ()=>Int = foo
f: () => Int = <function0>
在把方法转换为部分应用函数对象的过程中可能触发 eta-expansion
,但也不是所有的部分应用函数都会符合 eta-expansion
scala> def foo(x:Int, y:Int) = x+y
// 不符合 eta-expansion
scala> val f = foo(_:Int, 3)
f: Int => Int = <function1>
上面产生的部分应用函数与原 x => foo(x, 3)
与 (x,y) => foo(x, y)
不等价,并不是eta转换。
// 符合
scala> val f = foo(_:Int, _:Int)
f: (Int, Int) => Int = <function2>
上面产生的部分应用函数与原(x,y) => foo(x, y)
等价,符合eta-expansion
Pingback引用通告: scala类型系统:类型推导 | 在路上
他提到func _ 即把func方法转成对象也被成为eta-expansion,对比eta-conversion正好是反过来的过程
成为是错别字:称为?
看的不是太懂,在新版的scala 2.12.2中。
scala> val f = foo _
f: () => Int = $$Lambda$1044/1081047147@2b1416e1
scala> def foo(x:Int, y:Int) = x + y
foo: (x: Int, y: Int)Int
scala> val f = foo(_:Int, 3)
f: Int => Int = $$Lambda$1045/1329958413@14c4b715
scala> val f = foo(_:Int, _:Int)
f: (Int, Int) => Int = $$Lambda$1046/1631325517@1ab00937
没有函数的显示。
部分应用函数 = partially applied function
偏函数 = partial function
这是两个不也一样的概念。。。
谢谢指出