活动模式
活动模式可以使你执行一个函数,来看是否发生了匹配。这也是为什么叫活动的原因。活动模式的设计目标是允许你在程序中更好的复用模式匹配逻辑。活动模式接受一个输入参数,使用该参数执行一些计算,决定是否该匹配发生过。有2种活动模式,完全活动模式和分部活动匹配。
 
完全活动模式
定义活动模式的语法和定义函数相似。主要的不同点在于活动模式的标识符有个括号和竖线,(| |)。活动模式的主体就是一个F#函数,该函数必须为每个分支提供返回,每个case可以返回额外的数据,就如联合类型那样。如下的例子:

let
(|Dayu0|Xiaoyu0|Dengyu0|) value= if value<0 then Xiaoyu0 else if value=0 then Dengyu0 else Dayu0 let test input = match input with | Dayu0 -> printfn ">0" | Xiaoyu0-> printfn "<0" | Dengyu0 -> printfn "=0" test 3 test -2 test 0
运行test方法的时候,test内部有类型Dayu0,Xiaoyu0,Dengyu0由于自动推断类型,因此会去活动模式中匹配。依次判断是否是某种类型。再打印出相应的值。
421a7f0d7f98438cb628261ed877e725
 
看下面的例子。
 

#light
open System // 定义活动模式 let (|Bool|Int|Float|String|) input = // attempt to parse a bool let success, res = Boolean.TryParse input if success then Bool(res) else // attempt to parse an int let success, res = Int32.TryParse input if success then Int(res) else // attempt to parse a float (Double) let success, res = Double.TryParse input if success then Float(res) else String(input) // 根据模式打印的函数 // matching over the active pattern let printInputWithType input = match input with | Bool b -> printfn "Boolean: %b" b | Int i -> printfn "Integer: %i" i | Float f -> printfn "Floating point: %f" f | String s -> printfn "String: %s" s // print the results printInputWithType "true" printInputWithType "12" printInputWithType "-12.1"
上述例子的模式就是判断输入的字符串是布尔,整型,浮点型还是字符串。case名字是Bool,Int,Float和String。这个例子使用TryParse方法,轮流的判断输入的值是不是布尔,整型,浮点型,如果都不是,那么就是字符串。如果转换成功,则返回case名字和值。接下来,活动模式允许你对待string好像联合类型那样。你可以匹配每一个case,通过在强类型方式中使用活动模式恢复数据。注意红色字体的,就是活动模式定义的类型。
 
分部活动模式
分部活动模式的语法和完全活动模式类似。分部活动模式只有一个case名字。和完全活动模式不同处在于必须跟随一个|符号和一个下划线,表示是分部的。(和只有一个case 的完全活动模式区别)
 
注意,完全和分部活动模式的主要不同处在于完全活动模式保证能返回一个case,然而活动模式可能匹配失败。所以分部活动模式是option类型。option类型是简单的联合类型,已经内建在F#基类库里。只有2个case,Some和None,定义如下:
type option<'a> =
| Some of 'a
| None
option类型,正如名字的意思,表示值是否存在。所以分部活动模式返回Some和一些数据,表示匹配。或者None表示失败。
所有的活动模式可以带有参数。看如下的例子。
#light
open System.Text.RegularExpressions
// 定义活动模式
let (|Regex|_|) regexPattern input =
// 创建,匹配正则表达式
    let regex = new Regex(regexPattern)
    let regexMatch = regex.Match(input)
// 返回 Some or None
    if regexMatch.Success then
        Some regexMatch.Value
    else
        None

// function to print the results by pattern
// matching over different instances of the
// active pattern
let printInputWithType input =
    match input with
    | Regex "$true|false^" s -> printfn "Boolean: %s" s
    | Regex @"$-?\d+^" s -> printfn "Integer: %s" s
    | Regex "$-?\d+\.\d*^" s -> printfn "Floating point: %s" s
    | _ -> printfn "String: %s" input
// print the results
printInputWithType "true"
printInputWithType "12"
printInputWithType "-12.1"
完全活动模式的行为正如联合类型的一样,当没有case的时候,编译器会报错,一个分部活动模式总是要求一个最终的catch-all case避免编译器报错。然而,分部活动模式有利于你串接多个活动模式。后面的例子会有。
 
度量单位
度量单位允许你把数字分成不同的单位。这个功能可以避免数字操作出错,比如把代表英尺的值和表示厘米的值相加。
定义度量单位,只需要声明一个名字,前面加上熟悉Measure。如下的例子:
[<Measure>]type m
默认的,度量单位通常使用在浮点型的值上,也就是System.Double,创建带单位的值,只需要用尖括号即可。
let meters = 1.0
 
如下的代码:
[<Measure>]type liter
[<Measure>]type pint
let vol1 = 2.5
let vol2 = 2.5
let newVol = vol1 + vol2
编译出错:
57eb2623f3f8454b930a1ae589cc517e
因为不同的单位不允许相加。但是不同单位之间的乘法和除法的运算是可以的,并且会创建一个新的度量单位。比如下面代码:
let ratio = 1.0 / 1.76056338
 
标识符ratio的类型是float,明确的表达出了升和品脱之间的比率。此外,类型是float的值乘以值float,得到的类型就是float。这意味着你可以写出如下的代码: 
 
#light
[<Measure>]type liter
[<Measure>]type pint

let vol1 = 2.5
let vol2 = 2.5
// define the ratio of pints to liters
let ratio = 1.0 / 1.76056338
// a function to convert pints to liters
let convertPintToLiter pints =
    pints * ratio
// perform the conversion and add the values
let newVol = vol1 + (convertPintToLiter vol2)

printfn "%A" newVol