ZetCode

F# 引用单元格

最后修改于 2025 年 5 月 3 日

在本文中,我们将探讨 F# 中的引用单元格。引用单元格提供了一种在维护函数式编程原则的同时处理可变状态的方法。

一个引用单元格是一个特殊的容器,用于保存可变值。与常规的可变变量不同,引用单元格是一等值,可以被传递和存储在数据结构中。它们使用 ref 函数创建,使用 ! 访问,并使用 := 修改。当您需要在原本不可变函数式代码中需要可变状态时,引用单元格尤其有用。

F# 引用单元格基本示例

此示例展示了引用单元格的基本操作。

basic.fsx
let counter = ref 0
printfn "Initial value: %d" !counter

counter := !counter + 1
printfn "After increment: %d" !counter

counter := !counter + 1
printfn "After second increment: %d" !counter

我们创建一个引用单元格,然后修改和访问其内容。

let counter = ref 0

创建一个用值 0 初始化的引用单元格。ref 函数将值包装在一个可变容器中。

!counter

! 运算符解引用该单元格以访问其包含的值。

counter := !counter + 1

:= 运算符更新引用单元格的值。在递增之前,我们必须先解引用当前值。

λ dotnet fsi basic.fsx
Initial value: 0
After increment: 1
After second increment: 2

F# 引用单元格类型注解

引用单元格可以对其包含的值进行显式类型注解。

typed.fsx
let message: string ref = ref "hello"
let count: int ref = ref 0
let active: bool ref = ref true

printfn "Message: %s" !message
printfn "Count: %d" !count
printfn "Active: %b" !active

message := "world"
count := 42
active := false

printfn "After modification:"
printfn "Message: %s" !message
printfn "Count: %d" !count
printfn "Active: %b" !active

我们创建三个类型化的引用单元格,打印它们的初始值,修改它们,然后再次打印。

let message: string ref = ref "hello"

类型注解字符串 ref 指定此引用单元格包含一个字符串。

λ dotnet fsi typed.fsx
Message: hello
Count: 0
Active: true
After modification:
Message: world
Count: 42
Active: false

F# 引用单元格在函数中

引用单元格可以传递给函数并从函数返回。

functions.fsx
let increment (cell: int ref) =
    cell := !cell + 1

let createCounter initialValue =
    ref initialValue

let counter = createCounter 10
printfn "Initial: %d" !counter

increment counter
printfn "After increment: %d" !counter

increment counter
printfn "After second increment: %d" !counter

我们通过创建计数器工厂和增量函数来演示在函数中使用引用单元格。

let increment (cell: int ref) =
    cell := !cell + 1

一个接受引用单元格并修改其内容的函数。

let createCounter initialValue =
    ref initialValue

一个创建并返回新引用单元格的函数。

λ dotnet fsi functions.fsx
Initial: 10
After increment: 11
After second increment: 12

F# 引用单元格 vs 可变变量

此示例将引用单元格与常规可变变量进行比较。

comparison.fsx
let mutable mvar = 0
let rvar = ref 0

printfn "mutable: %d, reference: %d" mvar !rvar

mvar <- mvar + 1
rvar := !rvar + 1

printfn "After increment:"
printfn "mutable: %d, reference: %d" mvar !rvar

let modifyMutable x = x + 1
let modifyReference (x: int ref) = x := !x + 1

mvar <- modifyMutable mvar
modifyReference rvar

printfn "After function calls:"
printfn "mutable: %d, reference: %d" mvar !rvar

我们展示了可变变量和引用单元格之间的主要区别。

let modifyMutable x = x + 1
let modifyReference (x: int ref) = x := !x + 1

引用单元格可以在函数内部修改,而可变变量需要返回可变值。

λ dotnet fsi comparison.fsx
mutable: 0, reference: 0
After increment:
mutable: 1, reference: 1
After function calls:
mutable: 2, reference: 2

F# 引用单元格在数据结构中

引用单元格可以存储在列表和其他数据结构中。

structures.fsx
let counters = [ref 1; ref 2; ref 3; ref 4; ref 5]

printfn "Initial values:"
counters |> List.iter (fun x -> printf "%d " !x)
printfn ""

counters |> List.iter (fun x -> x := !x * 2)

printfn "After modification:"
counters |> List.iter (fun x -> printf "%d " !x)
printfn ""

我们创建一个引用单元格列表,打印它们的初始值,修改它们,然后再次打印。

let counters = [ref 1; ref 2; ref 3; ref 4; ref 5]

创建一个列表,其中每个元素都是一个单独的引用单元格。

counters |> List.iter (fun x -> x := !x * 2)

将列表中每个引用单元格的值加倍。

λ dotnet fsi structures.fsx
Initial values:
1 2 3 4 5 
After modification:
2 4 6 8 10 

F# 引用单元格与记录

引用单元格可以在记录类型中使用,用于可变字段。

records.fsx
type Counter = 
    { Name: string
      Count: int ref }

let createCounter name initialValue =
    { Name = name
      Count = ref initialValue }

let incrementCounter counter =
    counter.Count := !counter.Count + 1
    printfn "%s: %d" counter.Name !counter.Count

let c1 = createCounter "First" 0
let c2 = createCounter "Second" 10

incrementCounter c1
incrementCounter c2
incrementCounter c1
incrementCounter c2

我们创建了一个带有引用单元格字段的记录类型以及用于处理它的函数。

type Counter = 
    { Name: string
      Count: int ref }

Count 字段是一个引用单元格,允许在记录保持不可变的同时进行修改。

λ dotnet fsi records.fsx
First: 1
Second: 11
First: 2
Second: 12

F# 引用单元格在闭包中

在闭包的函数调用中,引用单元格会保持其状态。

closures.fsx
let makeCounter() =
    let count = ref 0
    fun () -> 
        count := !count + 1
        !count

let counter1 = makeCounter()
let counter2 = makeCounter()

printfn "Counter1: %d" (counter1())
printfn "Counter1: %d" (counter1())
printfn "Counter2: %d" (counter2())
printfn "Counter1: %d" (counter1())
printfn "Counter2: %d" (counter2())

我们使用引用单元格来创建计数器,以演示有状态的闭包。

let makeCounter() =
    let count = ref 0
    fun () -> 
        count := !count + 1
        !count

makeCounter 的每次调用都会创建一个被捕获在闭包中的新引用单元格。

λ dotnet fsi closures.fsx
Counter1: 1
Counter1: 2
Counter2: 1
Counter1: 3
Counter2: 2

F# 引用单元格的局限性

此示例显示了使用引用单元格时的一些局限性和注意事项。

limitations.fsx
let cell = ref "hello"

// This works
cell := "world"
printfn "%s" !cell

// This would cause a compilation error
// cell := 42  // Type mismatch

// Need to dereference to use the value
let length = String.length !cell
printfn "Length: %d" length

// Reference cells can be compared by reference
let cell2 = ref "world"
printfn "Same contents: %b" (!cell = !cell2)
printfn "Same reference: %b" (cell = cell2)

我们演示了类型安全、解引用需求和引用比较。

// cell := 42  // Type mismatch

引用单元格是类型安全的——您无法更改包含值的类型。

printfn "Same reference: %b" (cell = cell2)

引用单元格通过引用进行比较,而不是通过其内容进行比较。

λ dotnet fsi limitations.fsx
world
Length: 5
Same contents: true
Same reference: false

在本文中,我们探讨了 F# 中的引用单元格。它们提供了一种在维护函数式编程原则的同时处理可变状态的强大方法。

作者

我的名字是 Jan Bodnar,我是一位充满激情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。