scala学习指南:第二部分类型和泛型

 

明智地使用类型可以增加清晰度,而过份聪明只会迷乱...



原文链接:http://twitter.github.io/effectivescala/index-cn.html

Twitter的工程师出品(由小黄狗整理转载)

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

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

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

1        返回类型注解(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),使向后兼容很容易实现。

2        变型

变型(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)

}

和下面的类型层级:

traitMammal

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!

3        类型别名

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

() => 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 = addrToInetandThen inetToSocket

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

package com.twitter  package object net {

type SocketFactory = (SocketAddress) =>Socket

}

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

4        隐式转换

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

  • 扩展或增加一个Scala风格的集合
  • 适配或扩展一个对象(pimp my library模式)(译注参见:http://www.artima.com/weblogs/viewpost.jspthread=179766)
  • 通过提供约束证据来加强类型安全。
  • 提供了类型的证据 (typeclassing,haskell中的概念,指定义一组函数,其实现因所给的数据类型不同而不同)
  • 用于Manifests (注:Manifest[T]包含类型T的运行时信息)
如果你发现自己在用隐式转换,总要问问自己是否不使用这种方式也可以达到目的。

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


    关注 Scala开发者


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册