ZetCode

F# 表达式

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

在本文中,我们将探讨 F# 中的表达式。表达式是 F# 程序的基本构建块,代表计算并产生值。

在 F# 中,表达式是任何代码片段,执行时都会计算出一个特定的值。与命令式语言中的语句不同,F# 将所有代码结构都视为表达式,这意味着每个操作都会产生一个值。这包括简单的字面量、复杂的计算、函数调用,甚至包括条件语句等控制流结构。

表达式在 F# 中起着至关重要的作用,它允许开发人员以模块化和可预测的方式构建逻辑。由于表达式始终返回值,代码保持简洁,并避免了不必要的副作用。例如,像 if x > 0 then "Positive" else "Negative" 这样的条件表达式会产生一个字符串结果,无需显式的 return 语句。

F# 中的所有控制流结构都作为表达式工作,包括循环和模式匹配。match 表达式可以对一个值进行求值,并根据预定义的 case 生成输出。例如,match n with | 0 -> "Zero" | 1 -> "One" | _ -> "Other" 根据给定输入返回一个字符串。

表达式可以组合形成更复杂的表达式,支持函数式编程原则,如组合。函数在 F# 中是表达式,这意味着它们可以作为参数传递,作为结果返回,并以各种方式应用来构建动态逻辑。

计算表达式提供了附加功能,为异步编程、序列操作和查询处理等任务实现了结构化控制流。这些表达式在保持函数式纯粹性的同时,简化了副作用的管理。

F# 基本表达式

F# 中的简单表达式包括字面量和基本操作。

basic.fsx
// Literal expressions
let number = 42
let text = "Hello"
let truth = true

// Arithmetic expressions
let sum = 5 + 3 * 2
let power = 2.0 ** 8.0

// String expressions
let greeting = text + ", F#!"
let interpolated = $"The answer is {number}"

printfn "%d" sum
printfn "%f" power
printfn "%s" greeting
printfn "%s" interpolated

我们演示了 F# 中的各种基本表达式。

let sum = 5 + 3 * 2

算术表达式遵循标准的运算符优先级规则。

λ dotnet fsi basic.fsx
11
256.000000
Hello, F#!
The answer is 42

F# 条件表达式

If/then/else 结构是返回值的表达式。

conditional.fsx
let describeNumber n =
    if n % 2 = 0 then "even"
    else "odd"

let result = describeNumber 7
printfn "7 is %s" result

// Ternary-style expression
let max a b = if a > b then a else b
printfn "Max of 5 and 3 is %d" (max 5 3)

// Nested conditionals
let rating score =
    if score >= 90 then "A"
    elif score >= 80 then "B"
    elif score >= 70 then "C"
    else "F"

printfn "Score 85 gets: %s" (rating 85)

该示例展示了 if/then/else 结构如何在 F# 中作为表达式。

if n % 2 = 0 then "even"
else "odd"

整个 if 表达式求值为 "even" 或 "odd"。

λ dotnet fsi conditional.fsx
7 is odd
Max of 5 and 3 is 5
Score 85 gets: B

F# 块表达式

表达式可以分组到包含多个语句的块中。

blocks.fsx
let calculateTotal price quantity =
    // This is a block expression
    let subtotal = price * quantity
    let tax = subtotal * 0.08m
    subtotal + tax  // Last expression is the return value

let total = calculateTotal 25.0m 3m
printfn "Total: %M" total

// Another block expression example
let message =
    let name = "Alice"
    let age = 30
    $"{name} is {age} years old"

printfn "%s" message

该示例演示了具有多个 let 绑定的块表达式。

let subtotal = price * quantity
let tax = subtotal * 0.08m
subtotal + tax

该块求值为最后一个表达式 (subtotal + tax)。

λ dotnet fsi blocks.fsx
Total: 81.000000
Alice is 30 years old

F# 函数表达式

函数是 F# 中的一流表达式。它们可以使用函数表达式或 lambda 表达式定义。函数可以作为参数传递给其他函数,从函数返回,并存储在数据结构中。

functions.fsx
// Function as an expression
let square x = x * x

// Lambda expression
let cube = fun x -> x * x * x

// Higher-order function
let applyTwice f x = f (f x)

let result1 = applyTwice square 2
let result2 = applyTwice cube 2

printfn "Square twice: %d" result1
printfn "Cube twice: %d" result2

// Function composition
let negate x = -x
let squareThenNegate = negate >> square
printfn "Composed: %d" (squareThenNegate 3)

在本例中,我们在 F# 中使用了各种函数表达式。applyTwice 函数接收一个函数和一个参数,将该函数应用两次到该参数上,并返回结果。negate 函数对其参数取负。squareThenNegate 函数使用函数组合运算符 (>>) 组合了 negatesquare 函数。

let cube = fun x -> x * x * x

Lambda 表达式是匿名函数,可以分配给名称。

λ dotnet fsi functions.fsx
Square twice: 16
Cube twice: 256
Composed: -9

F# match 表达式

F# 中的模式匹配与 match 表达式一起非常强大。它允许您解构数据类型并匹配特定模式。match 表达式类似于其他语言中的 switch 语句,但更具表现力。

match.fsx
let describeNumber n =
    match n with
    | 0 -> "Zero"
    | 1 -> "One"
    | x when x < 0 -> "Negative"
    | x when x % 2 = 0 -> "Even"
    | _ -> "Odd"

printfn "0: %s" (describeNumber 0)
printfn "42: %s" (describeNumber 42)
printfn "-5: %s" (describeNumber -5)
printfn "7: %s" (describeNumber 7)

// Matching on tuples
let pointCategory (x, y) =
    match x, y with
    | 0, 0 -> "Origin"
    | _, 0 -> "X-axis"
    | 0, _ -> "Y-axis"
    | _ -> "Other"

printfn "(0,5): %s" (pointCategory (0, 5))

该示例演示了对整数和元组的匹配。match 表达式使用守卫 (when) 为模式添加额外的条件。下划线模式 (_) 是一个通配符,匹配未被先前模式匹配的任何内容。

match n with
| 0 -> "Zero"
| 1 -> "One"

Match 表达式求值为匹配分支中的表达式。

λ dotnet fsi match.fsx
0: Zero
42: Even
-5: Negative
7: Odd
(0,5): Y-axis

F# 序列表达式

序列表达式按需生成序列。它们使用 seq 关键字定义,并且可以包含循环和条件。它们对于生成无限序列或依赖于外部数据源的序列很有用。序列是按需计算的,这意味着值仅在需要时生成。这允许高效的内存使用,并在某些情况下可以提高性能。

sequences.fsx
// Basic sequence expression
let numbers = seq { 1..10 }

// Sequence with filtering
let evens = seq {
    for n in 1..20 do
        if n % 2 = 0 then
            yield n
}

// Sequence with transformation
let squares = seq {
    for n in 1..5 do
        yield n * n
}

printfn "Numbers: %A" (numbers |> Seq.take 3)
printfn "Evens: %A" (evens |> Seq.take 3)
printfn "Squares: %A" (squares |> Seq.toList)

// More complex sequence
let coordinates = seq {
    for x in 1..3 do
        for y in 1..3 do
            yield (x, y)
}

printfn "Coordinates: %A" (coordinates |> Seq.toList)

该示例展示了如何使用 seq 表达式创建序列。seq 表达式允许您使用 for 循环和 yield 关键字定义值序列。yield 关键字用于在序列中生成值。该示例演示了一个基本序列、一个过滤序列和一个转换序列。

seq {
    for n in 1..20 do
        if n % 2 = 0 then
            yield n
}

序列表达式按需(惰性地)生成值。

λ dotnet fsi sequences.fsx
Numbers: seq [1; 2; 3]
Evens: seq [2; 4; 6]
Squares: [1; 4; 9; 16; 25]
Coordinates: [(1, 1); (1, 2); (1, 3); (2, 1); (2, 2); (2, 3); (3, 1); (3, 2); (3, 3)]

F# 计算表达式

计算表达式为单子操作提供语法糖。它们允许您定义可用于封装异步或有状态计算的自定义工作流。计算表达式使用 let!do! 关键字定义,这些关键字允许您在表达式中绑定值并执行副作用。

computations.fsx
open System.Net.Http

// Using the task computation expression (F# 6+)
let fetchData (url: string) = task {
    let client = new HttpClient()
    let! response = client.GetAsync(url)
    let! content = response.Content.ReadAsStringAsync()
    return content
}

let fetchWebcode = fetchData "https://webcode.me"

printfn "fetchWebcode result: %s" fetchWebcode.Result

在本例中,我们使用任务计算表达式从 URL 获取数据。它使我们能够以更易读的方式编写异步代码。let! 关键字用于将异步操作的结果绑定到变量。计算表达式在调用函数时执行。结果是一个可以等待或同步执行的任务。

在本文中,我们探讨了表达式在 F# 中的基本作用。理解表达式是编写惯用 F# 代码的关键,因为 F# 中的所有内容都是求值为值的表达式。

作者

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