活动模式小例(一)

Applied Active Pattern in F# (1)

原创:顾远山
著作权归作者所有,转载请标明出处。

Active patterns enable you to define named partitions that subdivide input data, so that you can use these names in a pattern matching expression just as you would for a discriminated union. You can use active patterns to decompose data in a customized manner for each partition.

F# Language Reference

活动模式是F#语言的一个特性。根据上述官方语言指南,活动模式允许你通过定义命名分区分割输入数据,继而在模式匹配表达式中像可区分联合那样使用这些名字。你可以使用活动模式给每个分区按定制化的方式分解数据。

这句话读起来多少有点晦涩难懂,命名分区是什么鬼,模式匹配又是什么鬼,可区分联合又是什么鬼,每个字都认识,连在一起就不知道什么意思了。在揭示活动模式的本质之前,我们最好直观地观察一下它是怎么用的,知其然再知其所以然。为了方便理解,以下举个简单的例子,这个例子通过活动模式来判断输入字符串是身份证号码、护照号码还是未知号码。

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

光看上面的代码,感觉用普通函数也能实现类似的功能,似乎普通函数比它甚至还简洁许多,如下:

let isIDNumber input = Regex(@"\d{18}").Match(input).Success
let isPassportNumber input = Regex(@"G|E\d{8}").Match(input).Success
let isUnknownNumber input = not isIDNumber && not isPassportNumber

仔细对比的话,两者还是有区别的。活动模式的返回值不仅包含了对输入数据的分类,还包含了输入数据的拆分(本例是原值);而普通函数只对输入数据进行判断,只返回一个代表是否的布尔型结果,没有分类(甚至都不知道有几类),也没有输入数据(当然如果非要返回元组的话也可以带上输入数据,但就和函数名的意义冲突,显得画蛇添足了)。

如果接下来的业务逻辑是基于号码类型的判断进一步在号码上做处理的话,差异会更明显一些,比如,对于身份证号码则提取出生年份并输出,对于护照号码则把护照类型并输出,对于未知号码则原值输出。使用活动模式可以这么做:

let print input = 
    match input with
    | IDNumber(num) -> printfn "Birth Year: %s" num.Substring(6,4) //这里用的是分解后的值
    | PassportNumber(num) -> 
        match num.StartsWith("G") with
        | true -> printfn "Paper Passport!"
        | _ -> printfn "E passport!"
    | UnknownNumber(num) -> printfn "Unknown Number: %s" num

但我们用刚才的普通函数实现,可以有:

let print' input = 
    match isIDNumber input with
    | true -> printfn "Birth Year: %s" input.Substring(6,4)  //这里用的是原值
    | _ -> 
        match isPassportNumber input with
        | true -> 
            match input.StartsWith("G") with
            | true -> printfn "Paper Passport!"
            | _ -> printfn "E passport!"
        | _ -> printfn "Unknown Number: %s" num

到目前为止看起来也还好,有区别,但不明显。现在我们稍作变化,还是一样的业务逻辑,但把最开始的活动模式修改成以下:

let (|IDBirthInfo|Passport|Unknown|) input = 
    match Regex(@"\d{18}").Match(input).Success with
    | true -> 
        let year = input.Substring(6,4)
        let month = input.Substring(10,2)
        let day = input.Substring(12,2)
        IDBirthInfo(year,month,day)  //分解输入数据并以年,月,日三元组的方式返回值
    | _ ->
        match Regex(@"G|E\d{8}").Match(input).Success with
        | true -> 
            let category = input.Substring(0,1)
            let number = input.Substring(1)
            Passport(category,number)  //分解输入数据并以护照类型,护照号码二元组的方式返回值
        | _ -> Unknown(input)

而基于新改的活动模式,同样的业务逻辑便能简化如下:

let print'' input = 
    match input with
    | IDBirthInfo(year,_,_) -> printfn "Birth Year: %s" year //这里用的是分解后的值
    | Passport("G",_) -> printfn "Paper Passport!" //这里用的是分解后的值进行模式匹配
    | Passport(_,_) ->  printfn "E Passport!"
    | Unknown(num) -> printfn "Unknown Number: %s" num

这时候的活动模式,就和普通函数长得相当不一样了。由于数据分解的方式不一,返回模式的形态也各异,如本例,它返回一个三元组模式,同时又返回一个二元组模式,同时再可以返回原值模式,为后续进行模式匹配提供了极大的灵活性。现实中其他的编程语言,尤其是面向对象的编程语言,相同的函数名需要通过重载才能实现不同的返回值类型(二元组和三元组是不同的数据类型,如C#中的Tuple<T1,T2>Tuple<T1,T2,T3>),而活动模式这种返回值类型不确定的动态情况,在面向对象的编程语言里是罕见的,类似下面的C#代码应该是不能编译通过的:

public static ImpossibleType Parse (string input)
{
    if(isIDNumber(input))
        return new Tuple<string,string,string>(input.Substring(6,4),input.Substring(10,2),input.Substring(12,2));
    
    if(isPassportNumber(input))
        return new Tuple<string,string>(input.Substring(0,1),input.Substring(1));
    
    return input;
}

我们再回来看一眼官方语言指南中那句话:活动模式允许你通过定义命名分区 分割 输入数据,继而在模式匹配表达式中像可区分联合那样使用这些名字。现在应该就很好理解了,命名分区相当于告诉后面的模式匹配这个输入数据是什么(可以是业务人员也能看懂的领域术语),同时分割 提供远比函数更丰富的多样性,相当于告诉后面的应用逻辑这个输入数据可以被怎么利用。综上简言之,活动模式是F#语言的一个特性,它可用于按不同方式把数据分割成不同模式继而被后续模式匹配逻辑所利用,灵活且强大。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容