ZetCode

F# 匹配表达式

最后修改日期:2025 年 5 月 17 日

本文将探讨如何在 F# 中有效使用匹配表达式来增强分支逻辑和数据处理。

匹配表达式通过将表达式与预定义的模式集进行比较,实现结构化分支。每个可能的输出称为一个分支,允许开发人员高效地处理数据。匹配表达式可用于轻松地转换输入、分解复杂数据结构或提取特定元素。

F# 匹配表达式依赖于 matchwithwhen 关键字。match 关键字启动模式匹配过程,with 定义可能的匹配分支,when 引入额外的条件过滤器来细化匹配。这种语法提供了一种简洁而富有表现力的方式来处理条件逻辑,而无需过多的嵌套语句。

匹配常量模式

基本模式是常量、整数、字符串或枚举。

main.fsx
let word = "falcon"

let langs =
    [ "Slovak"
      "German"
      "Hungarian"
      "Russian"
      "French" ]

for lang in langs do

    let res =
        match lang with
        | "Slovak" -> "sokol"
        | "German" -> "Falke"
        | "Hungarian" -> "sólyom"
        | "Russian" -> "сокол"
        | "French" -> "faucon"
        | _ -> "unknown"

    printfn $"{word} in {lang} is {res}"

该程序打印出“猎鹰”一词在几种语言中的翻译。

let res =
    match lang with
    | "Slovak" -> "sokol"
    | "German" -> "Falke"
    | "Hungarian" -> "sólyom"
    | "Russian" -> "сокол"
    | "French" -> "faucon"
    | _ -> "unknown"

匹配表达式会返回值。匹配表达式中的每个分支都以 | 开头。字符串模式跟在 | 字符后面。返回值在 -> 之后指定。通配符匹配 _ 为未识别的选项返回值。

λ dotnet fsi main.fsx
falcon in Slovak is sokol
falcon in German is Falke
falcon in Hungarian is sólyom
falcon in Russian is сокол
falcon in French is faucon

匹配枚举

在下一个示例中,我们匹配枚举。

main.fsx
open System

type Day =
    | Monday
    | Tuesday
    | Wednesday
    | Thursday
    | Friday
    | Saturday
    | Sunday


let days = [ Monday; Tuesday; Wednesday; Thursday; 
    Friday; Saturday; Sunday ]

let rnd = new Random()

let res = days 
        |> Seq.sortBy (fun _ -> rnd.Next()) 
        |> Seq.take 3

for e in res do

    match e with
        | Monday -> printfn "%s" "monday"
        | Tuesday ->  printfn "%s" "tuesday"
        | Wednesday ->  printfn "%s" "wednesay"
        | Thursday ->  printfn "%s" "thursday"
        | Friday ->  printfn "%s" "friday"
        | Saturday ->  printfn "%s" "saturday"
        | Sunday ->  printfn "%s" "sunday"

我们定义了一个 Day 枚举。我们随机选择三个 Day 枚举。

match e with
    | Monday -> printfn "%s" "monday"
    | Tuesday ->  printfn "%s" "tuesday"
    | Wednesday ->  printfn "%s" "wednesay"
    | Thursday ->  printfn "%s" "thursday"
    | Friday ->  printfn "%s" "friday"
    | Saturday ->  printfn "%s" "saturday"
    | Sunday ->  printfn "%s" "sunday"

在此匹配表达式中,我们匹配枚举。在这里我们不返回值;我们打印消息。

λ dotnet fsi main.fsx
monday
friday
tuesday

F# 匹配守卫

守卫是必须在分支内满足的条件。守卫是通过 when 创建的。

main.fsx
let vals = [ 1; -3; 5; 6; 0; 4; -9; 11; 22; -7 ]

for wal in vals do

    match wal with
    | n when n < 0 -> printfn "%d is negative" n
    | n when n > 0 -> printfn "%d is positive" n
    | _ -> printfn "zero"

通过模式匹配,我们将值分类为负值、正值和零值。

| _ -> printfn "zero"

通过这一点,我们创建了一个详尽的匹配。

λ dotnet fsi main.fsx
1 is positive
-3 is negative
5 is positive
6 is positive
zero
4 is positive
-9 is negative
11 is positive
22 is positive
-7 is negative

F# 匹配多个选项

可以使用 | 组合多个选项。

main.fsx
let grades = ["A"; "B"; "C"; "D"; "E"; "F"; "FX"]

for grade in grades do

    match grade with
    | "A" | "B" | "C" | "D" | "E" | "F" -> printfn "%s" "passed"
    | _ -> printfn "%s" "failed"

我们将分数分为两类:及格和不及格。及格分支使用 | 组合所有匹配的值。

λ dotnet fsi main.fsx
passed
passed
passed
passed
passed
passed
failed

F# 匹配记录

在下一个示例中,我们匹配记录。

main.fsx
type User =
    { FirstName: string
      LastName: string
      Occupation: string }

let users =
    [ { FirstName = "John"
        LastName = "Doe"
        Occupation = "gardener" }
      { FirstName = "Jane"
        LastName = "Doe"
        Occupation = "teacher" }
      { FirstName = "Roger"
        LastName = "Roe"
        Occupation = "driver" } ]

for user in users do
    match user with
    | { LastName = "Doe" } -> printfn "%A" user
    | _ -> ()

我们有几个用户,它们是使用记录创建的。通过记录模式匹配,我们选择了所有 Doce。

match user with
| { LastName = "Doe" } -> printfn "%A" user
| _ -> ()

我们将一个带有属性的匿名记录指定为模式。

λ dotnet fsi main.fsx
{ FirstName = "John"
  LastName = "Doe"
  Occupation = "gardener" }
{ FirstName = "Jane"
  LastName = "Doe"
  Occupation = "teacher" }

F# 匹配类型

使用 :?,我们可以匹配类型。

main.fsx
open System.Collections

type User =
    { FirstName: string
      LastName: string
      Occupation: string }

let vals = new ArrayList()
vals.Add(1.2)
vals.Add(22)
vals.Add(true)
vals.Add("falcon")

vals.Add(
    { FirstName = "John"
      LastName = "Doe"
      Occupation = "gardener" }
)

for wal in vals do
    match wal with
    | :? int -> printfn "an integer"
    | :? float -> printfn "a float"
    | :? bool -> printfn "a boolean"
    | :? User -> printfn "a User"
    | _ -> ()

我们有一个包含不同数据类型的列表。我们遍历列表并打印每个元素的类型。

λ dotnet fsi main.fsx
a float
an integer
a boolean
a User

函数语法

当在函数中定义模式匹配时,可以使用函数关键字对其进行简化。

main.fsx
open System 

type Choices =
    | A
    | B
    | C

let getVal =
    function
    | 1 -> A
    | 2 -> B
    | _ -> C

let chx =
    [ for _ in 1..7 do
          yield getVal (Random().Next(1, 4)) ]

printfn "%A" chx

在示例中,我们随机构建了一个选择列表。

let getVal =
    function
    | 1 -> A
    | 2 -> B
    | _ -> C

这是函数内部简化的模式匹配语法。

λ dotnet fsi main.fsx
[B; C; A; C; A; B; B]

F# 匹配列表模式

在下一个示例中,我们匹配列表模式。

main.fsx
let vals =
    [ [ 1; 2; 3 ]
      [ 1; 2 ]
      [ 3; 4 ]
      [ 8; 8 ]
      [ 0 ] ]

let twoels (sub: int list) =
    match sub with
    | [ x; y ] -> printfn "%A" [ x; y ]
    | _ -> ()

for sub in vals do
    twoels sub

该程序打印所有包含两个元素的列表。

λ dotnet fsi main.fsx
[1; 2]
[3; 4]
[8; 8]

列表推导式

匹配模式可以用于列表推导式。

main.fsx
let res =
    [ for e in 1..100 do
          match e with
          | e when e % 3 = 0 -> yield "fizz"
          | e when e % 5 = 0 -> yield "buzz"
          | e when e % 15 = 0 -> yield "fizzbuzz"
          | _ -> yield (string e) ]

printfn "%A" res

我们使用列表推导式解决了 fizz-buzz 挑战。所有值都存储在列表中。

λ dotnet fsi main.fsx
["1"; "2"; "fizz"; "4"; "buzz"; "fizz"; "7"; "8"; "fizz"; "buzz"; "11"; "fizz";
 "13"; "14"; "fizz"; "16"; "17"; "fizz"; "19"; "buzz"; "fizz"; "22"; "23";
 "fizz"; "buzz"; "26"; "fizz"; "28"; "29"; "fizz"; "31"; "32"; "fizz"; "34";
 "buzz"; "fizz"; "37"; "38"; "fizz"; "buzz"; "41"; "fizz"; "43"; "44"; "fizz";
 "46"; "47"; "fizz"; "49"; "buzz"; "fizz"; "52"; "53"; "fizz"; "buzz"; "56";
 "fizz"; "58"; "59"; "fizz"; "61"; "62"; "fizz"; "64"; "buzz"; "fizz"; "67";
 "68"; "fizz"; "buzz"; "71"; "fizz"; "73"; "74"; "fizz"; "76"; "77"; "fizz";
 "79"; "buzz"; "fizz"; "82"; "83"; "fizz"; "buzz"; "86"; "fizz"; "88"; "89";
 "fizz"; "91"; "92"; "fizz"; "94"; "buzz"; "fizz"; "97"; "98"; "fizz"; "buzz"]

活动模式

通过活动模式,我们可以定义输入数据的命名分区,并在模式匹配表达式中使用这些名称。

main.fsx
let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd

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

testnum 3
testnum 8
testnum 11

在活动模式语法中,我们定义了 EvenOdd 名称。这些名称稍后在匹配表达式中使用。

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

我们定义了 EvenOdd 活动模式。

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

这些名称在匹配表达式中使用。

λ dotnet fsi main.fsx
3 is odd
8 is even
11 is odd

正则表达式

我们可以在匹配模式中使用正则表达式。

main.fsx
open System.Text.RegularExpressions

let (|RegEx|_|) p i =
    let m = Regex.Match(i, p)

    if m.Success then
        Some m.Groups
    else
        None

let checkrgx (msg) =
    match msg with
    | RegEx @"\d+" g -> printfn "Digit: %A" g
    | RegEx @"\w+" g -> printfn "Word : %A" g
    | _ -> printfn "Not recognized"


checkrgx "an old falcon"
checkrgx "1984"
checkrgx "3 hawks"

在示例中,我们在模式匹配中使用活动模式和正则表达式。

λ dotnet fsi main.fsx
Word : seq [an]
Digit: seq [1984]
Digit: seq [3]

异常处理

匹配表达式可用于处理异常。

main.fsx
open System

printf "Enter a number: "

let value = Console.ReadLine()

let n =
    match Int32.TryParse value with
    | true, num -> num
    | _ -> failwithf "'%s' is not an integer" value


let f = function
    | value when value > 0 -> printfn "positive value"
    | value when value = 0 -> printfn "zero"
    | value when value < 0 -> printfn "negative value"
    | _ -> ()

f n

我们期望用户输入一个整数值。我们尝试解析输入值;如果不是整数,我们将以错误消息失败。

let n =
    match Int32.TryParse value with
    | true, num -> num
    | _ -> failwithf "'%s' is not an integer" value

如果解析成功,TryParse 返回 true。因此,我们在第一个分支中有 true 布尔模式。对于其余的,我们使用 failwithf 失败。

匹配选项类型

选项类型用于表示可能存在也可能不存在的值。模式匹配是 F# 中处理 SomeNone 情况的安全且符合习惯的方式。

main.fsx
let describeOption opt =
    match opt with
    | Some v -> printfn "The value is %d" v
    | None -> printfn "No value found"

describeOption (Some 10)
describeOption None

在此示例中,函数 describeOption 在值存在时打印该值,否则打印一条消息。对选项类型进行模式匹配可以使代码简洁且安全。

λ dotnet fsi main.fsx
The value is 10
No value found

在本文中,我们研究了 F# 中的匹配表达式。

作者

我叫 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。