<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                前兩章討論了幾種保持 DRY 和靈活性的函數式編程技術: 1. 函數組合(function composition) 1. 部分函數應用(partial function application) 1. 柯里化(currying) 這一章依舊圍繞代碼靈活性而來,不過不再討論作為頭等公民的函數,而是類型系統(注意:并不是要真的去研究類型系統)。你將學習 **類型類** ! 可能你會覺得這沒有實際意義,認為這是被 Haskell 狂熱分子帶入 Scala 社區的異國情調,顯然不是這樣。類型類已經成為 Scala 標準庫,甚至是很多流行的、廣泛使用的第三方開源庫的重要組成部分,了解和熟悉類型類是很有必要的。 本章會討論: 1. 類型類的概念, 1. 它為什么有用, 1. 使用它如何受益, 1. 如何實現類型類,并用于實踐。 ### 問題 我們用例子,而不是一個對類型類的抽象解釋,開始本文的主題,例子簡化了概念,也相當實用。 假設想提供一系列可以操作數字集合的函數,主要是計算它們的聚合值。進一步假設只能通過索引來訪問集合的元素,只能使用定義在 Scala 集合上的 `reduce` 方法。(施加這些限制,是因為要實現的東西,Scala 標準庫已經提供了)最后,假定得到的值已排序。 我們先從 `median` , `quartiles` , `iqr` 的一個粗暴實現開始: ~~~ 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` 將數據集分成兩半,下四分位數和上四分位數( `quartiles` 方法返回的元組的第一、第三個元素)分別分割了數據集的 25% 。`iqr` 方法返回四分差(上四分衛數和下四分位數的差)。 現在我們想支持更多的類型,比如,`Int`,所以應該為這個類型實現上面這些方法,對吧? 不!不能想當然的為 `Vector[Int]` 重載上面的方法(詭異的技巧除外),因為類型參數會被擦除,而且這樣做有代碼冗余的嫌疑。 要是 `Int` 和 `Double` 擴展自一個共同的基類,或者都實現了一個像是 `Number` 這樣的特質,那該多好! 你可能會想著去把上述方法需要的參數類型替換成更通用的類型,看起來會是這樣: ~~~ 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 = ??? } ~~~ 這樣做,不僅丟掉了先前的類型信息,還違背了擴展性:不能強制第三方的數字類型擴展 `Number` 特質。幸運的是,本例并不存在這樣一個通用的特質。 對于這種問題,Ruby 的做法是 **猴子補丁(monkey patching)** ,擴展新類型讓它看起來像一個 `Number` ,但是這樣會污染全局命名空間。年輕時遭到 “四人幫” 打擊的 Java 開發者,則會認為 **適配器(Adpater)** 能解決上面所有問題: > “四人幫”這里指的是設計模式一書的作者:Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides,具體見:[http://en.wikipedia.org/wiki/Design_Patterns](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) } ~~~ 上述代碼解決了擴展性問題:使用這個庫的用戶可以將類型通過 `NumberLike` 適配器傳遞過來,無需重新編譯統計庫。 但是,把數字封裝在適配器里,這樣的代碼會令人厭倦,無論讀寫,而且和統計庫交互時,必須創建一大堆適配器實例。 ### 類型類來救援 對目前所介紹的方法來說,類型類是一個強大的替代。類型類是 Haskell 語言一個突出的特征,雖然它的名字里有類,但它和面向對象編程里的類沒有任何關系。 一個類型類 `C` 定義了一些行為,要想成為 `C` 的一員,類型 `T` 必須支持這些行為。一個類型 `T` 到底是不是 類型類 `C` 的成員,這一點并不是與生俱來的。開發者可以實現類必須支持的行為,使得這個類變成類型類的成員。一旦 `T` 變成 類型類 `C` 的一員,參數類型為類型類 `C` 成員的函數就可以接受類型 `T` 的實例。 這樣,類型類支持臨時的、追溯性的多態,依賴類型類的代碼支持擴展性,且無需創建任何適配器對象。 ### 創建類型類 Scala 中,類型類可以通過技術組合來實現和使用,比之 Haskell,它在 Scala 里的參與度更高,而且帶給開發者更多的控制。 創建一個類型類涉及到幾個步驟。 首先,我們來定義一個特質: ~~~ 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 } } ~~~ 上述代碼創建了名為 `NumberLike` 的類型類特質。類型類總會帶著一個或多個類型參數,通常是無狀態的,比如:里面定義的方法只對傳入的參數進行操作。前文的適配器操作的是它自己的字段和接受的一個參數,而這里定義的方法都需要兩個參數,其中第一個參數對應適配器中的字段。 ### 提供默認成員 第二步通常是在伴生對象里提供一些默認的類型類特質實現,之后你會知道為什么要這么做。在這之前,先來實現 `Double` 和 `Int` 的類型類特質: ~~~ 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 } } } ~~~ 兩件事情:第一,這兩個實現基本相同。但不總是這樣,畢竟 `NumberLike` 只是一個很小的域。后面會給出類型類的一些例子,當為這些例子實現多個類型時,重復的余地就少很多。第二, `NumberLikeInt` 做整數除法的時候,會損失一些精度,請忽略這一事實,這只是為簡單起見。 你也許會發現,類型類的成員通常是單例對象,而且會有一個 `implicit` 關鍵字位于前面,這是類型類在 Scala 中成為可能的幾個重要因素之一,在某些條件下,它讓類型類成員隱式可用。更多相關的知識在下一節。 ### 運用類型類 有了類型類和兩個默認實現之后,就可以根據它們來實現統計。我們先將重點放在 `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) } ~~~ 這樣的代碼初看起來可能有點嚇人,實際上是相當簡單,方法帶有一個類型參數 `T` ,接受類型為 `Vector[T]` 的參數。 將參數限制在特定類型類的成員上,是通過第二個 `implicit` 參數列表實現的。這是什么意思?這是說,當前作用域中必須存在一個隱式可用的 `NumberLike[T]` 對象,比如說,當前作用域聲明了一個 **隱式值(implicit value)**。這種聲明很多時候都是通過導入一個有隱式值定義的包或者對象來實現的。 當且僅當沒有發現其他隱式值時,編譯器會在隱式參數類型的伴生對象中尋找。作為庫的設計者,將默認的類型類實現放在伴生對象里意味著庫的使用者可以輕易的重寫默認實現,這正是庫設計者喜聞樂見的。用戶還可以為隱式參數傳遞一個顯示值,來重寫作用域內的隱式值。 讓我們來驗證下默認的實現是否可以被正確解析: ~~~ val numbers = Vector[Double](13, 23.0, 42, 45, 61, 73, 96, 100, 199, 420, 900, 3839) println(Statistics.mean(numbers)) ~~~ 漂亮極了!試試 `Vector[String]` ,你會在編譯期得到一個錯誤,這個錯誤指出參數 `ev: NumberLike[String]` 沒有隱式值可用。如果你不喜歡這個錯誤消息,你可以用 `@implicitNotFound` 為類型類添加批注,來自定義錯誤消息: ~~~ 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 } } ~~~ ### 上下文綁定 總是帶著這個隱式參數列表顯得有些冗長。對于只有一個類型參數的隱式參數,Scala 提供了一種叫做 **上下文綁定(context bound)** 的簡寫。為了說明這一使用方法,我們用它來實現剩下的統計方法: ~~~ 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` 意思是,必須有一個類型為 `NumberLike[T]` 的隱式值在當前上下文中可用,這和隱式參數列表是等價的。如果想要訪問這個隱式值,需要調用 `implicitly` 方法,就像上述 `iqr` 方法所做的那樣。如果類型類需要多個類型參數,就不能使用上下文綁定語法了。 ### 自定義的類型類成員 含有類型類的庫的使用者,或遲或早會想將他自己的類型加入到類型類成員中。比如說,可能想將統計用在 Joda Time 的 `Duration` 實例上。 我們來試試吧。首先將 Joda Time 加入到路徑里: ~~~ libraryDependencies += "joda-time" % "joda-time" % "2.1" libraryDependencies += "org.joda" % "joda-convert" % "1.3" ~~~ 現在,只需創建 `NumberLike` 的一個隱式實現: ~~~ 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) } } ~~~ 導入包含這個實現的包或者對象,就可以計算一堆 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) ~~~ ### 使用場景 `NumberLike` 類型類是一個非常好的例子,但 Scala 已經有 `Numeric` 了。對于集合的類型參數 `T` ,只要存在一個可用的 `Numeric[T]`,就可以在該集合上調用 `sum` 、 `product` 這樣的方法。標準庫中另一個使用比較多的類型類是 `Ordering`,可以為自定義類型提供一個隱式排序,用在 Scala 集合的 `sort` 方法。 標準庫中還有更多這樣的類型類,不過,Scala 開發者并不需要與它們中的每一個都打交道。 第三方庫中一個非常常見的用例是對象序列化和反序列化,尤其是 JSON 對象。使一個類成為某個格式器類型類的成員,就可以自定義類的序列化方式,序列化成 JSON、XML 或者是任何新的格式。 Scala 類型和數據庫驅動支持的類型之間的映射,通常也是通過類型類獲得自定義和可擴展性的。 ### 總結 一旦開始用 Scala 來做些正式的工作,不可避免的會遇到類型類。希望讀者在讀完這一章后,能夠利用好這一強大技術。 Scala 類型類使得在開發 Scala 應用時,一方面可以有無限可追加的擴展,另一方面又可以保留盡可能多的具體類型信息。 和其他語言應對這種問題的方法想比,Scala 給予了開發者完全的控制權,因為類型類的實現可以被輕易的重寫,而且在全局命名空間里不可用。 你將看到這種技術在編寫由其他人使用的庫時尤其有用,在應用程序代碼中,為了減少模塊之間的耦合,類型類也是有用武之地的。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看