再谈eta-conversion与eta-expansion

之前写过与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

再谈eta-conversion与eta-expansion》上有5个想法

  1. Pingback引用通告: scala类型系统:类型推导 | 在路上

  2. 他提到func _ 即把func方法转成对象也被成为eta-expansion,对比eta-conversion正好是反过来的过程
    成为是错别字:称为?

  3. 看的不是太懂,在新版的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

    没有函数的显示。

  4. 部分应用函数 = partially applied function
    偏函数 = partial function
    这是两个不也一样的概念。。。

发表评论

电子邮件地址不会被公开。 必填项已用*标注