這里我們轉(zhuǎn)載 Twitter 的 Scala 課堂 ,轉(zhuǎn)載的內(nèi)容基本來自 Twitter 的 Scala 課堂中文翻譯,部分有小改動(dòng).
Scala 可以對(duì)“更高階”的類型進(jìn)行抽象。例如,假設(shè)您需要用幾種類型的容器處理幾種類型的數(shù)據(jù)。你可能定義了一個(gè) Container 的接口,它可以被實(shí)現(xiàn)為幾種類型的容器:Option、List 等。你要定義可以使用這些容器里的值的接口,但不想確定值的類型。 這類似與函數(shù)柯里化。例如,盡管“一元類型”有類似 List[A] 的構(gòu)造函數(shù),這意味著我們必須滿足一個(gè)“級(jí)別”的類型變量來產(chǎn)生一個(gè)具體的類型(就像一個(gè)沒有柯里化的函數(shù)需要只提供一個(gè)參數(shù)列表來被調(diào)用),更高階的類型需要更多。
scala> trait Container[M[_]] { def put[A](x: A): M[A]; def get[A](m: M[A]): A }
defined trait Container
scala> val container = new Container[List] { def put[A](x: A) = List(x); def get[A](m: List[A]) = m.head }
container: Container[List] = $anon$1@434013d9
scala> container.put("hey")
res1: List[String] = List(hey)
scala> container.put(123)
res2: List[Int] = List(123)
注意: Container 是參數(shù)化類型的多態(tài)(“容器類型”)。 如果我們結(jié)合隱式轉(zhuǎn)換 implicits 使用容器,我們會(huì)得到“特設(shè)的”多態(tài)性:即對(duì)容器寫泛型函數(shù)的能力。
scala> trait Container[M[_]] { def put[A](x: A): M[A]; def get[A](m: M[A]): A }
defined trait Container
scala> implicit val listContainer = new Container[List] { def put[A](x: A) = List(x); def get[A](m: List[A]) = m.head }
listContainer: Container[List] = $anon$1@54f3d86c
scala> implicit val optionContainer = new Container[Some] { def put[A](x: A) = Some(x); def get[A](m: Some[A]) = m.get }
optionContainer: Container[Some] = $anon$1@591287f8
scala> :paste
// Entering paste mode (ctrl-D to finish)
def tupleize[M[_]: Container, A, B](fst: M[A], snd: M[B]) = {
val c = implicitly[Container[M]]
c.put(c.get(fst), c.get(snd))
}
// Exiting paste mode, now interpreting.
tupleize: [M[_], A, B](fst: M[A], snd: M[B])(implicit evidence$1: Container[M])M[(A, B)]
scala> tupleize(Some(1), Some(2))
res0: Some[(Int, Int)] = Some((1,2))
scala> tupleize(List(1), List(2))
res1: List[(Int, Int)] = List((1,2))
通常有必要來訪問一個(gè)(泛型)特質(zhì)的具體子類。例如,想象你有一些泛型特質(zhì),但需要可以與它的某一子類進(jìn)行比較。
trait Container extends Ordered[Container]
然而,現(xiàn)在比較方法是必須的了
def compare(that: Container): Int
因此,我們不能訪問具體子類型,例如
class MyContainer extends Container {
def compare(that: MyContainer): Int
}
編譯失敗,因?yàn)槲覀儗?duì) Container 指定了 Ordered 特質(zhì),而不是對(duì)特定子類型指定的。 為了調(diào)和這一點(diǎn),我們改用 F-界的多態(tài)性。
trait Container[A <: Container[A]] extends Ordered[A]
奇怪的類型!但可以看到怎樣對(duì) A 實(shí)現(xiàn)了 Ordered 參數(shù)化,它本身就是 Container[A]
class MyContainer extends Container[MyContainer] {
def compare(that: MyContainer) = 0
}
他們是有序的了:
scala> List(new MyContainer, new MyContainer, new MyContainer)
res0: List[MyContainer] = List(MyContainer@50e205df, MyContainer@26ef9cf5, MyContainer@3d29accb)
scala> List(new MyContainer, new MyContainer, new MyContainer).min
res1: MyContainer = MyContainer@622e8c17
鑒于他們都是 Container[] 的子類型,我們可以定義另一個(gè)子類并創(chuàng)建 Container[] 的一個(gè)混合列表:
scala> class YourContainer extends Container[YourContainer] { def compare(that: YourContainer) = 0 }
defined class YourContainer
scala> List(new MyContainer, new MyContainer, new MyContainer, new YourContainer)
res2: List[Container[_ >: YourContainer with MyContainer <: Container[_ >: YourContainer with MyContainer <: Object]]] = List(MyContainer@58f59add, MyContainer@648a50cb, MyContainer@34be72fe, YourContainer@436f9cbf)
注意結(jié)果類型是怎樣成為 YourContainer 和 MyContainer 類型確定的下界。這是類型推斷的工作。有趣的是,這種類型甚至不需要是有意義的,它只是提供了一個(gè)合乎邏輯的最大下界為列表的統(tǒng)一類型。如果現(xiàn)在我們嘗試使用 Ordered 會(huì)發(fā)生什么?
scala> (new MyContainer, new MyContainer, new MyContainer, new YourContainer).min
<console>:11: error: value min is not a member of (MyContainer, MyContainer, MyContainer, YourContainer)
(new MyContainer, new MyContainer, new MyContainer, new YourContainer).min
對(duì)統(tǒng)一的類型 Ordered[]不存在了。太糟糕了。
Scala 支持 結(jié)構(gòu)類型 structural types — 類型需求由接口 構(gòu)造 表示,而不是由具體的類型表示。
scala> def foo(x: { def get: Int }) = 123 + x.get
foo: (x: AnyRef{def get: Int})Int
scala> foo(new { def get = 10 })
res4: Int = 133
在特質(zhì)中,你可以讓類型成員保持抽象。
scala> trait Foo { type A; val x: A; def getX: A = x }
defined trait Foo
scala> (new Foo { type A = Int; val x = 123 }).getX
res5: Int = 123
scala> (new Foo { type A = String; val x = "hey" }).getX
res6: String = hey
在做依賴注入等情況下,這往往是一個(gè)有用的技巧。
您可以使用 hash 操作符來引用一個(gè)抽象類型的變量:
scala> trait Foo[M[_]] { type t[A] = M[A] }
defined trait Foo
scala> val x: Foo[List]#t[Int] = List(1)
x: List[Int] = List(1)
正如我們所知道的,類型信息在編譯的時(shí)候會(huì)因?yàn)?擦除 而丟失。 Scala 的 清單(Manifests) 功能,使我們能夠選擇性地恢復(fù)類型信息。清單提供了一個(gè)隱含值,根據(jù)需要由編譯器生成。
scala> class MakeFoo[A](implicit manifest: Manifest[A]) { def make: A = manifest.erasure.newInstance.asInstanceOf[A] }
defined class MakeFoo
scala> (new MakeFoo[String]).make
res7: String = ""