<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>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                在 Coursera 上,想必你遇到過一個非常強大的語言特性:[模式匹配](http://en.wikipedia.org/wiki/Pattern_matching) 。它可以解綁一個給定的數據結構。這不是 Scala 所特有的,在其他出色的語言中,如 Haskell、Erlang,模式匹配也扮演著重要的角色。 模式匹配可以解構各種數據結構,包括 _列表_ 、 _流_ ,以及 _樣例類_ 。但只有這些數據結構才能被解構嗎,還是可以用某種方式擴展其使用范圍?而且,它實際是怎么工作的?是不是有什么魔法在里面,得以寫些類似下面的代碼? ~~~ case class User(firstName: String, lastName: String, score: Int) def advance(xs: List[User]) = xs match { case User(_, _, score1) :: User(_, _, score2) :: _ => score1 - score2 case _ => 0 } ~~~ 事實證明沒有什么魔法,這都歸功于[提取器](http://www.scala-lang.org/node/112) 。 提取器使用最為廣泛的使用有著與 _構造器_ 相反的效果:構造器從給定的參數列表創建一個對象,而提取器卻是從傳遞給它的對象中提取出構造該對象的參數。Scala 標準庫包含了一些預定義的提取器,我們會大致的了解一下它們。 樣例類非常特殊,Scala會自動為其創建一個 _伴生對象_ :一個包含了 `apply` 和 `unapply` 方法的 _單例對象_ 。`apply` 方法用來創建樣例類的實例,而 `unapply` 需要被伴生對象實現,以使其成為提取器。 ### 第一個提取器 `unapply` 方法可能不止有一種方法簽名,不過,我們從只有最簡單的開始,畢竟使用更廣泛的還是只有一種方法簽名的 `unapply` 。假設要創建了一個 `User` 特質,有兩個類繼承自它,并且包含一個字段: ~~~ trait User { def name: String } class FreeUser(val name: String) extends User class PremiumUser(val name: String) extends User ~~~ 我們想在各自的伴生對象中為 `FreeUser` 和 `PremiumUser` 類實現提取器,就像 Scala 為樣例類所做的一樣。如果想讓樣例類只支持從給定對象中提取單個參數,那 `unapply` 方法的簽名看起來應該是這個樣子: ~~~ def unapply(object: S): Option[T] ~~~ 這個方法接受一個類型為 `S` 的對象,返回類型 `T` 的 `Option` , `T` 就是要提取的參數類型。 > 在Scala中, `Option` 是 `null` 值的安全替代。以后會有一個單獨的章節來講述它,不過現在,只需要知道,`unapply` 方法要么返回 `Some[T]` (如果它能成功提取出參數),要么返回 `None` ,`None` 表示參數不能被 `unapply` 具體實現中的任一提取規則所提取出。 下面的代碼是我們的提取器: ~~~ trait User { def name: String } class FreeUser(val name: String) extends User class PremiumUser(val name: String) extends User object FreeUser { def unapply(user: FreeUser): Option[String] = Some(user.name) } object PremiumUser { def unapply(user: PremiumUser): Option[String] = Some(user.name) } ~~~ 現在,可以在REPL中使用它: ~~~ scala> FreeUser.unapply(new FreeUser("Daniel")) res0: Option[String] = Some(Daniel) ~~~ 如果調用返回的結果是 `Some[T]` ,說明提取模式匹配成功,如果是 `None` ,說明模式不匹配。 一般不會直接調用它,因為用于提取器模式時,Scala 會隱式的調用提取器的 `unapply` 方法。 ~~~ val user: User = new PremiumUser("Daniel") user match { case FreeUser(name) => "Hello" + name case PremiumUser(name) => "Welcome back, dear" + name } ~~~ 你會發現,兩個提取器絕不會都返回 `None` 。這個例子展示的提取器要比之前所見的更有意義。如果你有一個類型不確定的對象,你可以同時檢查其類型并解構。 這個例子里, `FreeUser` 模式并不會匹配,因為它接受的類型和我們傳遞給它的不一樣。這樣一來, `user` 對象就會被傳遞給第二個模式,也就是 `PremiumUser` 伴生對象的 `unapply` 方法。而這個模式會匹配成功,從而返回值就被綁定到 `name` 參數上。 在接下來的文章里,我們會看到一個并不總是返回 `Some[T]` 的提取器的例子。 ### 提取多個值 現在,假設類有多個字段: ~~~ trait User { def name: String def score: Int } class FreeUser( val name: String, val score: Int, val upgradeProbability: Double ) extends User class PremiumUser( val name: String, val score: Int ) extends User ~~~ 如果提取器想解構出多個參數,那它的 `unapply` 方法應該有這樣的簽名: ~~~ def unapply(object: S): Option[(T1, ..., T2)] ~~~ 這個方法接受類型為 `S` 的對象,返回類型參數為 `TupleN` 的 `Option` 實例,`TupleN` 中的 `N` 是要提取的參數個數。 修改類之后,提取器也要做相應的修改: ~~~ trait User { def name: String def score: Int } class FreeUser( val name: String, val score: Int, val upgradeProbability: Double ) extends User class PremiumUser( val name: String, val score: Int ) extends User object FreeUser { def unapply(user: FreeUser): Option[(String, Int, Double)] = Some((user.name, user.score, user.upgradeProbability)) } object PremiumUser { def unapply(user: PremiumUser): Option[(String, Int)] = Some((user.name, user.score)) } ~~~ 現在可以拿它來做模式匹配了: ~~~ val user: User = new FreeUser("Daniel", 3000, 0.7d) user match { case FreeUser(name, _, p) => if (p > 0.75) "$name, what can we do for you today?" else "Hello $name" case PremiumUser(name, _) => "Welcome back, dear $name" } ~~~ ### 布爾提取器 有些時候,進行模式匹配并不是為了提取參數,而是為了檢查其是否匹配。這種情況下,第三種 `unapply` 方法簽名(也是最后一種)就有用了,這個方法接受 `S` 類型的對象,返回一個布爾值: ~~~ def unapply(object: S): Boolean ~~~ 使用的時候,如果這個提取器返回 `true` ,模式會匹配成功,否則,Scala 會嘗試拿 `object` 匹配下一個模式。 上一個例子存在一些邏輯代碼,用來檢查一個免費用戶有沒有可能被說服去升級他的賬戶。其實可以把這個邏輯放在一個單獨的提取器中: ~~~ object premiumCandidate { def unapply(user: FreeUser): Boolean = user.upgradeProbability > 0.75 } ~~~ 你會發現,提取器不一定非要在這個類的伴生對象中定義。正如其定義一樣,這個提取器的使用方法也很簡單: ~~~ val user: User = new FreeUser("Daniel", 2500, 0.8d) user match { case freeUser @ premiumCandidate() => initiateSpamProgram(freeUser) case _ => sendRegularNewsletter(user) } ~~~ 使用的時候,只需要把一個空的參數列表傳遞給提取器,因為它并不真的需要提取數據,自然也沒必要綁定變量。 這個例子有一個看起來比較奇怪的地方:我假設存在一個空想的 `initiateSpamProgram` 函數,其接受一個 `FreeUser` 對象作為參數。模式可以與任何一種 `User` 類型的實例進行匹配,但 `initiateSpamProgram` 不行,只有將實例強制轉換為 `FreeUser` 類型, `initiateSpamProgram` 才能接受。 因為如此,Scala 的模式匹配也允許將提取器匹配成功的實例綁定到一個變量上,這個變量有著與提取器所接受的對象相同的類型。這通過 `@` 操作符實現。`premiumCandidate` 接受 `FreeUser` 對象,因此,變量 `freeUser` 的類型也就是 `FreeUser` 。 布爾提取器的使用并沒有那么頻繁(就我自己的情況來說),但知道它存在也是很好的,或遲或早,你會遇到一個使用布爾提取器的場景。 ### 中綴表達方式 解構列表、流的方法與創建它們的方法類似,都是使用 cons 操作符: `::` 、 `#::` ,比如: ~~~ val xs = 58 #:: 43 #:: 93 #:: Stream.empty xs match { case first #:: second #:: _ => first - second case _ => -1 } ~~~ 你可能會對這種做法產生困惑。除了我們已經見過的提取器用法,Scala 還允許以中綴方式來使用提取器。所以,我們可以寫成 `e(p1, p2)` ,也可以寫成 `p1 e p2` ,其中 `e` 是提取器, `p1` 、 `p2` 是要提取的參數。 同樣,中綴操作方式的 `head #:: tail` 可以被寫成 `#::(head, tail)` ,提取器 `PremiumUser` 可以這樣使用: `name PremiumUser score` 。當然,這樣做并沒有什么實踐意義。一般來說,只有當一個提取器看起來真的像操作符,才推薦以中綴操作方式來使用它。所以,列表和流的 `cons` 操作符一般使用中綴表達,而 `PreimumUser` 則不用。 ### 進一步看流提取器 盡管 `#::` 提取器在模式匹配中的使用并沒有什么特殊的,但是,為了更好的理解上面的代碼,還是進一步來分析一下。而且,這是一個很好的例子,根據要匹配的數據結構的狀態,提取器很可能返回 `None` 。 如下是 _Scala 2.9.2_ 源代碼中完整的 `#::` 提取器代碼: ~~~ object #:: { def unapply[A](xs: Stream[A]): Option[(A, Stream[A]) = if (xs.isEmpty) None else Some((xs.head, xs.tail)) } ~~~ 如果給定的流是空的,提取器就直接返回 `None` 。因此, `case head #:: tail` 就不會匹配任何空的流。否則,就會返回一個 `Tuple2` ,其第一個元素是流的頭,第二個元素是流的尾,尾本身又是一個流。這樣, `case head #:: tail` 就會匹配有一個或多個元素的流。如果只有一個元素, `tail` 就會被綁定成空流。 為了理解流提取器是怎么在模式匹配中工作的,重寫上面的例子,把它從中綴寫法轉成普通的提取器模式寫法: ~~~ val xs = 58 #:: 43 #:: 93 #:: Stream.empty xs match { case #::(first, #::(second, _)) => first - second case _ => -1 } ~~~ 首先為傳遞給模式匹配的初始流 `xs` 調用提取器。由于提取器返回 `Some(xs.head, xs.tail)` ,從而 `first` 會綁定成 58,`xs` 的尾會繼續傳遞給提取器,提取器再一次被調用,返回首和尾, `second` 就被綁定成 `43` ,而尾就綁定到通配符 `_` ,被直接扔掉了。 ### 使用提取器 那到底該在什么時候使用、怎么使用自定義的提取器呢?尤其考慮到,使用樣例類就能自動獲得可用的提取器。 一些人指出,使用樣例類、對樣例類進行模式匹配打破了封裝,耦合了匹配數據和其具體實現的方式,這種批評通常是從面向對象的角度出發的。如果想用 Scala 進行函數式編程,將樣例類當作只包含純數據(不包含行為)的[代數數據類型](http://en.wikipedia.org/wiki/Algebraic_data_type) ,那它非常適合。 通常,只有當從無法掌控的類型中提取數據,或者是需要其他進行模式匹配的方法時,才需要實現自己的提取器。 > 提取器的一種常見用法是從字符串中提取出有意義的值,作為練習,想一想如何實現 `URLExtractor` 以匹配代表 URL 的字符串。 ### 小結 在這本書的第一章中,我們學習了 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>

                              哎呀哎呀视频在线观看