Scala教程:类型基础

英文原文: Scala School,翻译:ImportNew - 朱伟杰

这一节的内容包含:

什么是静态类型?为什么它们很有用?

根据Picrce的说法:“类型系统是一个可以根据代码段计算出来的值对它们进行分类,然后通过语法的手段来自动检测程序错误的系统。”

类型可以让你表示函数的域和值域。例如,在数学里,我们经常看到下面的函数:

f: R -> N

这个定义告诉我们函数”f”的作用是把实数集里的数映射到自然集里。

抽象地说,这才是具体意义上的类型。类型系统给了我们一些表示这些集合的更强大的方式。

有了这些类型标识,编译器现在可以 静态地(在编译期)判断这个程序是正确的。换句话说,如果一个值(在运行期)不能够满足程序里的限制条件的话,那么在编译期就会出错。

通常来说,类型检测器(typechecker)只能够保证不正确的的程序不能通过编译。但是,它不能够保证所有正确的程序都能通过编译。

由于类型系统的表达能力不断增加,使得我们能够生成出更加可靠的代码,因为它使得我们能够控制程序上的不可变,即是是程序还没有运行的情况下(在类型上限制bug的出现)。学术界一直在努力突破类型系统的表达能力的极限,包含值相关的类型。

注意,所有的类型信息都会在编译期擦除。后面不再需要。这个被称为类型擦除。

 

Scala中的类型

Scala强大的类型系统让我们可以使用更具有表现力的表达式。一些主要的特点如下:

  • 支持参数多态,泛型编程
  • 支持(局部)类型推导,这就是你为什么不需要写val i: Int = 12: Int
  • 支持存在向量(existential quantification),给一些没有名称的类型定义一些操作
  • 支持视图。 我们下个星期再讨论;给定的值从一个类型到其他类型的“可转换性”

 

参数多态

多态可以用来编写泛型代码(用于处理不同类型的值),并且不会减少静态类型的表达能力。

例如,没有参数多态的话,一个泛型的列表数据结构通常会是下面这样的写法(在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里的一等公民

简单来说,这意味着有一些你想在Scala里表达的类型概念会显得“太过于泛型”,从而导致编译器无法理解。假如你有这样一个函数:

def toList[A](a: A) = List(a)

你想要按照泛型的方式来使用它:

def foo[A, B](f: A => List[A], b: B) = f(b)

但是这样会编译不过,因为所有的类型变量在运行期必须是确定的。

def foo[A](f: A => List[A], i: Int) = f(i)

…you get a type mismatch.

…你会看到一个类型不匹配的错误

 

类型推导

对于静态类型的一个比较常见的缺陷就是有太多的类型语法。Scala提供了类型推导来解决这个问题。

函数式语言里比较经典的类型推导的方法是 Hindlry-Milner,并且它是在ML里首先使用的。

Scala的类型推导有一点点不同,不过思想上是一致的:推导所有的约束条件,然后统一到一个类型上。

在Scala里,例如,你不能这样写:

scala> { x => x }
:7: error: missing parameter type
       { x => x }

但是在OCaml里,你可以:

# fun x -> x;;
- : 'a -> 'a =

在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编译器自动推导参数的类型。注意我们也没有必要显示指定返回值的类型了。

 

Varicace

Scala的类型系统需要把类的继承关系和多态结合起来。类的继承使得类之间存在父子的关系。当把面向对象和多态结合在一起时,一个核心的问题就出来了:如果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’是它的子类,那么T’可以代替T吗?

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]
:6: error: type mismatch;
 found   : Contravariant[String]
 required: Contravariant[AnyRef]
       val fail: Contravariant[AnyRef] = new Contravariant[String]                                     ^

逆变(Contravariance)看起来比较奇怪。什么时候要用到它呢?确实让人感到惊讶!

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类库有一个函数可以完成你想要的任务,但是它的参数是Animal。在大部分的场景下,如果你说“我需要一个,我有一个的子类”,这样是可以的。但是函数的参数都是可逆变的(contravariant),如果你需要一个接受一个Bird的函数,并且你有一个接收一个Chicken的函数,这个函数会卡在Duck上。但是一个接收Animal作为参数的函数就没有问题:

scala> val getTweet: (Bird => String) = ((a: Animal) => a.sound )
getTweet: Bird => String =

函数的返回值是可逆变的。如果你需要一个返回Bird的函数,但是你只有一个返回Chicken的函数,这样也是可以的。

scala> val hatch: (() => Bird) = (() => new Chicken )
hatch: () => Bird =

范围(Bounds)

Scala允许你通过 bounds 来限制多态的范围。这些范围表示的是子类的关系。

scala> def cacophony[T](things: Seq[T]) = things map (_.sound)
: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)

还可以支持更低的类型范围;它们可以通过逆变(contravariance)和合适的协变(covariance)来实现。List[+T]是协变量;一个Bird的列表同时也是一个Animal的列表。List定义了一个操作符::[elem T]返回一个装载elem的列表。这个新的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],它返回一个List[B]。注意B >: T,它表示T的父类。这个会在我们把一个Animal添加到一个List[Bird]的时候提醒我们进行纠正。

scala> new Animal :: flock
res59: List[Animal] = List(Animal@11f8d3a8, Bird@7e1ec70e, Bird@169ea8d2)

注意返回的类型是Animal

 

量化(Quantification)

有时候你不需要给一个类型变量以名称,例如

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

注意量化(quantification)可能会显得比较诡异:

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))
: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中的存在类型

 

英文原文: Scala School,翻译:ImportNew - 朱伟杰

译文链接:http://www.importnew.com/4126.html

【如需转载,请在正文中标注并保留原文链接、译文链接和译者等信息,谢谢合作!

关于作者: 朱伟杰

Java开发工程师,业余翻译

查看朱伟杰的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部