课程内容:java
按Pierce的话讲:“类型系统是一个语法方法,它们根据程序计算的值的种类对程序短语进行分类,经过分类结果错误行为进行自动检查。”es6
类型容许你表示函数的定义域和值域。例如,从数学角度看这个定义:算法
f: R -> N
它告诉咱们函数“f”是从实数集到天然数集的映射。编程
抽象地说,这就是 具体 类型的准肯定义。类型系统给咱们提供了一些更强大的方式来表达这些集合。安全
鉴于这些注释,编译器能够 静态地 (在编译时)验证程序是 合理 的。也就是说,若是值(在运行时)不符合程序规定的约束,编译将失败。数据结构
通常说来,类型检查只能保证 不合理 的程序不能编译经过。它不能保证每个合理的程序都 能够 编译经过。编程语言
随着类型系统表达能力的提升,咱们能够生产更可靠的代码,由于它可以在咱们运行程序以前验证程序的不变性(固然是发现类型自己的模型bug!)。学术界一直很努力地提升类型系统的表现力,包括值依赖(value-dependent)类型!ide
须要注意的是,全部的类型信息会在编译时被删去,由于它已再也不须要。这就是所谓的擦除。函数式编程
Scala强大的类型系统拥有很是丰富的表现力。其主要特性有:函数
val i: Int = 12: Int
多态性是在不影响静态类型丰富性的前提下,用来(给不一样类型的值)编写通用代码的。
例如,若是没有参数化多态性,一个通用的列表数据结构老是看起来像这样(事实上,它看起来很像使用泛型前的Java):
scala> 2 :: 1 :: "bar" :: "foo" :: Nil res5: List[Any] = List(2, 1, bar, foo)
如今咱们没法恢复其中成员的任何类型信息。
scala> res5.head res6: Any = 2
因此咱们的应用程序将会退化为一系列类型转换(“asInstanceOf[]”),而且会缺少类型安全的保障(由于这些都是动态的)。
多态性是经过指定 类型变量 实现的。
scala> def drop1[A](l: List[A]) = l.tail drop1: [A](l: List[A])List[A] scala> drop1(List(1,2,3)) res1: List[Int] = List(2, 3)
粗略地说,这意味着在Scala中,有一些你想表达的类型概念“过于泛化”以致于编译器没法理解。假设你有一个函数
def toList[A](a: A) = List(a)
你但愿继续泛型地使用它:
def foo[A, B](f: A => List[A], b: B) = f(b)
这段代码不能编译,由于全部的类型变量只有在调用上下文中才被固定。即便你“钉住”了类型B
:
def foo[A](f: A => List[A], i: Int) = f(i)
…你也会获得一个类型不匹配的错误。
静态类型的一个传统反对意见是,它有大量的语法开销。Scala经过 类型推断 来缓解这个问题。
在函数式编程语言中,类型推断的经典方法是 Hindley Milner算法,它最先是实如今ML中的。
Scala类型推断系统的实现稍有不一样,但本质相似:推断约束,并试图统一类型。
例如,在Scala中你没法这样作:
scala> { x => x } <console>:7: error: missing parameter type { x => x }
而在OCaml中你能够:
# fun x -> x;; - : 'a -> 'a = <fun>
在Scala中全部类型推断是 局部的 。Scala一次分析一个表达式。例如:
scala> def id[T](x: T) = x id: [T](x: T)T scala> val x = id(322) x: Int = 322 scala> val x = id("hey") x: java.lang.String = hey scala> val x = id(Array(1,2,3,4)) x: Array[Int] = Array(1, 2, 3, 4)
类型信息都保存无缺,Scala编译器为咱们进行了类型推断。请注意咱们并不须要明确指定返回类型。
Scala的类型系统必须同时解释类层次和多态性。类层次结构能够表达子类关系。在混合OO和多态性时,一个核心问题是:若是T’是T一个子类,Container[T’]应该被看作是Container[T]的子类吗?变性(Variance)注解容许你表达类层次结构和多态类型之间的关系:
含义 | Scala 标记 | |
协变covariant | C[T’]是 C[T] 的子类 | [+T] |
逆变contravariant | C[T] 是 C[T’]的子类 | [-T] |
不变invariant | C[T] 和 C[T’]无关 | [T] |
子类型关系的真正含义:对一个给定的类型T,若是T’是其子类型,你能替换它吗?
scala> class Covariant[+A] defined class Covariant scala> val cv: Covariant[AnyRef] = new Covariant[String] cv: Covariant[AnyRef] = Covariant@4035acf6 scala> val cv: Covariant[String] = new Covariant[AnyRef] <console>:6: error: type mismatch; found : Covariant[AnyRef] required: Covariant[String] val cv: Covariant[String] = new Covariant[AnyRef] ^
scala> class Contravariant[-A] defined class Contravariant scala> val cv: Contravariant[String] = new Contravariant[AnyRef] cv: Contravariant[AnyRef] = Contravariant@49fa7ba scala> val fail: Contravariant[AnyRef] = new Contravariant[String] <console>:6: error: type mismatch; found : Contravariant[String] required: Contravariant[AnyRef] val fail: Contravariant[AnyRef] = new Contravariant[String] ^
逆变彷佛很奇怪。何时才会用到它呢?使人惊讶的是,函数特质的定义就使用了它!
trait Function1 [-T1, +R] extends AnyRef
若是你仔细从替换的角度思考一下,会发现它是很是合理的。让咱们先定义一个简单的类层次结构:
scala> class Animal { val sound = "rustle" } defined class Animal scala> class Bird extends Animal { override val sound = "call" } defined class Bird scala> class Chicken extends Bird { override val sound = "cluck" } defined class Chicken
假设你须要一个以Bird
为参数的函数:
scala> val getTweet: (Bird => String) = // TODO
标准动物库有一个函数知足了你的需求,但它的参数是Animal
。在大多数状况下,若是你说“我须要一个___,我有一个___的子类”是能够的。可是,在函数参数这里是逆变的。若是你须要一个参数为Bird
的函数,而且指向一个参数为Chicken
的函数,那么给它传入一个Duck
时就会出错。但指向一个参数为Animal
的函数就是能够的:
scala> val getTweet: (Bird => String) = ((a: Animal) => a.sound ) getTweet: Bird => String = <function1>
函数的返回值类型是协变的。若是你须要一个返回Bird
的函数,但指向的函数返回类型是Chicken
,这固然是能够的。
scala> val hatch: (() => Bird) = (() => new Chicken ) hatch: () => Bird = <function0>
Scala容许你经过 边界 来限制多态变量。这些边界表达了子类型关系。
scala> def cacophony[T](things: Seq[T]) = things map (_.sound) <console>:7: error: value sound is not a member of type parameter T def cacophony[T](things: Seq[T]) = things map (_.sound) ^ scala> def biophony[T <: Animal](things: Seq[T]) = things map (_.sound) biophony: [T <: Animal](things: Seq[T])Seq[java.lang.String] scala> biophony(Seq(new Chicken, new Bird)) res5: Seq[java.lang.String] = List(cluck, call)
类型下界也是支持的,这让逆变和巧妙协变的引入驾轻就熟。List[+T]
是协变的;一个Bird的列表也是Animal的列表。List
定义一个操做::(elem T)
返回一个加入了elem
的新的List
。新的List
和原来的列表具备相同的类型:
scala> val flock = List(new Bird, new Bird) flock: List[Bird] = List(Bird@7e1ec70e, Bird@169ea8d2) scala> new Chicken :: flock res53: List[Bird] = List(Chicken@56fbda05, Bird@7e1ec70e, Bird@169ea8d2)
List
一样 定义了::[B >: T](x: B)
来返回一个List[B]
。请注意B >: T
,这指明了类型B
为类型T
的超类。这个方法让咱们可以作正确地处理在一个List[Bird]
前面加一个Animal
的操做:
scala> new Animal :: flock res59: List[Animal] = List(Animal@11f8d3a8, Bird@7e1ec70e, Bird@169ea8d2)
注意返回类型是Animal
。
有时候,你并不关心是否可以命名一个类型变量,例如:
scala> def count[A](l: List[A]) = l.size count: [A](List[A])Int
这时你可使用“通配符”取而代之:
scala> def count(l: List[_]) = l.size count: (List[_])Int
这至关因而下面代码的简写:
scala> def count(l: List[T forSome { type T }]) = l.size count: (List[T forSome { type T }])Int
注意量化会的结果会变得很是难以理解:
scala> def drop1(l: List[_]) = l.tail drop1: (List[_])List[Any]
忽然,咱们失去了类型信息!让咱们细化代码看看发生了什么:
scala> def drop1(l: List[T forSome { type T }]) = l.tail drop1: (List[T forSome { type T }])List[T forSome { type T }]
咱们不能使用T由于类型不容许这样作。
你也能够为通配符类型变量应用边界:
scala> def hashcodes(l: Seq[_ <: AnyRef]) = l map (_.hashCode) hashcodes: (Seq[_ <: AnyRef])Seq[Int] scala> hashcodes(Seq(1,2,3)) <console>:7: error: type mismatch; found : Int(1) required: AnyRef Note: primitive types are not implicitly converted to AnyRef. You can safely force boxing by casting x.asInstanceOf[AnyRef]. hashcodes(Seq(1,2,3)) ^ scala> hashcodes(Seq("one", "two", "three")) res1: Seq[Int] = List(110182, 115276, 110339486)
参考 D. R. MacIver写的Scala中的存在类型