<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國際加速解決方案。 廣告
                上一章重點在于代碼重復:提升現有的函數功能、或者將函數進行組合。這一章,我們來看看另外兩種函數重用的機制:**函數的部分應用(Partial Application of Functions)** 、 **柯里化(Currying)** 。 ### 部分應用的函數 和其他遵循函數式編程范式的語言一樣,Scala 允許_部分_應用一個函數。調用一個函數時,不是把函數需要的所有參數都傳遞給它,而是僅僅傳遞一部分,其他參數留空;這樣會生成一個新的函數,其參數列表由那些被留空的參數組成。(不要把這個概念和偏函數混淆) 為了具體說明這一概念,回到上一章的例子:假想的免費郵件服務,能夠讓用戶配置篩選器,以使得滿足特定條件的郵件顯示在收件箱里,其他的被過濾掉。 `Email` 類看起來仍然是這樣: ~~~ case class Email( subject: String, text: String, sender: String, recipient: String) type EmailFilter = Email => Boolean ~~~ 過濾郵件的條件用謂詞 `Email => Boolean` 表示, `EmailFilter` 是其別名。調用適當的工廠方法可以生成這些謂詞。 上一章,我們創建了兩個這樣的工廠方法,它們檢查郵件內容長度是否滿足給定的最大值或最小值。這一次,我們使用部分應用函數來實現這些工廠方法,做法是,修改 `sizeConstraint` ,固定某些參數可以創建更具體的限制條件: 其修改后的代碼如下: ~~~ type IntPairPred = (Int, Int) => Boolean def sizeConstraint(pred: IntPairPred, n: Int, email: Email) = pred(email.text.size, n) ~~~ 上述代碼為一個謂詞函數定義了別名 `IntPairPred` ,該函數接受一對整數(值 `n` 和郵件內容長度),檢查郵件長度對于 `n` 是否 OK。 請注意,不像上一章的 `sizeConstraint` ,這一個并不返回新的 `EmailFilter`,它只是簡單的用參數做計算,返回一個布爾值。秘訣在于,你可以部分應用這個 `sizeConstraint` 來得到一個 `EmailFilter` 。 遵循 DRY 原則,我們先來定義常用的 `IntPairPred` 實例,這樣,在調用 `sizeConstraint` 時,不需要重復的寫相同的匿名函數,只需傳遞下面這些: ~~~ val gt: IntPairPred = _ > _ val ge: IntPairPred = _ >= _ val lt: IntPairPred = _ < _ val le: IntPairPred = _ <= _ val eq: IntPairPred = _ == _ ~~~ 最后,調用 `sizeConstraint` 函數,用上面的 `IntPairPred` 傳入第一個參數: ~~~ val minimumSize: (Int, Email) => Boolean = sizeConstraint(ge, _: Int, _: Email) val maximumSize: (Int, Email) => Boolean = sizeConstraint(le, _: Int, _: Email) ~~~ 對所有沒有傳入值的參數,必須使用占位符 `_` ,還需要指定這些參數的類型,這使得函數的部分應用多少有些繁瑣。Scala 編譯器無法推斷它們的類型,方法重載使編譯器不可能知道你想使用哪個方法。 不過,你可以綁定或漏掉任意個、任意位置的參數。比如,我們可以漏掉第一個值,只傳遞約束值 `n` : ~~~ val constr20: (IntPairPred, Email) => Boolean = sizeConstraint(_: IntPairPred, 20, _: Email) val constr30: (IntPairPred, Email) => Boolean = sizeConstraint(_: IntPairPred, 30, _: Email) ~~~ 得到的兩個函數,接受一個 `IntPairPred` 和一個 `Email` 作為參數,然后利用謂詞函數 `IntPairPred` 把郵件長度和 `20` 、 `30` 比較,只不過比較方法的邏輯 `IntPairPred` 需要另外指定。 由此可見,雖然函數部分應用看起來比較冗長,但它要比 Clojure 的靈活,在 Clojure 里,必須從左到右的傳遞參數,不能略掉中間的任何參數。 ### 從方法到函數對象 在一個方法上做部分應用時,可以不綁定任何的參數,這樣做的效果是產生一個函數對象,并且其參數列表和原方法一模一樣。通過這種方式可以將方法變成一個可賦值、可傳遞的函數! ~~~ val sizeConstraintFn: (IntPairPred, Int, Email) => Boolean = sizeConstraint _ ~~~ ### 更有趣的函數 部分函數應用顯得太啰嗦,用起來不夠優雅,幸好還有其他的替代方法。 也許你已經知道 Scala 里的方法可以有多個參數列表。下面的代碼用多個參數列表重新定義了 `sizeConstraint` : ~~~ def sizeConstraint(pred: IntPairPred)(n: Int)(email: Email): Boolean = pred(email.text.size, n) ~~~ 如果把它變成一個可賦值、可傳遞的函數對象,它的簽名看起來會像是這樣: ~~~ val sizeConstraintFn: IntPairPred => Int => Email => Boolean = sizeConstraint _ ~~~ 這種單參數的鏈式函數稱做 **柯里化函數** ,以發明人 Haskell Curry 命名。在 Haskell 編程語言里,所有的函數默認都是柯里化的。 `sizeConstraintFn` 接受一個 `IntPairPred` ,返回一個函數,這個函數又接受 `Int` 類型的參數,返回另一個函數,最終的這個函數接受一個 `Email` ,返回布爾值。 現在,可以把要傳入的 `IntPairPred` 傳遞給 `sizeConstraint` 得到: ~~~ val minSize: Int => Email => Boolean = sizeConstraint(ge) val maxSize: Int => Email => Boolean = sizeConstraint(le) ~~~ 被留空的參數沒必要使用占位符,因為這不是部分函數應用。 現在,可以通過這兩個柯里化函數來創建 `EmailFilter` 謂詞: ~~~ val min20: Email => Boolean = minSize(20) val max20: Email => Boolean = maxSize(20) ~~~ 也可以在柯里化的函數上一次性綁定多個參數,直接得到上面的結果。傳入第一個參數得到的函數會立即應用到第二個參數上: ~~~ val min20: Email => Boolean = sizeConstraintFn(ge)(20) val max20: Email => Boolean = sizeConstraintFn(le)(20) ~~~ ### 函數柯里化 有時候,并不總是能提前知道要不要將一個函數寫成柯里化形式,畢竟,和只有單參數列表的函數相比,柯里化函數的使用并不清晰。而且,偶爾還會想以柯里化的形式去使用第三方的函數,但這些函數的參數都在一個參數列表里。 這就需要一種方法能對函數進行柯里化。這種的柯里化行為本質上也是一個高階函數:接受現有的函數,返回新函數。這個高階函數就是 `curried` :`curried` 方法存在于 `Function2` 、 `Function3` 這樣的多參數函數類型里。如果存在一個接受兩個參數的 `sum` ,可以通過調用 `curried` 方法得到它的柯里化版本: ~~~ val sum: (Int, Int) => Int = _ + _ val sumCurried: Int => Int => Int = sum.curried ~~~ 使用 `Funtion.uncurried` 進行反向操作,可以將一個柯里化函數轉換成非柯里化版本。 ### 函數化的依賴注入 在這一章的最后,我們來看看柯里化函數如何發揮其更大的作用。來自 Java 或者 .NET 世界的人,或多或少都用過依賴注入容器,這些容器為使用者管理對象,以及對象之間的依賴關系。在 Scala 里,你并不真的需要這樣的外部工具,語言已經提供了許多功能,這些功能簡化了依賴注入的實現。 函數式編程仍然需要注入依賴:應用程序中上層函數需要調用其他函數。把要調用的函數硬編碼在上層函數里,不利于它們的獨立測試。從而需要把被依賴的函數以參數的形式傳遞給上層函數。 但是,每次調用都傳遞相同的依賴,是不符合 DRY 原則的,這時候,柯里化函數就有用了!柯里化和部分函數應用是函數式編程里依賴注入的幾種方式之一。 下面這個簡化的例子說明了這項技術: ~~~ case class User(name: String) trait EmailRepository { def getMails(user: User, unread: Boolean): Seq[Email] } trait FilterRepository { def getEmailFilter(user: User): EmailFilter } trait MailboxService { def getNewMails(emailRepo: EmailRepository)(filterRepo: FilterRepository)(user: User) = emailRepo.getMails(user, true).filter(filterRepo.getEmailFilter(user)) val newMails: User => Seq[Email] } ~~~ 這個例子有一個依賴兩個不同存儲庫的服務,這些依賴被聲明為 `getNewMails` 方法的參數,并且每個依賴都在一個單獨的參數列表里。 `MailboxService` 實現了這個方法,留空了字段 `newMails`,這個字段的類型是一個函數: `User => Seq[Email]`,依賴于 `MailboxService` 的組件會調用這個函數。 擴展 `MailboxService` 時,實現 `newMails` 的方法就是應用 `getNewMails` 這個方法,把依賴 `EmailRepository` 、 `FilterRepository` 的具體實現傳遞給它: ~~~ object MockEmailRepository extends EmailRepository { def getMails(user: User, unread: Boolean): Seq[Email] = Nil } object MockFilterRepository extends FilterRepository { def getEmailFilter(user: User): EmailFilter = _ => true } object MailboxServiceWithMockDeps extends MailboxService { val newMails: (User) => Seq[Email] = getNewMails(MockEmailRepository)(MockFilterRepository) _ } ~~~ 調用 `MailboxServiceWithMockDeps.newMails(User("daniel")` 無需指定要使用的存儲庫。在實際的應用程序中,這個服務也可能是以依賴的方式被使用,而不是直接引用。 這可能不是最強大、可擴展的依賴注入實現方式,但依舊是一個非常不錯的選擇,對展示部分函數應用和柯里化更廣泛的功用來說,這也是一個不錯的例子。如果你想知道更多關于這一點的知識,推薦看 Debasish Ghosh 的幻燈片“[Dependency Injection in Scala](http://de.slideshare.net/debasishg/dependency-injection-in-scala-beyond-the-cake-pattern)”。 ### 總結 這一章討論了兩個附加的可以避免代碼重復的函數式編程技術,并且在這個基礎上,得到了很大的靈活性,可以用多種不同的形式重用函數。部分函數應用和柯里化,這兩者或多或少都可以實現同樣的效果,只是有時候,其中的某一個會更為優雅。下一章會繼續探討保持靈活性的方法:類型類(type class)。
                  <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>

                              哎呀哎呀视频在线观看