99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

高階函數(shù)與 DRY

2018-02-24 16:00 更新

前幾章介紹了 Scala 容器類(lèi)型的可組合性特征。接下來(lái),你會(huì)發(fā)現(xiàn),Scala 中的一等公民——函數(shù)也具有這一性質(zhì)。

組合性產(chǎn)生可重用性,雖然后者是經(jīng)由面向?qū)ο缶幊潭鵀槿耸熘?,但它也絕對(duì)是純函數(shù)的固有性質(zhì)。(純函數(shù)是指那些沒(méi)有副作用且是引用透明的函數(shù))

一個(gè)明顯的例子是調(diào)用已知函數(shù)實(shí)現(xiàn)一個(gè)新的函數(shù),當(dāng)然,還有其他的方式來(lái)重用已知函數(shù)。這一章會(huì)討論函數(shù)式編程的一些基本原理。你將會(huì)學(xué)到如何使用高階函數(shù),以及重用已有代碼時(shí),遵守 DRY 原則。

高階函數(shù)

和一階函數(shù)相比,高階函數(shù)可以有三種形式:

  1. 一個(gè)或多個(gè)參數(shù)是函數(shù),并返回一個(gè)值。
  2. 返回一個(gè)函數(shù),但沒(méi)有參數(shù)是函數(shù)。
  3. 上述兩者疊加:一個(gè)或多個(gè)參數(shù)是函數(shù),并返回一個(gè)函數(shù)。

看到這里的讀者應(yīng)該已經(jīng)見(jiàn)到過(guò)第一種使用:我們調(diào)用一個(gè)方法,像 mapfilter 、 flatMap ,并傳遞另一個(gè)函數(shù)給它。傳遞給方法的函數(shù)通常是匿名函數(shù),有時(shí)候,還涉及一些代碼冗余。

這一章只關(guān)注另外兩種功能:一個(gè)可以根據(jù)輸入值構(gòu)建新的函數(shù),另一個(gè)可以根據(jù)現(xiàn)有的函數(shù)組合出新的函數(shù)。這兩種情況都能夠消除代碼冗余。

函數(shù)生成

你可能認(rèn)為依據(jù)輸入值創(chuàng)建新函數(shù)的能力并不是那么有用。函數(shù)組合非常重要,但在這之前,還是先來(lái)看看如何使用可以產(chǎn)生新函數(shù)的函數(shù)。

假設(shè)要實(shí)現(xiàn)一個(gè)免費(fèi)的郵件服務(wù),用戶可以設(shè)置對(duì)郵件的屏蔽。我們用一個(gè)簡(jiǎn)單的樣例類(lèi)來(lái)代表郵件:

case class Email(
  subject: String,
  text: String,
  sender: String,
  recipient: String
)

想讓用戶可以自定義過(guò)濾條件,需有一個(gè)過(guò)濾函數(shù)——類(lèi)型為 Email => Boolean 的謂詞函數(shù),這個(gè)謂詞函數(shù)決定某個(gè)郵件是否該被屏蔽:如果謂詞成真,那這個(gè)郵件被接受,否則就被屏蔽掉。

type EmailFilter = Email => Boolean
def newMailsForUser(mails: Seq[Email], f: EmailFilter) = mails.filter(f)

注意,類(lèi)型別名使得代碼看起來(lái)更有意義。

現(xiàn)在,為了使用戶能夠配置郵件過(guò)濾器,實(shí)現(xiàn)了一些可以產(chǎn)生 EmailFilter 的工廠方法:

  val sentByOneOf: Set[String] => EmailFilter =
    senders =>
      email => senders.contains(email.sender)
  val notSentByAnyOf: Set[String] => EmailFilter =
    senders =>
      email => !senders.contains(email.sender)
  val minimumSize: Int => EmailFilter =
    n =>
      email => email.text.size >= n
  val maximumSize: Int => EmailFilter =
    n =>
      email => email.text.size <= n

這四個(gè) vals 都是可以返回 EmailFilter 的函數(shù),前兩個(gè)接受代表發(fā)送者的 Set[String] 作為輸入,后兩個(gè)接受代表郵件內(nèi)容長(zhǎng)度的 Int 作為輸入。

可以使用這些函數(shù)來(lái)創(chuàng)建 EmialFilter

  val emailFilter: EmailFilter = notSentByAnyOf(Set("johndoe@example.com"))
  val mails = Email(
    subject = "It's me again, your stalker friend!",
    text = "Hello my friend! How are you?",
    sender = "johndoe@example.com",
    recipient = "me@example.com") :: Nil
  newMailsForUser(mails, emailFilter) // returns an empty list

這個(gè)過(guò)濾器過(guò)濾掉列表里唯一的一個(gè)元素,因?yàn)橛脩羝帘瘟藖?lái)自 johndoe@example.com 的郵件??梢杂霉S方法創(chuàng)建任意的 EmailFilter 函數(shù),這取決于用戶的需求了。

重用已有函數(shù)

當(dāng)前的解決方案有兩個(gè)問(wèn)題。第一個(gè)是工廠方法中有重復(fù)代碼。上文提到過(guò),函數(shù)的組合特征可以很輕易的保持 DRY 原則,既然如此,那就試著使用它吧!

對(duì)于 minimumSizemaximumSize ,我們引入一個(gè)叫做 sizeConstraint 的函數(shù)。這個(gè)函數(shù)接受一個(gè)謂詞函數(shù),該謂詞函數(shù)檢查函數(shù)內(nèi)容長(zhǎng)度是否OK,郵件長(zhǎng)度會(huì)通過(guò)參數(shù)傳遞給它:

  type SizeChecker = Int => Boolean
  val sizeConstraint: SizeChecker => EmailFilter =
    f =>
      email => f(email.text.size)

這樣,我們就可以用 sizeConstraint 來(lái)表示 minimumSizemaximumSize 了:

  val minimumSize: Int => EmailFilter =
    n =>
      sizeConstraint(_ >= n)
  val maximumSize: Int => EmailFilter =
    n =>
      sizeConstraint(_ <= n)

函數(shù)組合

為另外兩個(gè)謂詞(sentByOneOfnotSentByAnyOf)介紹一個(gè)通用的高階函數(shù),通過(guò)它,可以用一個(gè)函數(shù)去表達(dá)另外一個(gè)函數(shù)。

這個(gè)高階函數(shù)就是 complement ,給定一個(gè)類(lèi)型為 A => Boolean 的謂詞,它返回一個(gè)新函數(shù),這個(gè)新函數(shù)總是得出和謂詞相對(duì)立的結(jié)果:

  def complement[A](predicate: A => Boolean) = (a: A) => !predicate(a)

現(xiàn)在,對(duì)于一個(gè)已有的謂詞 p ,調(diào)用 complement(p) 可以得到它的補(bǔ)。然而, sentByAnyOf 并不是一個(gè)謂詞函數(shù),它返回類(lèi)型為 EmailFilter 的謂詞。

Scala 函數(shù)的可組合能力現(xiàn)在就用的上了:給定兩個(gè)函數(shù) f 、 g , f.compose(g) 返回一個(gè)新函數(shù),調(diào)用這個(gè)新函數(shù)時(shí),會(huì)首先調(diào)用 g ,然后應(yīng)用 fg 的返回結(jié)果上。類(lèi)似的, f.andThen(g) 返回的新函數(shù)會(huì)應(yīng)用 gf 的返回結(jié)果上。

知道了這些,我們就可以重寫(xiě) notSentByAnyOf 了:

  val notSentByAnyOf = sentByOneOf andThen (g => complement(g))

上面的代碼創(chuàng)建了一個(gè)新的函數(shù),這個(gè)函數(shù)首先應(yīng)用 sentByOneOf 到參數(shù) Set[String] 上,產(chǎn)生一個(gè) EmailFilter 謂詞,然后,應(yīng)用 complement 到這個(gè)謂詞上。使用 Scala 的下劃線語(yǔ)法,這短代碼還能更精簡(jiǎn):

  val notSentByAnyOf = sentByOneOf andThen (complement(_))

讀者可能已經(jīng)注意到,給定 complement 函數(shù),也可以通過(guò) minimumSize 來(lái)實(shí)現(xiàn) maximumSize 。不過(guò),先前的實(shí)現(xiàn)方式更加靈活,它允許檢查郵件內(nèi)容的任意長(zhǎng)度。謂

謂詞組合

郵件過(guò)濾器的第二個(gè)問(wèn)題是,當(dāng)前只能傳遞一個(gè) EmailFilternewMailsForUser 函數(shù),而用戶必然想設(shè)置多個(gè)標(biāo)準(zhǔn)。所以需要可以一種可以創(chuàng)建組合謂詞的方法,這個(gè)組合謂詞可以在任意一個(gè)標(biāo)準(zhǔn)滿足的情況下返回 true ,或者在都不滿足時(shí)返回 false

下面的代碼是一種實(shí)現(xiàn)方式:

  def any[A](predicates: (A => Boolean)*): A => Boolean =
    a => predicates.exists(pred => pred(a))
  def none[A](predicates: (A => Boolean)*) = complement(any(predicates: _*))
  def every[A](predicates: (A => Boolean)*) = none(predicates.view.map(complement(_)): _*)

any 函數(shù)返回的新函數(shù)會(huì)檢查是否有一個(gè)謂詞對(duì)于輸入 a 成真。none 返回的是 any 返回函數(shù)的補(bǔ),只要存在一個(gè)成真的謂詞, none 的條件就無(wú)法滿足。最后, every 利用 noneany 來(lái)判定是否每個(gè)謂詞的補(bǔ)對(duì)于輸入 a 都不成真。

可以使用它們來(lái)創(chuàng)建代表用戶設(shè)置的組合 EmialFilter

  val filter: EmailFilter = every(
      notSentByAnyOf(Set("johndoe@example.com")),
      minimumSize(100),
      maximumSize(10000)
    )

流水線組合

再舉一個(gè)函數(shù)組合的例子?;仡櫹律厦娴膱?chǎng)景,郵件提供者不僅想讓用戶可以配置郵件過(guò)濾器,還想對(duì)用戶發(fā)送的郵件做一些處理。這是一些簡(jiǎn)單的 Emial => Email 函數(shù),一些可能的處理函數(shù)是:

  val addMissingSubject = (email: Email) =>
    if (email.subject.isEmpty) email.copy(subject = "No subject")
    else email
  val checkSpelling = (email: Email) =>
    email.copy(text = email.text.replaceAll("your", "you're"))
  val removeInappropriateLanguage = (email: Email) =>
    email.copy(text = email.text.replaceAll("dynamic typing", "**CENSORED**"))
  val addAdvertismentToFooter = (email: Email) =>
    email.copy(text = email.text + "\nThis mail sent via Super Awesome Free Mail")

現(xiàn)在,根據(jù)老板的心情,可以按需配置郵件處理的流水線。通過(guò) andThen 調(diào)用實(shí)現(xiàn),或者使用 Function 伴生對(duì)象上的 chain 方法:

  val pipeline = Function.chain(Seq(
    addMissingSubject,
    checkSpelling,
    removeInappropriateLanguage,
    addAdvertismentToFooter))

高階函數(shù)與偏函數(shù)

這部分不會(huì)關(guān)注細(xì)節(jié),不過(guò),在知道了這么多通過(guò)高階函數(shù)來(lái)組合和重用函數(shù)的方法之后,你可能想再重新看看偏函數(shù)。

鏈接偏函數(shù)

匿名函數(shù)那一章提到過(guò),偏函數(shù)可以被用來(lái)創(chuàng)建責(zé)任鏈:PartialFunction 上的 orElse 方法允許鏈接任意個(gè)偏函數(shù),從而組合出一個(gè)新的偏函數(shù)。不過(guò),只有在一個(gè)偏函數(shù)沒(méi)有為給定輸入定義的時(shí)候,才會(huì)把責(zé)任傳遞給下一個(gè)偏函數(shù)。從而可以做下面這樣的事情:

  val handler = fooHandler orElse barHandler orElse bazHandler

再看偏函數(shù)

有時(shí)候,偏函數(shù)并不合適。仔細(xì)想想,一個(gè)函數(shù)沒(méi)有為所有的輸入值定義操作,這樣的事實(shí)還可以用一個(gè)返回 Option[A] 的標(biāo)準(zhǔn)函數(shù)代替:如果函數(shù)為一個(gè)輸入定義了操作,那就返回 Some[A] ,否則返回 None 。

要這么做的話,可以在給定的偏函數(shù) pf 上調(diào)用 lift 方法得到一個(gè)普通的函數(shù),這個(gè)函數(shù)返回 Option 。反過(guò)來(lái),如果有一個(gè)返回 Option 的普通函數(shù) f ,也可以調(diào)用 Function.unlift(f) 來(lái)得到一個(gè)偏函數(shù)???/p>

總結(jié)

這一章給出了高階函數(shù)的使用,利用它可以在一個(gè)新的環(huán)境里重用已有函數(shù),并用靈活的方式去組合它們。在所舉的例子中,就代碼行數(shù)而言,可能看不出太多價(jià)值,這些例子都很簡(jiǎn)單,只是為了說(shuō)明而已,在架構(gòu)層面,組合和重用函數(shù)是有很大幫助的。

下一章,我們繼續(xù)探索函數(shù)組合的方式:函數(shù)部分應(yīng)用和柯里化(Partial Function Application and Currying)。

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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)