类型推导是一门博大的学问,背后有繁冗的理论,好在非编译器开发者不用太多了解,我看到这方面的文章时立刻就知难而退了。这里蜻蜓点水的带过。
Scala采用的局部的(local)、基于流的(flow-based)类型推断;而非像ML,Haskell等语言采用的更加全局化的Hindley-Milner类型推断方式。
在《Programming in Scala》一书中提到基于流的类型推断有它的局限性,但是对于面向对象的分支类型处理比Hindley-Mlner更加优雅。
基于流的类型推导在偏应用函数场景下,不能对参数类型省略,正体现了它的局限性。下面的偏应用函数声明,在Hindley-Milner类型推导(基于全局的)可以正常的推导的,会在scala里报错:
scala> def foo(a:Int, b:String) = a+b
scala> val f = foo(200, _)
<console>:8: error: missing parameter type for expanded function ((x$1) => foo(200, x$1))
val f = foo(200, _)
^
上面第二个参数占位符缺乏类型。需要显式的声明变量f的类型
scala> val f: String=>String = foo(200, _)
f: String => String = <function1>
或者显式的声明占位符参数类型
scala> val f = foo(200, _:String)
f: String => String = <function1>
不过这里有一个细节是eta-expansion
的情况下可以省略参数类型,比如:
scala> def foo(a:Int, b:String) = a+b
scala> val f = foo _
f: (Int, String) => String = <function2>
scala> val f = foo(_,_)
f: (Int, String) => String = <function2>
上面两种写法占位符都是对全部参数,foo _
和 foo(_,_)
在编译过程会编译器会进行eta-expansion
,eta扩展
会把这个表达式转换为与foo
方法类型一致的函数对象。这是在规范里定义的。
而最开始的写法 foo(200, _)
并不是eta-expansion
(原因见之前的这篇)。这种情况下类型推导并不会从方法体的上下文去推断参数的类型。所以需要显式的声明参数类型。
另外,对于泛型方法的情况,类型推导也需要注意泛型参数,参考这篇
学习了,有些了解了,还需要多实践才能了解其中的真谛,多谢了
在scala2.13中测试这种写法已经不再报错了
“`
def f2(a:Int, b:String) = b+a
val f = f2(1, _)
—–
f2: f2[](val a: Int,val b: String) => String
f: String => String = $Lambda$949/1509325680@3584b76e
“`