Condividi tramite


Reference Cells (F#)

Reference cells are storage locations that enable you to create mutable values with reference semantics.

ref expression

Remarks

You use the ref operator before a value to create a new reference cell that encapsulates the value. You can then change the underlying value because it is mutable.

A reference cell holds an actual value; it is not just an address. When you create a reference cell by using the ref operator, you create a copy of the underlying value as an encapsulated mutable value.

You can dereference a reference cell by using the ! (bang) operator.

The following code example illustrates the declaration and use of reference cells.

// Declare a reference. 
let refVar = ref 6

// Change the value referred to by the reference.
refVar := 50

// Dereference by using the ! operator.
printfn "%d" !refVar

The output is 50.

Reference cells are instances of the Ref generic record type, which is declared as follows.

type Ref<'a> =
    { mutable contents: 'a }

The type 'a ref is a synonym for Ref<'a>. The compiler and IntelliSense in the IDE display the former for this type, but the underlying definition is the latter.

The ref operator creates a new reference cell. The following code is the declaration of the ref operator.

let ref x = { contents = x }

The following table shows the features that are available on the reference cell.

Operator, member, or field

Description

Type

Definition

! (dereference operator)

Returns the underlying value.

'a ref -> 'a

let (!) r = r.contents

:= (assignment operator)

Changes the underlying value.

'a ref -> 'a -> unit

let (:=) r x = r.contents <- x

ref (operator)

Encapsulates a value into a new reference cell.

'a -> 'a ref

let ref x = { contents = x }

Value (property)

Gets or sets the underlying value.

unit -> 'a

member x.Value = x.contents

contents (record field)

Gets or sets the underlying value.

'a

let ref x = { contents = x }

There are several ways to access the underlying value. The value returned by the dereference operator (!) is not an assignable value. Therefore, if you are modifying the underlying value, you must use the assignment operator (:=) instead.

Both the Value property and the contents field are assignable values. Therefore, you can use these to either access or change the underlying value, as shown in the following code.

let xRef : int ref = ref 10

printfn "%d" (xRef.Value)
printfn "%d" (xRef.contents)

xRef.Value <- 11
printfn "%d" (xRef.Value)
xRef.contents <- 12
printfn "%d" (xRef.contents)

The output is as follows.

10
10
11
12

The field contents is provided for compatibility with other versions of ML and will produce a warning during compilation. To disable the warning, use the --mlcompatibility compiler option. For more information, see Compiler Options (F#).

Example

The following code illustrates the use of reference cells in parameter passing. The Incrementor type has a method Increment that takes a parameter that includes byref in the parameter type. The byref in the parameter type indicates that callers must pass a reference cell or the address of a typical variable of the specified type, in this case int. The remaining code illustrates how to call Increment with both of these types of arguments, and shows the use of the ref operator on a variable to create a reference cell (ref myDelta1). It then shows the use of the address-of operator (&) to generate an appropriate argument. Finally, the Increment method is called again by using a reference cell that is declared by using a let binding. The final line of code demonstrates the use of the ! operator to dereference the reference cell for printing.

type Incrementor(delta) =
    member this.Increment(i : int byref) =
        i <- i + delta

let incrementor = new Incrementor(1)
let mutable myDelta1 = 10
incrementor.Increment(ref myDelta1)
// Prints 10:
printfn "%d" myDelta1  

let mutable myDelta2 = 10
incrementor.Increment(&myDelta2)
// Prints 11:
printfn "%d" myDelta2 

let refInt = ref 10
incrementor.Increment(refInt)
// Prints 11:
printfn "%d" !refInt

For more information about how to pass by reference, see Parameters and Arguments (F#).

Note

C# programmers should know that ref works differently in F# than it does in C#. For example, the use of ref when you pass an argument does not have the same effect in F# as it does in C#.

Reference Cells vs. Mutable Variables

Reference cells and mutable variables can often be used in the same situations. However, there are some situations in which mutable variables cannot be used, and you must use a reference cell instead. In general, you should prefer mutable variables where they are accepted by the compiler. However, in expressions that generate closures, the compiler will report that you cannot use mutable variables. Closures are local functions that are generated by certain F# expressions, such as lambda expressions, sequence expressions, computation expressions, and curried functions that use partially applied arguments. The closures generated by these expressions are stored for later evaluation. This process is not compatible with mutable variables. Therefore, if you need mutable state in such an expression, you have to use reference cells. For more information about closures, see Closures (F#).

The following code example demonstrates the scenario in which you must use a reference cell.

// Print all the lines read in from the console. 
let PrintLines1() =
    let mutable finished = false 
    while not finished do 
        match System.Console.ReadLine() with
        | null -> finished <- true
        | s -> printfn "line is: %s" s


// Attempt to wrap the printing loop into a  
// sequence expression to delay the computation. 
let PrintLines2() =
    seq {
        let mutable finished = false 
        // Compiler error: 
        while not finished do   
            match System.Console.ReadLine() with
            | null -> finished <- true
            | s -> yield s
    }

// You must use a reference cell instead. 
let PrintLines3() =
    seq {
        let finished = ref false 
        while not !finished do 
            match System.Console.ReadLine() with
            | null -> finished := true
            | s -> yield s
    }

In the previous code, the reference cell finished is included in local state, that is, variables that are in the closure are created and used entirely within the expression, in this case a sequence expression. Consider what occurs when the variables are non-local. Closures can also access non-local state, but when this occurs, the variables are copied and stored by value. This process is known as value semantics. This means that the values at the time of the copy are stored, and any subsequent changes to the variables are not reflected. If you want to track the changes of non-local variables, or, in other words, if you need a closure that interacts with non-local state by using reference semantics, you must use a reference cell.

The following code examples demonstrate the use of reference cells in closures. In this case, the closure results from the partial application of function arguments.

// The following code demonstrates the use of reference 
// cells to enable partially applied arguments to be changed 
// by later code. 

let increment1 delta number = number + delta

let mutable myMutableIncrement = 10

// Closures created by partial application and literals. 
let incrementBy1 = increment1 1
let incrementBy2 = increment1 2

// Partial application of one argument from a mutable variable. 
let incrementMutable = increment1 myMutableIncrement

myMutableIncrement <- 12

// This line prints 110.
printfn "%d" (incrementMutable 100)

let myRefIncrement = ref 10

// Partial application of one argument, dereferenced 
// from a reference cell. 
let incrementRef = increment1 !myRefIncrement

myRefIncrement := 12

// This line also prints 110.
printfn "%d" (incrementRef 100)

// Reset the value of the reference cell.
myRefIncrement := 10

// New increment function takes a reference cell. 
let increment2 delta number = number + !delta

// Partial application of one argument, passing a reference cell 
// without dereferencing first. 
let incrementRef2 = increment2 myRefIncrement

myRefIncrement := 12

// This line prints 112.
printfn "%d" (incrementRef2 100)

See Also

Reference

Symbol and Operator Reference (F#)

Concepts

Parameters and Arguments (F#)

Other Resources

F# Language Reference