Scala是一门多范式的编程语言,一种类似java的编程语言,设计初衷是实现可伸缩的语言 、并集成面向对象编程和函数式编程的各种特性。
什么是静态类型?为什么它们很有用?
根据Picrce的说法:“类型系统是一个可以根据代码段计算出来的值对它们进行分类,然后通过语法的手段来自动检测程序错误的系统。”
类型可以让你表示函数的域和值域。例如,在数学里,我们经常看到下面的函数:
这个定义告诉我们函数”f”的作用是把实数集里的数映射到自然集里。
抽象地说,这才是具体意义上的类型。类型系统给了我们一些表示这些集合的更强大的方式。
有了这些类型标识,编译器现在可以静态地(在编译期)判断这个程序是正确的。换句话说,如果一个值(在运行期)不能够满足程序里的限制条件的话,那么在编译期就会出错。
通常来说,类型检测器(typechecker)只能够保证不正确的的程序不能通过编译。但是,它不能够保证所有正确的程序都能通过编译。
由于类型系统的表达能力不断增加,使得我们能够生成出更加可靠的代码,因为它使得我们能够控制程序上的不可变,即是是程序还没有运行的情况下(在类型上限制bug的出现)。学术界一直在努力突破类型系统的表达能力的极限,包含值相关的类型。
注意,所有的类型信息都会在编译期擦除。后面不再需要。这个被称为类型擦除。
Scala中的类型
Scala强大的类型系统让我们可以使用更具有表现力的表达式。一些主要的特点如下:
支持参数多态,泛型编程
支持(局部)类型推导,这就是你为什么不需要写
val i: Int = 12: Int
支持存在向量(existential quantification),给一些没有名称的类型定义一些操作
支持视图。 我们下个星期再讨论;给定的值从一个类型到其他类型的“可转换性”
参数多态
多态可以用来编写泛型代码(用于处理不同类型的值),并且不会减少静态类型的表达能力。
例如,没有参数多态的话,一个泛型的列表数据结构通常会是下面这样的写法(在Java还没有泛型的时候,确实是这样的):
这样的话,我们就不能够恢复每个元素的类型信息。
这样一来,我们的应用里会包含一系列的类型转换(“asInstanceOf[]“),代码会缺少类型安全(因为他们都是动态类型的)。
多态是通过指定类型变量来达到的。
多态是scala里的一等公民
简单来说,这意味着有一些你想在Scala里表达的类型概念会显得“太过于泛型”,从而导致编译器无法理解。假如你有这样一个函数:
你想要按照泛型的方式来使用它:
但是这样会编译不过,因为所有的类型变量在运行期必须是确定的。
…you get a type mismatch.
…你会看到一个类型不匹配的错误
类型推导
对于静态类型的一个比较常见的缺陷就是有太多的类型语法。Scala提供了类型推导来解决这个问题。
函数式语言里比较经典的类型推导的方法是 Hindlry-Milner,并且它是在ML里首先使用的。
Scala的类型推导有一点点不同,不过思想上是一致的:推导所有的约束条件,然后统一到一个类型上。
在Scala里,例如,你不能这样写:
但是在OCaml里,你可以:
在Scala里,所有的类型推导都是局部的。Scala一次只考虑一个表达式。例如:
在这里,类型都被隐藏了。Scala编译器自动推导参数的类型。注意我们也没有必要显示指定返回值的类型了。
Varicace
Scala的类型系统需要把类的继承关系和多态结合起来。类的继承使得类之间存在父子的关系。当把面向对象和多态结合在一起时,一个核心的问题就出来了:如果T'是T的子类,那么Container[T']是不是Container[T]的子类呢?Variance注释允许你在类继承和多态类型之间表达下面的这些关系:
子类关系的真正意思是:对于一个给定的类型T,如果T’是它的子类,那么T’可以代替T吗?
逆变(Contravariance)看起来比较奇怪。什么时候要用到它呢?确实让人感到惊讶!
如果你从替代的角度来看这个的话,非常容易理解这一点。我们首先来定义一个简单的类继承关系:
假设你需要一个接收
Bird
类型参数的函数:
标准的animal类库有一个函数可以完成你想要的任务,但是它的参数是
Animal
。在大部分的场景下,如果你说“我需要一个,我有一个的子类”,这样是可以的。但是函数的参数都是可逆变的(contravariant),如果你需要一个接受一个
Bird
的函数,并且你有一个接收一个
Chicken
的函数,这个函数会卡在
Duck
上。但是一个接收
Animal
作为参数的函数就没有问题:
函数的返回值是可逆变的。如果你需要一个返回
Bird
的函数,但是你只有一个返回
Chicken
的函数,这样也是可以的。
范围(Bounds)
Scala允许你通过 bounds 来限制多态的范围。这些范围表示的是子类的关系。
还可以支持更低的类型范围;它们可以通过逆变(contravariance)和合适的协变(covariance)来实现。
List[+T]
是协变量;一个Bird的列表同时也是一个Animal的列表。
List
定义了一个操作符
::[elem T]
返回一个装载
elem
的列表。这个新的
List
和原来的列表有着相同的类型:
List
同时 也定义了
::[B >: T]
,它返回一个
List[B]
。注意
,它表示
T
的父类。这个会在我们把一个
Animal
添加到一个
List[Bird]
的时候提醒我们进行纠正。
注意返回的类型是
Animal
。
量化(Quantification)
有时候你不需要给一个类型变量以名称,例如
你可以用“通配符”来替代:
这个可以替代:
注意量化(quantification)可能会显得比较诡异:
突然之间我们丢失了类型信息!为了一探究竟,我们来看看最原始的语法:
我们不能说明T的任何信息,因为在这里的类型不允许。
你也可以对通配符类型使用范围来进行限定:
领取专属 10元无门槛券
私享最新 技术干货