自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文讀懂Scala中的偏函數(shù)

開發(fā) 開發(fā)工具
藝術(shù)地說,Scala中的Partial Function就是一個(gè)“殘缺”的函數(shù),Partial Function做不到以“偏”概全,因而需要將多個(gè)偏函數(shù)組合,最終才能達(dá)到全面覆蓋的目的。

藝術(shù)地說,Scala中的Partial Function就是一個(gè)“殘缺”的函數(shù),就像一個(gè)嚴(yán)重偏科的學(xué)生,只對某些科目感興趣,而對沒有興趣的內(nèi)容棄若蔽履。Partial Function做不到以“偏”概全,因而需要將多個(gè)偏函數(shù)組合,最終才能達(dá)到全面覆蓋的目的。所以這個(gè)Partial Function確實(shí)是一個(gè)“部分”的函數(shù)。

一文讀懂Scala中的偏函數(shù)

對比Function和Partial Function,更學(xué)術(shù)味的解釋如下:

  • 對給定的輸入?yún)?shù)類型,函數(shù)可接受該類型的任何值。換句話說,一個(gè)(Int) => String 的函數(shù)可以接收任意Int值,并返回一個(gè)字符串。
  • 對給定的輸入?yún)?shù)類型,偏函數(shù)只能接受該類型的某些特定的值。一個(gè)定義為(Int) => String 的偏函數(shù)可能不能接受所有Int值為輸入。

在Scala中,所有偏函數(shù)的類型皆被定義為PartialFunction[-A, +B]類型,PartialFunction[-A, +B]又派生自Function1。由于它僅僅處理輸入?yún)?shù)的部分分支,因而它通過isDefineAt()來判斷輸入值是否應(yīng)該由當(dāng)前偏函數(shù)進(jìn)行處理。PartialFunction的定義如下所示:

  1. trait PartialFunction[-A, +B] extends (A => B) { self => 
  2.   import PartialFunction._ 
  3.   def isDefinedAt(x: A): Boolean 
  4.   def applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1 = 
  5.     if (isDefinedAt(x)) apply(x) else default(x) 

既然偏函數(shù)僅處理部分分支,自然可以與模式匹配結(jié)合起來。case語句從本質(zhì)上講就是PartialFunction的子類。當(dāng)我們定義了如下值:

  1. val p:PartialFunction[Int, String] = { case 1 => "One" } 

實(shí)際上就是創(chuàng)建了一個(gè)PartialFunction[Int, String]的子類,其中isDefineAt方法提供類似這樣的實(shí)現(xiàn):

  1. def isDefineAt(x: Int):Boolean = x == 1 

當(dāng)我們通過p(1)去調(diào)用該偏函數(shù)時(shí),就相當(dāng)于調(diào)用了Int => String函數(shù)的apply()方法,從而返回轉(zhuǎn)換后的值“one”。如果傳入的參數(shù)使得isDifineAt返回false,就會拋出MatchError異常。追本溯源,是因?yàn)檫@里對偏函數(shù)值的調(diào)用,實(shí)則是調(diào)用了AbstractPartialFunction的apply()方法(case語句相當(dāng)于是繼承AbstractPartialFunction的子類):

  1. abstract class AbstractPartialFunction[ 
  2. @specialized(scala.Int, scala.Long, scala.Float, scala.Double, scala.AnyRef) -T1,  
  3. @specialized(scala.Unit, scala.Boolean, scala.Int, scala.Float, scala.Long, scala.Double, scala.AnyRef) +R]  
  4. extends Function1[T1, R] with PartialFunction[T1, R] { self => 
  5.     def apply(x: T1): R = applyOrElse(x, PartialFunction.empty) 

apply()方法內(nèi)部調(diào)用了PartialFunction的applyOrElse()方法。若isDefineAt(x)返回為false,就會將x值傳遞給PartialFunction.empty。這個(gè)empty等于類型為PartialFunction[Any, Nothong]的值empty_pf,定義如下:

  1. private[this] val empty_pf: PartialFunction[Any, Nothing] = new PartialFunction[Any, Nothing] { 
  2.    def isDefinedAt(x: Any) = false 
  3.    def apply(x: Any) = throw new MatchError(x) 
  4.    override def orElse[A1, B1](that: PartialFunction[A1, B1]) = that 
  5.    override def andThen[C](k: Nothing => C) = this 
  6.    override val lift = (x: Any) => None 
  7.    override def runWith[U](action: Nothing => U) = constFalse 
  8.  } 

這正是執(zhí)行p(2)會拋出MatchError的由來。

為什么要用偏函數(shù)呢?以我個(gè)人愚見,還是一個(gè)重用粒度的問題。函數(shù)式的編程思想是以一種“演繹法”而非“歸納法”去尋求解決空間。也就是說,它并不是要去歸納問題然后分解問題并解決問題,而是看透問題本質(zhì),定義最原初的操作和組合規(guī)則,面對問題時(shí),可以通過組合各種函數(shù)去解決問題,這也正是“組合子(combinator)”的含義。偏函數(shù)則更進(jìn)一步,將函數(shù)求解空間中各個(gè)分支也分離出來,形成可以被組合的偏函數(shù)。

偏函數(shù)中最常見的組合方法為orElse、andThen與compose。orElse相當(dāng)于一個(gè)或運(yùn)算,如果通過它將多個(gè)偏函數(shù)組合起來,就相當(dāng)于形成了多個(gè)case合成的模式匹配。倘若所有偏函數(shù)滿足了輸入值的所有分支,組合起來就形成一個(gè)函數(shù)了。例如寫一個(gè)求絕對值的運(yùn)算,就可以利用偏函數(shù):

  1. val positiveNumber:PartialFunction[Int, Int] = {  
  2.     case x if x > 0 => x  
  3. val zero:PartialFunction[Int, Int] = {  
  4.     case x if x == 0 => 0  
  5. val negativeNumber:PartialFunction[Int, Int] = {  
  6.     case x if x < 00 => -x  
  7. def abs(x: Int): Int = { 
  8.     (positiveNumber orElse zero orElse negativeNumber)(x) 

利用orElse組合時(shí),還可以直接組合case語句,例如:

  1. val pf: PartialFunction[Int, String] = { 
  2.   case i if i%2 == 0 => "even" 
  3. val tf: (Int => String) = pf orElse { case _ => "odd" } 

orElse被定義在PartialFunction類型中,而andThen與compose卻不同,它們實(shí)則被定義在Function中,PartialFunction只是重寫了這兩個(gè)方法。這意味著函數(shù)之間的組合可以使用andThen與compose,偏函數(shù)也可以。這兩個(gè)方法的功能都是將多個(gè)(偏)函數(shù)組合起來形成一個(gè)新函數(shù),只是組合的順序不同,andThen是組合***個(gè),接著是第二個(gè),依次類推;而compose則順序相反。

利用andThen組合偏函數(shù),設(shè)計(jì)本質(zhì)接近Pipe-and-Filter模式,每個(gè)偏函數(shù)都可以理解為是一個(gè)Filter。因?yàn)橐獙⑦@些偏函數(shù)組合起來形成一個(gè)管道,這就要求被組合的偏函數(shù)其輸入值與輸出值必須支持可串接,即上一個(gè)偏函數(shù)的輸出值會作為下一個(gè)偏函數(shù)的輸入值。對比orElse,則有所不同,orElse要求組合的所有偏函數(shù)必須是同樣類型的偏函數(shù)定義,例如都是Int => String,或者String => CustomizedClass。

在PartialFunction中,andThen方法返回的是一個(gè)名為AndThen的偏函數(shù):

  1. trait PartialFunction[-A, +B] extends (A => B) { 
  2.   override def andThen[C](k: B => C): PartialFunction[A, C] = 
  3.     new AndThen[A, B, C] (this, k) 
  4. object PartialFunction { 
  5.   private class AndThen[-A, B, +C] (pf: PartialFunction[A, B], k: B => C) extends PartialFunction[A, C] { 
  6.     def isDefinedAt(x: A) = pf.isDefinedAt(x) 
  7.     def apply(x: A): C = k(pf(x)) 
  8.     override def applyOrElse[A1 <: A, C1 >: C](x: A1, default: A1 => C1): C1 = { 
  9.       val z = pf.applyOrElse(x, checkFallback[B]) 
  10.       if (!fallbackOccurred(z)) k(z) else default(x) 
  11.     } 
  12.   } 

注意看,andThen接收的參數(shù)為k: B => C,即函數(shù)類型而非偏函數(shù)類型。當(dāng)然,由于偏函數(shù)繼承自函數(shù),它也可以組合偏函數(shù)。如果andThen組合了偏函數(shù),則要求輸入?yún)?shù)必須滿足所有參與組合的偏函數(shù),否則就會拋出MatchError錯(cuò)誤。例如編寫一個(gè)函數(shù),要求將字符串中的數(shù)字替換為對應(yīng)的英文單詞,則可以實(shí)現(xiàn)為:

  1. val p1:PartialFunction[String, String] = {  
  2.     case s if s.contains("1") => s.replace("1", "one")  
  3. val p2:PartialFunction[String, String] = {  
  4.     case s if s.contains("2") => s.replace("2", "two")  
  5. val p = p1 andThen p2 

如果調(diào)用p("123"),返回結(jié)果為"onetwo3",但如果傳入p("13"),由于p2偏函數(shù)的isDefineAt返回false,就會拋出MatchError錯(cuò)誤。

偏函數(shù)可以用在很多場景。例如我們可以利用orElse之類的語義,編寫DSL風(fēng)格的代碼,使其更加靈活且可讀。《DSL in Action》一書中就是用了orElse來處理金融行業(yè)的需求:

  1. val forHKG:PartialFunction[Market, List[TaxFee]] = ... 
  2. val forSGP:PartialFunction[Market, List[TaxFee]] = ... 
  3. val forAll:PartialFunction[Market, List[TaxFee]] = ... 
  4.  
  5. def forTrade(trade: Trade): List[TaxFee] =  
  6.     (forHKG orElse forSGP orElse forAll)(trade.market) 

也可以有效地利用偏函數(shù)的開放性,使得API的調(diào)用者可以根據(jù)具體的需求場景傳入自己的case語句。例如Twitter的Effective Scala給出的案例:

  1. trait Publisher[T] { 
  2.   def subscribe(f: PartialFunction[T, Unit]) 
  3. val publisher: Publisher[Int] = ... 
  4.  
  5. publisher.subscribe { 
  6.   case i if isPrime(i) => println("found prime", i) 
  7.   case i if i%2 == 0 => count += 2 
  8.   /* ignore the rest */ 

定義在AKKA的Actor中的receive()方法也是一個(gè)偏函數(shù):

  1. trait Actor { 
  2.     type Receive = Actor.Receive 
  3.     def receive: Actor.Receive 
  4. object Actor { 
  5.   type Receive = PartialFunction[Any, Unit] 

由于偏函數(shù)繼承自函數(shù),因而,如果一個(gè)方法要求接收函數(shù),那么它也可以接收偏函數(shù)。例如我們常常使用的map、filter等方法,就可以接收偏函數(shù):

  1. val sample = 1 to 10 
  2. sample map { 
  3.     case x if x % 2 == 0 => x + " is even" 
  4.     case x if x % 2 == 1 => x + " is odd" 

在Twitter的Effetive Scala中,給出了一個(gè)使用map的編碼風(fēng)格建議:

  1. //avoid 
  2. list map { item => 
  3.   item match { 
  4.     case Some(x) => x 
  5.     case None => default 
  6.   } 
  7. //recommend 
  8. list map { 
  9.   case Some(x) => x 
  10.   case None => default 

從本質(zhì)上講,假設(shè)這個(gè)list的類型為List[Option[String]],則前者傳給map的其實(shí)是一個(gè)形如Option[String] => String的函數(shù),后者則通過case語句創(chuàng)建了PartialFunction[Option[String], String]的實(shí)例傳遞給了map。

【本文為51CTO專欄作者“張逸”原創(chuàng)稿件,轉(zhuǎn)載請聯(lián)系原作者】

戳這里,看該作者更多好文

責(zé)任編輯:趙寧寧 來源: 51CTO專欄
相關(guān)推薦

2009-07-22 07:42:00

Scala偏應(yīng)用函數(shù)

2022-04-20 11:10:17

bias推薦系統(tǒng)debias

2023-12-22 19:59:15

2021-08-04 16:06:45

DataOps智領(lǐng)云

2018-09-28 14:06:25

前端緩存后端

2022-09-22 09:00:46

CSS單位

2025-04-03 10:56:47

2022-11-06 21:14:02

數(shù)據(jù)驅(qū)動(dòng)架構(gòu)數(shù)據(jù)

2019-12-17 08:16:04

JavaScriptthis編程

2021-09-04 19:04:14

配置LogbackJava

2023-05-20 17:58:31

低代碼軟件

2023-11-27 17:35:48

ComponentWeb外層

2022-10-20 08:01:23

2022-07-26 00:00:03

語言模型人工智能

2021-12-29 18:00:19

無損網(wǎng)絡(luò)網(wǎng)絡(luò)通信網(wǎng)絡(luò)

2022-07-05 06:30:54

云網(wǎng)絡(luò)網(wǎng)絡(luò)云原生

2022-12-01 17:23:45

2018-10-18 11:00:50

人工智能機(jī)器學(xué)習(xí)模型偏差

2023-11-21 09:41:00

緩存策略存儲

2021-02-05 05:26:33

字節(jié)ASCII控制
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號