Scala课堂(14):高级类型(二)

jerry Scala 2015年11月25日 收藏

这里我们转载Twitter的Scala课堂  ,转载的内容基本来自Twitter的Scala课堂中文翻译,部分有小改动.
更高级多态性类型 和 特设多态性
Scala可以对“更高阶”的类型进行抽象。例如,假设您需要用几种类型的容器处理几种类型的数据。你可能定义了一个Container的接口,它可以被实现为几种类型的容器:Option、List等。你要定义可以使用这些容器里的值的接口,但不想确定值的类型。
这类似与函数柯里化。例如,尽管“一元类型”有类似List[A]的构造函数,这意味着我们必须满足一个“级别”的类型变量来产生一个具体的类型(就像一个没有柯里化的函数需要只提供一个参数列表来被调用),更高阶的类型需要更多。

  1. scala> trait Container[M[_]] { def put[A](x: A): M[A]; def get[A](m: M[A]): A }
  2. defined trait Container
  3.  
  4. scala> val container = new Container[List] { def put[A](x: A) = List(x); def get[A](m: List[A]) = m.head }
  5. container: Container[List] = $anon$1@434013d9
  6.  
  7. scala> container.put("hey")
  8. res1: List[String] = List(hey)
  9.  
  10. scala> container.put(123)
  11. res2: List[Int] = List(123)
  12.  

注意:*Container*是参数化类型的多态(“容器类型”)。
如果我们结合隐式转换implicits使用容器,我们会得到“特设的”多态性:即对容器写泛型函数的能力。

  1. scala> trait Container[M[_]] { def put[A](x: A): M[A]; def get[A](m: M[A]): A }
  2. defined trait Container
  3.  
  4. scala> implicit val listContainer = new Container[List] { def put[A](x: A) = List(x); def get[A](m: List[A]) = m.head }
  5. listContainer: Container[List] = $anon$1@54f3d86c
  6.  
  7. scala> implicit val optionContainer = new Container[Some] { def put[A](x: A) = Some(x); def get[A](m: Some[A]) = m.get }
  8. optionContainer: Container[Some] = $anon$1@591287f8
  9.  
  10. scala> :paste
  11. // Entering paste mode (ctrl-D to finish)
  12.  
  13. def tupleize[M[_]: Container, A, B](fst: M[A], snd: M[B]) = {
  14. val c = implicitly[Container[M]]
  15. c.put(c.get(fst), c.get(snd))
  16. }
  17.  
  18. // Exiting paste mode, now interpreting.
  19.  
  20. tupleize: [M[_], A, B](fst: M[A], snd: M[B])(implicit evidence$1: Container[M])M[(A, B)]
  21.  
  22. scala> tupleize(Some(1), Some(2))
  23. res0: Some[(Int, Int)] = Some((1,2))
  24.  
  25. scala> tupleize(List(1), List(2))
  26. res1: List[(Int, Int)] = List((1,2))
  27.  

F-界多态性

通常有必要来访问一个(泛型)特质的具体子类。例如,想象你有一些泛型特质,但需要可以与它的某一子类进行比较。

  1. trait Container extends Ordered[Container]

然而,现在比较方法是必须的了

  1. def compare(that: Container): Int

因此,我们不能访问具体子类型,例如

  1. class MyContainer extends Container {
  2. def compare(that: MyContainer): Int
  3. }

编译失败,因为我们对 Container 指定了Ordered特质,而不是对特定子类型指定的。
为了调和这一点,我们改用F-界的多态性。

  1. trait Container[A <: Container[A]] extends Ordered[A]

奇怪的类型!但可以看到怎样对 A 实现了Ordered参数化,它本身就是 Container[A]

  1. class MyContainer extends Container[MyContainer] {
  2. def compare(that: MyContainer) = 0
  3. }

他们是有序的了:

  1. scala> List(new MyContainer, new MyContainer, new MyContainer)
  2. res0: List[MyContainer] = List(MyContainer@50e205df, MyContainer@26ef9cf5, MyContainer@3d29accb)
  3.  
  4. scala> List(new MyContainer, new MyContainer, new MyContainer).min
  5. res1: MyContainer = MyContainer@622e8c17
  6.  

鉴于他们都是 Container[_] 的子类型,我们可以定义另一个子类并创建 Container[_] 的一个混合列表:

  1. scala> class YourContainer extends Container[YourContainer] { def compare(that: YourContainer) = 0 }
  2. defined class YourContainer
  3.  
  4. scala> List(new MyContainer, new MyContainer, new MyContainer, new YourContainer)
  5. res2: List[Container[_ >: YourContainer with MyContainer <: Container[_ >: YourContainer with MyContainer <: Object]]] = List(MyContainer@58f59add, MyContainer@648a50cb, MyContainer@34be72fe, YourContainer@436f9cbf)
  6.  

注意结果类型是怎样成为 YourContainer 和 MyContainer 类型确定的下界。这是类型推断的工作。有趣的是,这种类型甚至不需要是有意义的,它只是提供了一个合乎逻辑的最大下界为列表的统一类型。如果现在我们尝试使用 Ordered 会发生什么?

  1. scala> (new MyContainer, new MyContainer, new MyContainer, new YourContainer).min
  2. <console>:11: error: value min is not a member of (MyContainer, MyContainer, MyContainer, YourContainer)
  3. (new MyContainer, new MyContainer, new MyContainer, new YourContainer).min
  4.  

对统一的类型 Ordered[]不存在了。太糟糕了。

结构类型
Scala 支持 结构类型 structural types — 类型需求由接口 构造 表示,而不是由具体的类型表示。

  1. scala> def foo(x: { def get: Int }) = 123 + x.get
  2. foo: (x: AnyRef{def get: Int})Int
  3.  
  4. scala> foo(new { def get = 10 })
  5. res4: Int = 133
  6.  

抽象类型成员
在特质中,你可以让类型成员保持抽象。

  1. scala> trait Foo { type A; val x: A; def getX: A = x }
  2. defined trait Foo
  3.  
  4. scala> (new Foo { type A = Int; val x = 123 }).getX
  5. res5: Int = 123
  6.  
  7. scala> (new Foo { type A = String; val x = "hey" }).getX
  8. res6: String = hey
  9.  

在做依赖注入等情况下,这往往是一个有用的技巧。

您可以使用hash操作符来引用一个抽象类型的变量:

  1. scala> trait Foo[M[_]] { type t[A] = M[A] }
  2. defined trait Foo
  3.  
  4. scala> val x: Foo[List]#t[Int] = List(1)
  5. x: List[Int] = List(1)
  6.  

类型擦除和清单
正如我们所知道的,类型信息在编译的时候会因为 擦除 而丢失。 Scala的 清单(Manifests) 功能,使我们能够选择性地恢复类型信息。清单提供了一个隐含值,根据需要由编译器生成。

  1. scala> class MakeFoo[A](implicit manifest: Manifest[A]) { def make: A = manifest.erasure.newInstance.asInstanceOf[A] }
  2. defined class MakeFoo
  3.  
  4. scala> (new MakeFoo[String]).make
  5. res7: String = ""
  6.