类型和泛型

优质
小牛编辑
132浏览
2023-12-01

类型和泛型

类型系统的首要目的是检测程序错误。类型系统有效的提供了一个静态检测的有限形式,允许我们代码中明确某种类型的变量并且编译器可以验证。类型系统当然也提供了其他好处,但错误检测是他存在的理由(Raison d’Être)

我们使用类型系统应当反映这一目标,但我们必须考虑到读者(译注:读你代码的人):明智地使用类型可以增加清晰度,而过份聪明只会迷乱。

Scala的强大类型系统是学术探索和实践共同来源(例如Type level programming in Scala) 。但这是一个迷人的学术话题,这些技术很少在应用和正式产品代码中使用。它们应该被避免。

在使用混入(mixin)实例化对象时这一点尤其重要,Scala编译器为这些对象创造了单类。例如:

  1. trait Service
  2. def make() = new Service {
  3. def getId = 123
  4. }

上面的make不需要定义返回类型为Service;编译器会创建一个加工过的类型: Object with Service{def getId:Int}(译注:with是Scala里的mixin的语法)。若用一个显式的注释:

  1. def make(): Service = new Service{}

现在作者则不必改变make方法的公开类型而随意的混入(mix in) 更多的特质(traits),使向后兼容很容易实现。

变型

变型(Variance)发生在泛型与子类型化(subtyping)结合的时候。与容器类型的子类型化有关,它们定义了对所包含的类型如何子类型化。因为Scala有声明点变型(declaration site variance)注释(annotation),公共库的作者——特别是集合——必须有丰富的注释器。这些注释对共享代码的可用性很重要,但滥用也会很危险。

不可变(invariants)是Scala类型系统中高级部分,但也是必须的一面,因为它有助于子类型化的应用,应该广泛(并且正确)地使用。

不可变(Immutable)集合应该是协变的(covariant)。接受容器化类型得方法应该适当地降级(downgrade)集合:

  1. trait Collection[+T] {
  2. def add[U >: T](other: U): Collection[U]
  3. }

可变(mutable)集合应该是不可变的(invariant). 协变对于可变集合是典型无效的。考虑:

  1. trait HashSet[+T] {
  2. def add[U >: T](item: U)
  3. }

和下面的类型层级:

  1. trait Mammal
  2. trait Dog extends Mammal
  3. trait Cat extends Mammal

如果我现在有一个狗(dog)的 HashSet:

  1. val dogs: HashSet[Dog]

把它作为一个哺乳动物的Set,增加一只猫(cat)

  1. val mammals: HashSet[Mammal] = dogs
  2. mammals.add(new Cat{})

这将不再是一个只存储狗(dog)的HashSet!



类型别名

类型别名应当在其提供了便捷的命名或阐明意图时使用,但对于自解释(不言自明)的类型不要使用类型别名。比如

  1. () => Int

比下面定义的别名IntMarker更清晰

  1. type IntMaker = () => Int
  2. IntMaker

但,下面的别名:

  1. class ConcurrentPool[K, V] {
  2. type Queue = ConcurrentLinkedQueue[V]
  3. type Map = ConcurrentHashMap[K, Queue]
  4. ...
  5. }

是有用的,因为它表达了目的并更加简短。

当使用类型别名的时候不要使用子类型化(subtyping)

  1. trait SocketFactory extends (SocketAddress => Socket)

SocketFactory 是一个生产Socket的方法。使用一个类型别名更好:

  1. type SocketFactory = SocketAddress => Socket

我们现在可以对 SocketFactory类型的值 提供函数字面量(function literals) ,也可以使用函数组合:

  1. val addrToInet: SocketAddress => Long
  2. val inetToSocket: Long => Socket
  3. val factory: SocketFactory = addrToInet andThen inetToSocket

类型别名通过用 package object 将名字绑定在顶层:

  1. package com.twitter
  2. package object net {
  3. type SocketFactory = (SocketAddress) => Socket
  4. }

注意类型别名不是新类型——他们等价于在语法上用别名代替了原类型。

隐式转换

隐式转换是类型系统里一个强大的功能,但应当谨慎地使用。它们有复杂的解决规则, 使得通过简单的词法检查领会实际发生了什么很困难。在下面的场景使用隐式转换是OK的:

  • 扩展或增加一个Scala风格的集合
  • 适配或扩展一个对象(pimpmylibrary模式)(译注参见:http://www.artima.com/weblogs/viewpost.jsp?thread=179766)
  • 通过提供约束证据来加强类型安全。
  • 提供了类型的证据(typeclassing,haskell中的概念,指定义一组函数,其实现因所给的数据类型不同而不同)
  • 用于Manifests(注:Manifest[T]包含类型T的运行时信息)

如果你发现自己在用隐式转换,总要问问自己是否不使用这种方式也可以达到目的。

不要使用隐式转换对两个相似的数据类型做自动转换(例如,把list转换为stream);显示地做更好,因为不同类型有不同的语意,读者应该意识到这些含义。
译注: 1)一些单词的意义不同,但翻译为中文时可能用的相似的词语,比如mutable, Immutable 这两个翻译为可变和不可变,它们是指数据的可变与不可变。
variance, invariant 也翻译为 可变和不可变,(variance也翻译为“变型”),它们是指类型的可变与不可变。variance指支持协变或逆变的类型,invariant则相反。