ZetCode

F# 活动模式

最后修改于 2025 年 5 月 3 日

在本文中,我们将探讨 F# 中的活动模式。活动模式使得模式匹配的能力超越了标准的可辨识联合,功能强大且灵活。

一个活动模式是 F# 中一个强大的特性,它允许开发者定义自定义规则来分解和处理数据,从而扩展了传统的模式匹配。与主要处理内置类型和预定义结构的标准模式匹配不同,活动模式能够以灵活且特定于领域的方式来解释值。这使得它们在处理复杂转换、结构化数据提取以及处理非统一类型的同时保持类型安全方面特别有用。

活动模式有几种形式:

F# 单例活动模式

单例活动模式将输入数据转换为另一种形式。

single_case.fsx
let (|ToUpper|) (s: string) = s.ToUpper()

let greet name =
    match name with
    | ToUpper upper -> printfn "HELLO, %s!" upper

greet "John"
greet "Alice"

我们创建一个简单的活动模式,将字符串转换为大写。

let (|ToUpper|) (s: string) = s.ToUpper()

定义一个转换其输入的单例活动模式。

λ dotnet fsi single_case.fsx
HELLO, JOHN!
HELLO, ALICE!

F# 多例活动模式

多例活动模式将输入数据划分成多种可能性。

multi_case.fsx
let (|Even|Odd|) n =
    if n % 2 = 0 then Even else Odd

let testNumber x =
    match x with
    | Even -> printfn "%d is even" x
    | Odd -> printfn "%d is odd" x

testNumber 4
testNumber 7

我们定义一个活动模式,将数字分类为偶数或奇数。

let (|Even|Odd|) n =

该多例活动模式将输入分为两类。

λ dotnet fsi multi_case.fsx
4 is even
7 is odd

F# 部分活动模式

部分活动模式仅匹配某些输入,返回选项类型。

partial.fsx
let (|Integer|_|) (s: string) =
    match System.Int32.TryParse(s) with
    | true, n -> Some n
    | _ -> None

let parseInput input =
    match input with
    | Integer n -> printfn "Got integer: %d" n
    | _ -> printfn "Not an integer"

parseInput "42"
parseInput "hello"

展示一个用于将字符串解析为整数的部分活动模式。

let (|Integer|_|) (s: string) =

|_| 表示这是一个可能匹配失败的部分活动模式。

λ dotnet fsi partial.fsx
Got integer: 42
Not an integer

F# 参数化活动模式

活动模式除了匹配的值之外,还可以接受额外的参数。

parameterized.fsx
let (|DivisibleBy|_|) divisor n =
    if n % divisor = 0 then Some DivisibleBy else None

let fizzbuzz n =
    match n with
    | DivisibleBy 15 _ -> "FizzBuzz"
    | DivisibleBy 3 _ -> "Fizz"
    | DivisibleBy 5 _ -> "Buzz"
    | _ -> string n

[1..20] |> List.map fizzbuzz |> List.iter (printfn "%s")

用 FizzBuzz 问题演示参数化活动模式。

let (|DivisibleBy|_|) divisor n =

该活动模式同时接受一个除数参数和要匹配的值。

λ dotnet fsi parameterized.fsx
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz

用于类型测试的 F# 活动模式

活动模式可以简化类型测试和转换。

type_test.fsx
let (|IsString|IsInt|IsBool|Other|) (obj: obj) =
    match obj with
    | :? string as s -> IsString s
    | :? int as i -> IsInt i
    | :? bool as b -> IsBool b
    | _ -> Other

let describe obj =
    match obj with
    | IsString s -> printfn "String: %s" s
    | IsInt i -> printfn "Int: %d" i
    | IsBool b -> printfn "Bool: %b" b
    | Other -> printfn "Unknown type"

describe (box "hello")
describe (box 42)
describe (box true)
describe (box 3.14)

展示活动模式如何清晰地处理运行时类型测试。

match obj with
| :? string as s -> IsString s

该活动模式封装了类型测试和转换的逻辑。

λ dotnet fsi type_test.fsx
String: hello
Int: 42
Bool: true
Unknown type

用于 XML 解析的 F# 活动模式

活动模式可以简化处理复杂数据结构的工作。

xml.fsx
open System.Xml.Linq

let (|Elem|_|) name (el: XElement) =
    if el.Name.LocalName = name then Some (el.Elements()) else None

let (|Attr|_|) name (el: XElement) =
    match el.Attribute(XName.Get name) with
    | null -> None
    | attr -> Some attr.Value

let parseBook (el: XElement) =
    match el with
    | Elem "book" children ->
        children |> Seq.iter (fun child ->
            match child with
            | Elem "title" _ -> printfn "Title: %s" (child.Value)
            | Elem "author" _ -> printfn "Author: %s" (child.Value)
            | Elem "price" _ -> printfn "Price: %s" (child.Value)
            | _ -> printfn "Unknown element")
    | _ -> printfn "Not a book element"

let xml = """
<books>
    <book>
        <title>F# in Depth</title>
        <author>John Smith</author>
        <price>45.99</price>
    </book>
</books>
"""

let doc = XDocument.Parse(xml)
doc.Root.Elements() |> Seq.iter parseBook

演示如何使用活动模式来解析和处理 XML 数据。

let (|Elem|_|) name (el: XElement) =

活动模式使 XML 处理代码更具声明性且更易读。

λ dotnet fsi xml.fsx
Title: F# in Depth
Author: John Smith
Price: 45.99

F# 活动模式的组合

活动模式可以组合在一起以实现更复杂的匹配。

composition.fsx
let (|Positive|_|) n = if n > 0 then Some Positive else None
let (|Negative|_|) n = if n < 0 then Some Negative else None
let (|Zero|_|) n = if n = 0 then Some Zero else None

let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd

let describeNumber n =
    match n with
    | Positive & Even -> printfn "%d is positive and even" n
    | Positive & Odd -> printfn "%d is positive and odd" n
    | Negative & Even -> printfn "%d is negative and even" n
    | Negative & Odd -> printfn "%d is negative and odd" n
    | Zero -> printfn "Zero"

describeNumber 4
describeNumber 7
describeNumber -2
describeNumber -3
describeNumber 0

展示如何使用逻辑与 (&) 运算符组合多个活动模式。

Positive & Even

组合两个活动模式来匹配既是正数又是偶数的数字。

λ dotnet fsi composition.fsx
4 is positive and even
7 is positive and odd
-2 is negative and even
-3 is negative and odd
Zero

在本文中,我们探讨了 F# 活动模式的强大功能和灵活性。它们提供了一种将模式匹配扩展到几乎任何场景的方法,同时保持代码的整洁和声明性。

作者

我的名字是 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。至今,我已撰写了超过 1400 篇文章和 8 本电子书。我拥有超过十年的编程教学经验。