企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
类型系统的首要目的是检测程序错误。类型系统有效的提供了一个静态检测的有限形式,允许我们代码中明确某种类型的变量并且编译器可以验证。类型系统当然也提供了其他好处,但错误检测是他存在的理由(Raison d’Être) 我们使用类型系统应当反映这一目标,但我们必须考虑到读者(译注:读你代码的人):明智地使用类型可以增加清晰度,而过份聪明只会迷乱。 Scala的强大类型系统是学术探索和实践共同来源(例如[Type level programming in Scala](http://apocalisp.wordpress.com/2010/06/08/type-level-programming-in-scala/)) 。但这是一个迷人的学术话题,这些技术很少在应用和正式产品代码中使用。它们应该被避免。 ### 返回类型注解(annotation) 尽管Scala允许返回类型是可以省略的,加上它们提供了很好的文档:这对public方法特别重要。而当一个方法不需要对外暴露,并且它的返回值类型是显而易见的时候,则可以直接省略。 在使用混入(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风格的集合 * 适配或扩展一个对象(pimp my library模式)(译注参见: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则相反。