活動模式小結(jié)

Recap Active Pattern in F#

原創(chuàng):顧遠(yuǎn)山
著作權(quán)歸作者所有侠草,轉(zhuǎn)載請標(biāo)明出處。

筆者早前針對F#中活動模式的應(yīng)用列舉了五個小例犁嗅,分別為活動模式的簡介(小例一)边涕、以及把活動模式應(yīng)用到各類數(shù)據(jù)的解析場景,包括日期(小例二)褂微、指令(小例三)功蜓、網(wǎng)頁(小例四)和結(jié)構(gòu)化數(shù)據(jù)(小例五)等。然而很多時候知其然是不夠的宠蚂,為了知其所以然式撼,筆者對活動模式進(jìn)行了簡單的小結(jié),分為三個部分:活動模式的由來求厕,活動模式的分類著隆,以及活動模式的本質(zhì),以便大家能深刻理解活動模式并靈活自如地把它應(yīng)用到項目實踐中呀癣。

活動模式的由來

模式匹配對于函數(shù)式編程語言來說是標(biāo)配美浦,參考F#語言指南:模式匹配我們可以發(fā)現(xiàn),F(xiàn)#中的模式匹配只支持以下十七種模式:

模式匹配

眾所周知项栏,以上有限的模式匹配與抽象的交互很差浦辨,所以F#的設(shè)計者們決定引入活動模式,以支持對通用異構(gòu)數(shù)據(jù)的抽象表示進(jìn)行模式匹配沼沈』珉梗活動模式在設(shè)計上結(jié)合了完全分解數(shù)據(jù)的視圖功能和部分分解數(shù)據(jù)的其他匹配功能案腺,在實現(xiàn)上則是基于F#語言一個簡單輕巧的語法擴展,其官方基本語法如下:

// Active pattern of one choice.
let (|identifier|) [arguments] valueToMatch = expression

// Active Pattern with multiple choices.
// Uses a FSharp.Core.Choice<_,...,_> based on the number of case names. 
// In F#, the limitation n <= 7 applies.
let (|identifer1|identifier2|...|) valueToMatch = expression

// Partial active pattern definition.
// Uses a FSharp.Core.option<_> to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments ] valueToMatch = expression

活動模式的分類

上文提到活動模式適用三種語法康吵,但從實踐角度我們習(xí)慣把它分為四類:

  • 單例完全模式
  • 多例完全模式
  • 單例部分模式
  • 參數(shù)部分模式

現(xiàn)針對其語法及適用場景逐個分述劈榨。

單例完全模式 (Single-case Total Pattern)

單例完全模式語法為:let (|ActivePatternName|) input = ...

活動模式需要被(||)進(jìn)行定義晦嵌,(||)又俗稱"香蕉夾"同辣,其中(||)之間的字面量則是活動模式的名稱。因為是單例完全惭载,所以香蕉夾之間只有一個字面量旱函。如下例:

type Account = {Phone:string; Password:string; Name:string; Address:string; Email:string}
let (|LoginCredentials|) user = (user.Phone,user.Password)

LoginCredentials就是活動模式的名字,其目的是從Account類型的記錄中抽取PhonePassword字段組成二元組并返回描滔。

單例完全模式適合“視圖”功能的場景棒妨,當(dāng)需要從已有類型中抽取特定的字段進(jìn)行組合及計算時,可使用單例完全模式含长。再如下段代碼中的Area活動模式:

type Shape = 
    | Circle of Radius:float
    | Rectangle of Width:float * Height:float    
let (|Area|) shape = 
    match shape with
    | Circle radius -> radius * radius * 3.14
    | Rectangle (width,height) -> width * height

多例完全模式 (Multiple-case Total Pattern)

多例完全模式的語法為:let (|APN1|APN2|APN3|...|APNm|) input = ...券腔,其中字面量APN1...APNm是所有活動模式的名稱。由于是多例完全拘泞,所以香蕉夾里有m個字面量纷纫。如以前舉過的例子:

let (|IDNumber|PassportNumber|UnknownNumber|) input = 
    match Regex(@"\d{18}").Match(input).Success with
    | true -> IDNumber
    | _ ->
        match Regex(@"G|E\d{8}").Match(input).Success with
        | true -> PassportNumber
        | _ -> UnknownNumber

上述活動模式對數(shù)據(jù)進(jìn)行判斷及分類:

  • 若輸入字符串是18位數(shù)字,理解為身份證號碼陪腌,返回IDNumber模式
  • 若輸入字符串以GE開頭辱魁,后接8位數(shù)字,理解為護(hù)照號碼诗鸭,返回PassportNumber模式
  • 若輸入字符串不滿足上述條件染簇,理解為未知號碼,返回UnknownNumber模式

多例完全模式適合條件判斷的場景强岸,當(dāng)某個輸入要么是甲要么是乙要么是丙要么是丁時剖笙,可使用多例完全模式。再如以下代碼中的``活動模式:

let (|ADD|SUB|MUL|DIV|REM|) input = 
    let elements = Regex(@"(\d+)\s*([\+\-\*\/\%])\s*(\d+)").Match(input).Groups
    let operand1 = elements.[1].Value
    let operator = elements.[2].Value
    let operand2 = elements.[3].Value    
    match operator with
    | "+" -> ADD(operand1,operand2)
    | "-" -> SUB(operand1,operand2)
    | "*" -> MUL(operand1,operand2)
    | "/" -> DIV(operand1,operand2)
    | "%" -> REM(operand1,operand2)

上述活動模式把諸如"12 + 5""100 % 3"之類的字符串通過正則表達(dá)式轉(zhuǎn)換成抽象語法樹片段请唱。

單例部分模式 (Single-case Partial Pattern)

單例部分模式的語法為:let (|ActivePatternName|_|) input = ...库糠。

正如香蕉夾中的內(nèi)容所示被济,單例部分模式由兩種(且只有兩種)模式組合而成懦胞,其中第二種模式總是為通配符蛛淋,實際上單例部分模式可以理解為多例完全模式在二維上的一個特例坠韩。

比如常見的整型解析雀哨,如下:

let (|ParseInt|_|) input = 
    match Int32.TryParse(input.ToString()) with
    | true, i -> Some (i)
    | _ -> None

單例部分模式非常適合處理Option類型值的場景埋凯,可提高函數(shù)式代碼的純粹性锅减,是替代傳統(tǒng)try...with異常處理的良好實踐脆诉。

從活動模式設(shè)計者發(fā)表的論文易知甚亭,他們沒有發(fā)掘到多例部分模式的好處贷币,所以F#并不支持多例部分模式。

參數(shù)部分模式 (Parameterized Partial Pattern)

參數(shù)部分模式的語法為:let (|ActivePatternName|_|) param1 ... paramN input = ...亏狰。

參數(shù)部分模式是單例部分模式的擴展役纹,在活動模式名稱和輸入之間,可定義一到多個參數(shù)暇唾,其中param1 ... paramN是參數(shù)的名稱促脉。如在小例中應(yīng)用多次的正則表達(dá)式匹配活動模式:

let (|RegexMatch|_|) pattern input =
      match input with
      | null -> None
      | _    -> let m = Regex.Match(input, pattern)
                match m.Success with
                | false -> None
                | _     -> Some ([for x in m.Groups -> x.Value] |> List.tail)

上述活動模式通過pattern這個參數(shù)對輸入字符串input進(jìn)行正則表達(dá)式匹配:

  • 若輸入字符串為空,返回Option類型值None
  • 若輸入字符串不為空策州,且不能被正則表達(dá)式通過pattern進(jìn)行匹配瘸味,返回Option類型值None
  • 若輸入字符串不為空,且能被正則表達(dá)式通過pattern進(jìn)行匹配够挂,返回所有匹配組的值列表

參數(shù)部分模式為實際的項目應(yīng)用提供了極大的靈活性旁仿,但活動模式的參數(shù)化會導(dǎo)致模式匹配同一性的損失,因此編譯器無法對其執(zhí)行冗余或完整性分析孽糖,所以在同一個match塊內(nèi)枯冈,就算每個模式在語法上都有相同的參數(shù),它們出現(xiàn)的時候仍需要被重新求值梭姓。比如之前演示過的日期解析器代碼:

let parseDate dstr = 
      let p1 = @"^(\d{4}|\d{2})([/\-\.])(\d{1,2})\2(\d{1,2})$"
      let p2 = @"^(\d{1,2})([/\-\.])(.+?)\2(\d{4}|\d{2})$"
      let p3 = @"^(.+?)\s(\d{1,2})\,\s*(\d{4}|\d{2})$"
      let p4 = @"^(.+?)\s(\d{1,2}).{2}\,\s*(\d{4}|\d{2})$"
      let p5 = @"^(\d{4}|\d{2})年(\d{1,2})月(\d{1,2})日$"
      match dstr with
      | RegexMatch p1 [_;Year y;_;Month m;Day d] 
      | RegexMatch p2 [_;Day d;_;Month m;Year y]
      | RegexMatch p3 [_;Month m;Day d;Year y]
      | RegexMatch p4 [_;Month m;Day d;Year y]
      | RegexMatch p5 [_;Year y;Month m;Day d]
          -> Some {Year=y; Month=m; Day=d}
      | _ -> None

雖然match塊內(nèi)只有五個形式一致的RegexMatch p [...]模式霜幼,但由于參數(shù)化的緣故,每一個模式都會被求值誉尖,另外罪既,最后也需要手動添加通配符模式以保證匹配的完整性。

活動模式的本質(zhì)

無論是單例完全模式铡恕、多例完全模式琢感、單例部分模式還是參數(shù)部分模式,嚴(yán)格意義上來說探熔,都是針對F#模式匹配的語言擴展驹针,也就是語法糖,究其本質(zhì)诀艰,所有的活動模式都是函數(shù)柬甥,只是類型有異,如下:

活動模式

在函數(shù)式編程語言中其垄,函數(shù)是頭等公民苛蒲。活動模式在F#中本質(zhì)就是函數(shù)绿满,所以自然也屬于頭等公民臂外。既然活動模式是頭等公民,那它就可以被當(dāng)成“值”用于“值”可用的地方。略舉二例如下:

  • 活動模式作為函數(shù)的參數(shù)

    復(fù)用單例完全模式中的LoginCredentials活動模式漏健,易得login函數(shù):

    let login (LoginCredentials(phone,password)) = ...
    

    該函數(shù)的簽名為Account -> unit 嚎货,它接受一個Account類型的參數(shù),但此參數(shù)在函數(shù)定義時就被活動模式LoginCredentials分解為phonepassword兩個值蔫浆,函數(shù)體內(nèi)可直接使用這兩個值殖属,而Account類型的參數(shù)中其他字段值則被函數(shù)忽略。此例再次演示了單例完全模式常被用作數(shù)據(jù)篩選的“視圖”克懊。

  • 活動模式作為值參與運算

    當(dāng)活動模式在模式匹配中被用于條件判斷時忱辅,可以帶上輸出作為值進(jìn)行組合運算,如下例:

    let (|DividedBy|) d y = y % d = 0
    let isLeapYear year = 
        match year with
        | DividedBy 400 true -> true
        | DividedBy 4 true & DividedBy 100 false -> true
        | _ -> false
    

    其中DividedBy 4 trueDividedBy 100 false兩個模式進(jìn)行了與運算谭溉,而模式匹配本身就是或運算墙懂。

結(jié)語

  • 活動模式是F#中針對通用異構(gòu)數(shù)據(jù)的抽象表示基于模式匹配的語言擴展;

  • 活動模式可分為單例完全模式扮念、多例完全模式损搬、單例部分模式和參數(shù)部分模式四種類型;

  • 活動模式是F#中的頭等公民柜与,其本質(zhì)是函數(shù)巧勤,可作為參數(shù)傳入高階函數(shù),亦可帶輸出作為值進(jìn)行組合運算弄匕。

參考資料

Extensible Pattern Matching Via a Lightweight Language Extension
(Nearly) Everything You Ever Wanted to Know About F# Active Patterns
F#語言指南:活動模式
活動模式小例(一)
活動模式小例(二)
活動模式小例(三)
活動模式小例(四)
活動模式小例(五)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颅悉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子迁匠,更是在濱河造成了極大的恐慌剩瓶,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件城丧,死亡現(xiàn)場離奇詭異延曙,居然都是意外死亡,警方通過查閱死者的電腦和手機亡哄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門枝缔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蚊惯,你說我怎么就攤上這事愿卸。” “怎么了截型?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵趴荸,是天一觀的道長。 經(jīng)常有香客問我菠劝,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任赶诊,我火速辦了婚禮笼平,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舔痪。我一直安慰自己寓调,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布锄码。 她就那樣靜靜地躺著夺英,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滋捶。 梳的紋絲不亂的頭發(fā)上痛悯,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音重窟,去河邊找鬼载萌。 笑死,一個胖子當(dāng)著我的面吹牛巡扇,可吹牛的內(nèi)容都是我干的扭仁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼厅翔,長吁一口氣:“原來是場噩夢啊……” “哼乖坠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起刀闷,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤熊泵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后涩赢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戈次,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年筒扒,在試婚紗的時候發(fā)現(xiàn)自己被綠了怯邪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡花墩,死狀恐怖悬秉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情冰蘑,我是刑警寧澤和泌,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站祠肥,受9級特大地震影響武氓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一县恕、第九天 我趴在偏房一處隱蔽的房頂上張望东羹。 院中可真熱鬧,春花似錦忠烛、人聲如沸属提。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冤议。三九已至,卻和暖如春师坎,著一層夾襖步出監(jiān)牢的瞬間恕酸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工屹耐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尸疆,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓惶岭,卻偏偏與公主長得像寿弱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子按灶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Applied Active Pattern in F# (5) 原創(chuàng):顧遠(yuǎn)山著作權(quán)歸作者所有症革,轉(zhuǎn)載請標(biāo)明出處。 ...
    顧遠(yuǎn)山閱讀 323評論 0 5
  • Applied Active Pattern in F# (2) 原創(chuàng):顧遠(yuǎn)山著作權(quán)歸作者所有鸯旁,轉(zhuǎn)載請標(biāo)明出處噪矛。 ...
    顧遠(yuǎn)山閱讀 371評論 0 5
  • Applied Active Pattern in F# (4) 原創(chuàng):顧遠(yuǎn)山著作權(quán)歸作者所有,轉(zhuǎn)載請標(biāo)明出處铺罢。 ...
    顧遠(yuǎn)山閱讀 306評論 0 5
  • Applied Active Pattern in F# (3) 原創(chuàng):顧遠(yuǎn)山著作權(quán)歸作者所有艇挨,轉(zhuǎn)載請標(biāo)明出處。 ...
    顧遠(yuǎn)山閱讀 447評論 0 5
  • Applied Active Pattern in F# (1) 原創(chuàng):顧遠(yuǎn)山著作權(quán)歸作者所有韭赘,轉(zhuǎn)載請標(biāo)明出處缩滨。 ...
    顧遠(yuǎn)山閱讀 270評論 0 2