国产gaysexchina男同gay,japanrcep老熟妇乱子伦视频,吃奶呻吟打开双腿做受动态图,成人色网站,国产av一区二区三区最新精品

類(lèi)型類(lèi)

2018-02-24 16:00 更新

前兩章討論了幾種保持 DRY 和靈活性的函數(shù)式編程技術(shù):

  1. 函數(shù)組合(function composition)
  2. 部分函數(shù)應(yīng)用(partial function application)
  3. 柯里化(currying)

這一章依舊圍繞代碼靈活性而來(lái),不過(guò)不再討論作為頭等公民的函數(shù),而是類(lèi)型系統(tǒng)(注意:并不是要真的去研究類(lèi)型系統(tǒng))。你將學(xué)習(xí) 類(lèi)型類(lèi) !

可能你會(huì)覺(jué)得這沒(méi)有實(shí)際意義,認(rèn)為這是被 Haskell 狂熱分子帶入 Scala 社區(qū)的異國(guó)情調(diào),顯然不是這樣。類(lèi)型類(lèi)已經(jīng)成為 Scala 標(biāo)準(zhǔn)庫(kù),甚至是很多流行的、廣泛使用的第三方開(kāi)源庫(kù)的重要組成部分,了解和熟悉類(lèi)型類(lèi)是很有必要的。

本章會(huì)討論:

  1. 類(lèi)型類(lèi)的概念,
  2. 它為什么有用,
  3. 使用它如何受益,
  4. 如何實(shí)現(xiàn)類(lèi)型類(lèi),并用于實(shí)踐。

問(wèn)題

我們用例子,而不是一個(gè)對(duì)類(lèi)型類(lèi)的抽象解釋?zhuān)_(kāi)始本文的主題,例子簡(jiǎn)化了概念,也相當(dāng)實(shí)用。

假設(shè)想提供一系列可以操作數(shù)字集合的函數(shù),主要是計(jì)算它們的聚合值。進(jìn)一步假設(shè)只能通過(guò)索引來(lái)訪問(wèn)集合的元素,只能使用定義在 Scala 集合上的 reduce 方法。(施加這些限制,是因?yàn)橐獙?shí)現(xiàn)的東西,Scala 標(biāo)準(zhǔn)庫(kù)已經(jīng)提供了)最后,假定得到的值已排序。

我們先從 median , quartilesiqr 的一個(gè)粗暴實(shí)現(xiàn)開(kāi)始:

    object Statistics {
      def median(xs: Vector[Double]): Double = xs(xs.size / 2)
      def quartiles(xs: Vector[Double]): (Double, Double, Double) =
        (xs(xs.size / 4), median(xs), xs(xs.size / 4 * 3))
      def iqr(xs: Vector[Double]): Double = quartiles(xs) match {
        case (lowerQuartile, _, upperQuartile) => upperQuartile - lowerQuartile
      }
      def mean(xs: Vector[Double]): Double = {
        xs.reduce(_ + _) / xs.size
      }
    }

median 將數(shù)據(jù)集分成兩半,下四分位數(shù)和上四分位數(shù)( quartiles 方法返回的元組的第一、第三個(gè)元素)分別分割了數(shù)據(jù)集的 25% 。iqr 方法返回四分差(上四分衛(wèi)數(shù)和下四分位數(shù)的差)。

現(xiàn)在我們想支持更多的類(lèi)型,比如,Int,所以應(yīng)該為這個(gè)類(lèi)型實(shí)現(xiàn)上面這些方法,對(duì)吧?

不!不能想當(dāng)然的為 Vector[Int] 重載上面的方法(詭異的技巧除外),因?yàn)轭?lèi)型參數(shù)會(huì)被擦除,而且這樣做有代碼冗余的嫌疑。

要是 IntDouble 擴(kuò)展自一個(gè)共同的基類(lèi),或者都實(shí)現(xiàn)了一個(gè)像是 Number 這樣的特質(zhì),那該多好!

你可能會(huì)想著去把上述方法需要的參數(shù)類(lèi)型替換成更通用的類(lèi)型,看起來(lái)會(huì)是這樣:

    object Statistics {
      def median(xs: Vector[Number]): Number = ???
      def quartiles(xs: Vector[Number]): (Number, Number, Number) = ???
      def iqr(xs: Vector[Number]): Number = ???
      def mean(xs: Vector[Number]): Number = ???
    }

這樣做,不僅丟掉了先前的類(lèi)型信息,還違背了擴(kuò)展性:不能強(qiáng)制第三方的數(shù)字類(lèi)型擴(kuò)展 Number 特質(zhì)。幸運(yùn)的是,本例并不存在這樣一個(gè)通用的特質(zhì)。

對(duì)于這種問(wèn)題,Ruby 的做法是 猴子補(bǔ)?。╩onkey patching) ,擴(kuò)展新類(lèi)型讓它看起來(lái)像一個(gè) Number ,但是這樣會(huì)污染全局命名空間。年輕時(shí)遭到 “四人幫” 打擊的 Java 開(kāi)發(fā)者,則會(huì)認(rèn)為 適配器(Adpater) 能解決上面所有問(wèn)題:

“四人幫”這里指的是設(shè)計(jì)模式一書(shū)的作者:Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides,具體見(jiàn):http://en.wikipedia.org/wiki/Design_Patterns

    object Statistics {
      trait NumberLike[A] {
        def get: A
        def plus(y: NumberLike[A]): NumberLike[A]
        def minus(y: NumberLike[A]): NumberLike[A]
        def divide(y: Int): NumberLike[A]
      }
      case class NumberLikeDouble(x: Double) extends NumberLike[Double] {
        def get: Double = x
        def minus(y: NumberLike[Double]) = NumberLikeDouble(x - y.get)
        def plus(y: NumberLike[Double]) = NumberLikeDouble(x + y.get)
        def divide(y: Int) = NumberLikeDouble(x / y)
      }
      type Quartile[A] = (NumberLike[A], NumberLike[A], NumberLike[A])
      def median[A](xs: Vector[NumberLike[A]]): NumberLike[A] = xs(xs.size / 2)
      def quartiles[A](xs: Vector[NumberLike[A]]): Quartile[A] =
        (xs(xs.size / 4), median(xs), xs(xs.size / 4 * 3))
      def iqr[A](xs: Vector[NumberLike[A]]): NumberLike[A] = quartiles(xs) match {
        case (lowerQuartile, _, upperQuartile) => upperQuartile.minus(lowerQuartile)
      }
      def mean[A](xs: Vector[NumberLike[A]]): NumberLike[A] =
        xs.reduce(_.plus(_)).divide(xs.size)
    }

上述代碼解決了擴(kuò)展性問(wèn)題:使用這個(gè)庫(kù)的用戶(hù)可以將類(lèi)型通過(guò) NumberLike 適配器傳遞過(guò)來(lái),無(wú)需重新編譯統(tǒng)計(jì)庫(kù)。

但是,把數(shù)字封裝在適配器里,這樣的代碼會(huì)令人厭倦,無(wú)論讀寫(xiě),而且和統(tǒng)計(jì)庫(kù)交互時(shí),必須創(chuàng)建一大堆適配器實(shí)例。

類(lèi)型類(lèi)來(lái)救援

對(duì)目前所介紹的方法來(lái)說(shuō),類(lèi)型類(lèi)是一個(gè)強(qiáng)大的替代。類(lèi)型類(lèi)是 Haskell 語(yǔ)言一個(gè)突出的特征,雖然它的名字里有類(lèi),但它和面向?qū)ο缶幊汤锏念?lèi)沒(méi)有任何關(guān)系。

一個(gè)類(lèi)型類(lèi) C 定義了一些行為,要想成為 C 的一員,類(lèi)型 T 必須支持這些行為。一個(gè)類(lèi)型 T 到底是不是 類(lèi)型類(lèi) C 的成員,這一點(diǎn)并不是與生俱來(lái)的。開(kāi)發(fā)者可以實(shí)現(xiàn)類(lèi)必須支持的行為,使得這個(gè)類(lèi)變成類(lèi)型類(lèi)的成員。一旦 T 變成 類(lèi)型類(lèi) C 的一員,參數(shù)類(lèi)型為類(lèi)型類(lèi) C 成員的函數(shù)就可以接受類(lèi)型 T 的實(shí)例。

這樣,類(lèi)型類(lèi)支持臨時(shí)的、追溯性的多態(tài),依賴(lài)類(lèi)型類(lèi)的代碼支持?jǐn)U展性,且無(wú)需創(chuàng)建任何適配器對(duì)象。

創(chuàng)建類(lèi)型類(lèi)

Scala 中,類(lèi)型類(lèi)可以通過(guò)技術(shù)組合來(lái)實(shí)現(xiàn)和使用,比之 Haskell,它在 Scala 里的參與度更高,而且?guī)Ыo開(kāi)發(fā)者更多的控制。

創(chuàng)建一個(gè)類(lèi)型類(lèi)涉及到幾個(gè)步驟。

首先,我們來(lái)定義一個(gè)特質(zhì):

    object Math {
      trait NumberLike[T] {
        def plus(x: T, y: T): T
        def divide(x: T, y: Int): T
        def minus(x: T, y: T): T
      }
    }

上述代碼創(chuàng)建了名為 NumberLike 的類(lèi)型類(lèi)特質(zhì)。類(lèi)型類(lèi)總會(huì)帶著一個(gè)或多個(gè)類(lèi)型參數(shù),通常是無(wú)狀態(tài)的,比如:里面定義的方法只對(duì)傳入的參數(shù)進(jìn)行操作。前文的適配器操作的是它自己的字段和接受的一個(gè)參數(shù),而這里定義的方法都需要兩個(gè)參數(shù),其中第一個(gè)參數(shù)對(duì)應(yīng)適配器中的字段。

提供默認(rèn)成員

第二步通常是在伴生對(duì)象里提供一些默認(rèn)的類(lèi)型類(lèi)特質(zhì)實(shí)現(xiàn),之后你會(huì)知道為什么要這么做。在這之前,先來(lái)實(shí)現(xiàn) DoubleInt 的類(lèi)型類(lèi)特質(zhì):

    object Math {
      trait NumberLike[T] {
        def plus(x: T, y: T): T
        def divide(x: T, y: Int): T
        def minus(x: T, y: T): T
      }
      object NumberLike {
        implicit object NumberLikeDouble extends NumberLike[Double] {
          def plus(x: Double, y: Double): Double = x + y
          def divide(x: Double, y: Int): Double = x / y
          def minus(x: Double, y: Double): Double = x - y
        }
        implicit object NumberLikeInt extends NumberLike[Int] {
          def plus(x: Int, y: Int): Int = x + y
          def divide(x: Int, y: Int): Int = x / y
          def minus(x: Int, y: Int): Int = x - y
        }
      }
    }

兩件事情:第一,這兩個(gè)實(shí)現(xiàn)基本相同。但不總是這樣,畢竟 NumberLike 只是一個(gè)很小的域。后面會(huì)給出類(lèi)型類(lèi)的一些例子,當(dāng)為這些例子實(shí)現(xiàn)多個(gè)類(lèi)型時(shí),重復(fù)的余地就少很多。第二, NumberLikeInt 做整數(shù)除法的時(shí)候,會(huì)損失一些精度,請(qǐng)忽略這一事實(shí),這只是為簡(jiǎn)單起見(jiàn)。

你也許會(huì)發(fā)現(xiàn),類(lèi)型類(lèi)的成員通常是單例對(duì)象,而且會(huì)有一個(gè) implicit 關(guān)鍵字位于前面,這是類(lèi)型類(lèi)在 Scala 中成為可能的幾個(gè)重要因素之一,在某些條件下,它讓類(lèi)型類(lèi)成員隱式可用。更多相關(guān)的知識(shí)在下一節(jié)。

運(yùn)用類(lèi)型類(lèi)

有了類(lèi)型類(lèi)和兩個(gè)默認(rèn)實(shí)現(xiàn)之后,就可以根據(jù)它們來(lái)實(shí)現(xiàn)統(tǒng)計(jì)。我們先將重點(diǎn)放在 mean 方法上:

    object Statistics {
      import Math.NumberLike
      def mean[T](xs: Vector[T])(implicit ev: NumberLike[T]): T =
        ev.divide(xs.reduce(ev.plus(_, _)), xs.size)
    }

這樣的代碼初看起來(lái)可能有點(diǎn)嚇人,實(shí)際上是相當(dāng)簡(jiǎn)單,方法帶有一個(gè)類(lèi)型參數(shù) T ,接受類(lèi)型為 Vector[T] 的參數(shù)。

將參數(shù)限制在特定類(lèi)型類(lèi)的成員上,是通過(guò)第二個(gè) implicit 參數(shù)列表實(shí)現(xiàn)的。這是什么意思?這是說(shuō),當(dāng)前作用域中必須存在一個(gè)隱式可用的 NumberLike[T] 對(duì)象,比如說(shuō),當(dāng)前作用域聲明了一個(gè) 隱式值(implicit value)。這種聲明很多時(shí)候都是通過(guò)導(dǎo)入一個(gè)有隱式值定義的包或者對(duì)象來(lái)實(shí)現(xiàn)的。

當(dāng)且僅當(dāng)沒(méi)有發(fā)現(xiàn)其他隱式值時(shí),編譯器會(huì)在隱式參數(shù)類(lèi)型的伴生對(duì)象中尋找。作為庫(kù)的設(shè)計(jì)者,將默認(rèn)的類(lèi)型類(lèi)實(shí)現(xiàn)放在伴生對(duì)象里意味著庫(kù)的使用者可以輕易的重寫(xiě)默認(rèn)實(shí)現(xiàn),這正是庫(kù)設(shè)計(jì)者喜聞樂(lè)見(jiàn)的。用戶(hù)還可以為隱式參數(shù)傳遞一個(gè)顯示值,來(lái)重寫(xiě)作用域內(nèi)的隱式值。

讓我們來(lái)驗(yàn)證下默認(rèn)的實(shí)現(xiàn)是否可以被正確解析:

    val numbers = Vector[Double](13, 23.0, 42, 45, 61, 73, 96, 100, 199, 420, 900, 3839)
    println(Statistics.mean(numbers))

漂亮極了!試試 Vector[String] ,你會(huì)在編譯期得到一個(gè)錯(cuò)誤,這個(gè)錯(cuò)誤指出參數(shù) ev: NumberLike[String] 沒(méi)有隱式值可用。如果你不喜歡這個(gè)錯(cuò)誤消息,你可以用 @implicitNotFound 為類(lèi)型類(lèi)添加批注,來(lái)自定義錯(cuò)誤消息:

    object Math {
      import annotation.implicitNotFound
      @implicitNotFound("No member of type class NumberLike in scope for ${T}")
      trait NumberLike[T] {
        def plus(x: T, y: T): T
        def divide(x: T, y: Int): T
        def minus(x: T, y: T): T
      }
    }

上下文綁定

總是帶著這個(gè)隱式參數(shù)列表顯得有些冗長(zhǎng)。對(duì)于只有一個(gè)類(lèi)型參數(shù)的隱式參數(shù),Scala 提供了一種叫做 上下文綁定(context bound) 的簡(jiǎn)寫(xiě)。為了說(shuō)明這一使用方法,我們用它來(lái)實(shí)現(xiàn)剩下的統(tǒng)計(jì)方法:

    object Statistics {
      import Math.NumberLike
      def mean[T](xs: Vector[T])(implicit ev: NumberLike[T]): T =
        ev.divide(xs.reduce(ev.plus(_, _)), xs.size)
      def median[T : NumberLike](xs: Vector[T]): T = xs(xs.size / 2)
      def quartiles[T: NumberLike](xs: Vector[T]): (T, T, T) =
        (xs(xs.size / 4), median(xs), xs(xs.size / 4 * 3))
      def iqr[T: NumberLike](xs: Vector[T]): T = quartiles(xs) match {
        case (lowerQuartile, _, upperQuartile) =>
          implicitly[NumberLike[T]].minus(upperQuartile, lowerQuartile)
      }
    }

上下文綁定 T: NumberLike 意思是,必須有一個(gè)類(lèi)型為 NumberLike[T] 的隱式值在當(dāng)前上下文中可用,這和隱式參數(shù)列表是等價(jià)的。如果想要訪問(wèn)這個(gè)隱式值,需要調(diào)用 implicitly 方法,就像上述 iqr 方法所做的那樣。如果類(lèi)型類(lèi)需要多個(gè)類(lèi)型參數(shù),就不能使用上下文綁定語(yǔ)法了。

自定義的類(lèi)型類(lèi)成員

含有類(lèi)型類(lèi)的庫(kù)的使用者,或遲或早會(huì)想將他自己的類(lèi)型加入到類(lèi)型類(lèi)成員中。比如說(shuō),可能想將統(tǒng)計(jì)用在 Joda Time 的 Duration 實(shí)例上。

我們來(lái)試試吧。首先將 Joda Time 加入到路徑里:

    libraryDependencies += "joda-time" % "joda-time" % "2.1"

    libraryDependencies += "org.joda" % "joda-convert" % "1.3"

現(xiàn)在,只需創(chuàng)建 NumberLike 的一個(gè)隱式實(shí)現(xiàn):

    object JodaImplicits {
      import Math.NumberLike
      import org.joda.time.Duration
      implicit object NumberLikeDuration extends NumberLike[Duration] {
        def plus(x: Duration, y: Duration): Duration = x.plus(y)
        def divide(x: Duration, y: Int): Duration = Duration.millis(x.getMillis / y)
        def minus(x: Duration, y: Duration): Duration = x.minus(y)
      }
    }

導(dǎo)入包含這個(gè)實(shí)現(xiàn)的包或者對(duì)象,就可以計(jì)算一堆 durations 的平均值了:

    import Statistics._
    import JodaImplicits._
    import org.joda.time.Duration._

    val durations = Vector(standardSeconds(20), standardSeconds(57), standardMinutes(2),
      standardMinutes(17), standardMinutes(30), standardMinutes(58), standardHours(2),
      standardHours(5), standardHours(8), standardHours(17), standardDays(1),
      standardDays(4))
    println(mean(durations).getStandardHours)

使用場(chǎng)景

NumberLike 類(lèi)型類(lèi)是一個(gè)非常好的例子,但 Scala 已經(jīng)有 Numeric 了。對(duì)于集合的類(lèi)型參數(shù) T ,只要存在一個(gè)可用的 Numeric[T],就可以在該集合上調(diào)用 sum 、 product 這樣的方法。標(biāo)準(zhǔn)庫(kù)中另一個(gè)使用比較多的類(lèi)型類(lèi)是 Ordering,可以為自定義類(lèi)型提供一個(gè)隱式排序,用在 Scala 集合的 sort 方法。

標(biāo)準(zhǔn)庫(kù)中還有更多這樣的類(lèi)型類(lèi),不過(guò),Scala 開(kāi)發(fā)者并不需要與它們中的每一個(gè)都打交道。

第三方庫(kù)中一個(gè)非常常見(jiàn)的用例是對(duì)象序列化和反序列化,尤其是 JSON 對(duì)象。使一個(gè)類(lèi)成為某個(gè)格式器類(lèi)型類(lèi)的成員,就可以自定義類(lèi)的序列化方式,序列化成 JSON、XML 或者是任何新的格式。

Scala 類(lèi)型和數(shù)據(jù)庫(kù)驅(qū)動(dòng)支持的類(lèi)型之間的映射,通常也是通過(guò)類(lèi)型類(lèi)獲得自定義和可擴(kuò)展性的。

總結(jié)

一旦開(kāi)始用 Scala 來(lái)做些正式的工作,不可避免的會(huì)遇到類(lèi)型類(lèi)。希望讀者在讀完這一章后,能夠利用好這一強(qiáng)大技術(shù)。

Scala 類(lèi)型類(lèi)使得在開(kāi)發(fā) Scala 應(yīng)用時(shí),一方面可以有無(wú)限可追加的擴(kuò)展,另一方面又可以保留盡可能多的具體類(lèi)型信息。

和其他語(yǔ)言應(yīng)對(duì)這種問(wèn)題的方法想比,Scala 給予了開(kāi)發(fā)者完全的控制權(quán),因?yàn)轭?lèi)型類(lèi)的實(shí)現(xiàn)可以被輕易的重寫(xiě),而且在全局命名空間里不可用。

你將看到這種技術(shù)在編寫(xiě)由其他人使用的庫(kù)時(shí)尤其有用,在應(yīng)用程序代碼中,為了減少模塊之間的耦合,類(lèi)型類(lèi)也是有用武之地的。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)