Scala的类型推断

Posted 鸿的学习笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala的类型推断相关的知识,希望对你有一定的参考价值。

从一段代码开始:

scala> def sort1[T](cp:(T,T) => Boolean)(xs:List[T]) = {xs sortWith (cp)}
sort1: [T](cp: (T, T) => Boolean)(xs: List[T])List[T]

scala> sort1((x:Int,y:Int)=>x>y)(List(1,2,3,4,5))
res0: List[Int] = List(5, 4, 3, 2, 1)

scala> List(1,2,3,4,5) sortWith (_>_)
res1: List[Int] = List(5, 4, 3, 2, 1)

scala> sort1(_>_)(List(1,2,3,4,5))
<console>:9: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$greater(x$2))
             sort1(_>_)(List(1,2,3,4,5))

上面的两端代码都是等价的,但是第一段代码sort1这个偏函数需要指定传入的类型才能运行,而sortWith则不需要。对于等效的代码,为什么sort1无法使用类型推断,而sortWith可以呢?

首先看看维基上对类型推断的定义:

Type inference refers to the automatic detection of the data type of an expression in a programming language.

类型推断指的是程序语言有自动推断表达式数据类型的能力,而无需程序员指定数据类型,简化程序员的工作。如下面,可以指定a为Int类型,也可以让Scala推断出b为Int类型。

scala> val a:Int = 1
a: Int = 1

scala> val b = 1
b: Int = 1

对于类型推断算法最出名的应该是HM算法,大概意思就是先构建一棵包含全部元素的解析树,再分析全部元素可能属于的类型,并且达到推导出最终的数据类型。具体的细节可以参考它们的论文(https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system)。HM算法是基于全局类型进行推导的,但是Scala有些许不同,因为Scala需要支持面向对象编程,所以它选择了局部的基于程序流的方式。
对于HM算法,下面的函数是成立的,但是Scala会报错:

scala> def sum(x:Int) = {if (x == 1) 1 else x + sum(x-1)}
<console>:7: error: recursive method sum needs result type
      def sum(x:Int) = {if (x == 1) 1 else x + sum(x-1)}

只能给sum函数的结果加上数据类型:

scala> def sum(x:Int):Int = {if (x == 1) 1 else x + sum(x-1)}
sum: (x: Int)Int

scala> sum(2)
res3: Int = 3

这里体现了基于局部的类型推断的局限,Scala无法推断出sum函数的返回类型。但是这样的处理方式会使得对子类型处理更加优雅。

scala> List(1,2,3,4)
res5: List[Int] = List(1, 2, 3, 4)

scala> List(1,"foo")
res6: List[Any] = List(1, foo)

我们可以通过局部的类型判断直接将异构的List判定为所有类型的父类Any。

现在再回到第一段代码:
sortWith函数的可以通过List(1,2,3,4,5)进而推断出_>_等价于(x:Int,y:Int)=>x>y,而sort1如果传入的判断方法为_>_,Scala的类型推断无法根据sort1的类型推断出_>_的类型,自然就会报错了,而我们对sort1函数稍作修改:

scala> def sort1[T](xs:List[T])(cp:(T,T) => Boolean) = {xs sortWith (cp)}
sort1: [T](xs: List[T])(cp: (T, T) => Boolean)List[T]

scala> sort1(List(1,2,3,4,5))(_>_)
res0: List[Int] = List(5, 4, 3, 2, 1)

这样我们就可以借助Scala的类型推断,而不需要写(x:Int,y:Int)=>x>y这么长的语句了。除了修改原函数外,我们也可以使用类型参数,指定sort1的参数。

scala> def sort1[T](cp:(T,T) => Boolean)(xs:List[T]) = {xs sortWith (cp)}
sort1: [T](cp: (T, T) => Boolean)(xs: List[T])List[T]

scala> sort1[Int](_>_)(List(1,2,3,4,5))
res1: List[Int] = List(5, 4, 3, 2, 1)


以上是关于Scala的类型推断的主要内容,如果未能解决你的问题,请参考以下文章

Scala的类型推断

为啥方差注释会导致 Scala 无法推断出这种子类型关系?

Scala 编译器无法在 Spark lambda 函数中推断类型

为啥 Scala 类型推断在这里失败?

如何知道 Spark 使用 Scala 推断出的 RDD 类型是啥

[原创]Scala学习:关于变量(val,var,类型推断)