类型和泛型
类型和泛型
类型系统的首要目的是检测程序错误。类型系统有效的提供了一个静态检测的有限形式,允许我们代码中明确某种类型的变量并且编译器可以验证。类型系统当然也提供了其他好处,但错误检测是他存在的理由(Raison d’Être)
我们使用类型系统应当反映这一目标,但我们必须考虑到读者(译注:读你代码的人):明智地使用类型可以增加清晰度,而过份聪明只会迷乱。
Scala的强大类型系统是学术探索和实践共同来源(例如Type level programming in Scala) 。但这是一个迷人的学术话题,这些技术很少在应用和正式产品代码中使用。它们应该被避免。
在使用混入(mixin)实例化对象时这一点尤其重要,Scala编译器为这些对象创造了单类。例如:
trait Service
def make() = new Service {
def getId = 123
}
上面的make不需要定义返回类型为Service;编译器会创建一个加工过的类型: Object with Service{def getId:Int}(译注:with是Scala里的mixin的语法)。若用一个显式的注释:
def make(): Service = new Service{}
现在作者则不必改变make方法的公开类型而随意的混入(mix in) 更多的特质(traits),使向后兼容很容易实现。
变型
变型(Variance)发生在泛型与子类型化(subtyping)结合的时候。与容器类型的子类型化有关,它们定义了对所包含的类型如何子类型化。因为Scala有声明点变型(declaration site variance)注释(annotation),公共库的作者——特别是集合——必须有丰富的注释器。这些注释对共享代码的可用性很重要,但滥用也会很危险。
不可变(invariants)是Scala类型系统中高级部分,但也是必须的一面,因为它有助于子类型化的应用,应该广泛(并且正确)地使用。
不可变(Immutable)集合应该是协变的(covariant)。接受容器化类型得方法应该适当地降级(downgrade)集合:
trait Collection[+T] {
def add[U >: T](other: U): Collection[U]
}
可变(mutable)集合应该是不可变的(invariant). 协变对于可变集合是典型无效的。考虑:
trait HashSet[+T] {
def add[U >: T](item: U)
}
和下面的类型层级:
trait Mammal
trait Dog extends Mammal
trait Cat extends Mammal
如果我现在有一个狗(dog)的 HashSet:
val dogs: HashSet[Dog]
把它作为一个哺乳动物的Set,增加一只猫(cat)
val mammals: HashSet[Mammal] = dogs
mammals.add(new Cat{})
这将不再是一个只存储狗(dog)的HashSet!
类型别名
类型别名应当在其提供了便捷的命名或阐明意图时使用,但对于自解释(不言自明)的类型不要使用类型别名。比如
() => Int
比下面定义的别名IntMarker更清晰
type IntMaker = () => Int
IntMaker
但,下面的别名:
class ConcurrentPool[K, V] {
type Queue = ConcurrentLinkedQueue[V]
type Map = ConcurrentHashMap[K, Queue]
...
}
是有用的,因为它表达了目的并更加简短。
当使用类型别名的时候不要使用子类型化(subtyping)
trait SocketFactory extends (SocketAddress => Socket)
SocketFactory 是一个生产Socket的方法。使用一个类型别名更好:
type SocketFactory = SocketAddress => Socket
我们现在可以对 SocketFactory类型的值 提供函数字面量(function literals) ,也可以使用函数组合:
val addrToInet: SocketAddress => Long
val inetToSocket: Long => Socket
val factory: SocketFactory = addrToInet andThen inetToSocket
类型别名通过用 package object 将名字绑定在顶层:
package com.twitter
package object net {
type SocketFactory = (SocketAddress) => Socket
}
注意类型别名不是新类型——他们等价于在语法上用别名代替了原类型。
隐式转换
隐式转换是类型系统里一个强大的功能,但应当谨慎地使用。它们有复杂的解决规则, 使得通过简单的词法检查领会实际发生了什么很困难。在下面的场景使用隐式转换是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则相反。