Поделиться через


Ссылочные ячейки (F#)

Ссылочные ячейки — это места хранения, которые позволяют создавать изменяющиеся значения с семантикой ссылок.

ref expression

Заметки

Для создания новой ссылочной ячейки, инкапсулирующей значение, перед значением ставится оператор ref. Базовое значение затем можно изменить, так как оно является изменяемым.

Ссылочная ячейка содержит фактическое значение; это не просто адрес. При создании ссылочной ячейки с помощью оператора ref создается копия базового значения в качестве инкапсулированного изменяемого значения.

Разыменовать ссылочную ячейку можно с помощью оператора ! (восклицательного знака).

Следующий пример кода иллюстрирует объявление и использование ссылочных ячеек.

// 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

В результате получается 50.

Ссылочные ячейки являются экземплярами универсального типа записей Ref, который объявляется следующим образом.

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

Тип 'a ref является синонимом Ref<'a>. В интегрированной среде разработки в компиляторе и в IntelliSense отображается первое обозначение данного типа, однако базовым определением является второе.

Оператор ref создает новую ссылочную ячейку. Следующий код представляет собой объявление оператора ref.

let ref x = { contents = x }

В следующей таблице перечислены возможности, доступные для ссылочной ячейки.

Оператор, член или поле

Описание

Тип

Определение

! (оператор разыменования)

Возвращает базовое значение.

'a ref -> 'a

let (!) r = r.contents

:= (оператор присваивания)

Изменяет базовое значение.

'a ref -> 'a -> unit

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

ref (оператор)

Инкапсулирует значение в новую ссылочную ячейку.

'a -> 'a ref

let ref x = { contents = x }

Value (свойство)

Получает или задает базовое значение.

unit -> 'a

member x.Value = x.contents

contents (поле записи)

Получает или задает базовое значение.

'a

let ref x = { contents = x }

Существует несколько способов доступа к базовому значению. Значение, возвращаемое оператором разыменования (!), не является присваиваемым значением. Следовательно, при изменении базового значения нужно вместо этого оператора использовать оператор присваивания (:=).

И свойство Value, и поле contents являются присваиваемыми значениями. Следовательно, их можно использовать для доступа к базовому значению или его изменения, как показано в следующем коде.

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)

Выходные данные выглядят следующим образом.

10
10
11
12

Поле contents предусмотрено для совместимости с другими версиями языка ML, и его наличие приводит к выводу предупреждения в процессе компиляции. Для отключения этого предупреждения используется параметр компилятора --mlcompatibility. Дополнительные сведения см. в разделе Параметры компилятора (F#).

Пример

Следующий код иллюстрирует использование ссылочных ячеек при передаче параметров. Тип Incrementor имеет метод Increment, который принимает параметр, включающий ключевое слово byref в типе параметра. Ключевое слово byref в типе параметра указывает, что вызывающие объекты должны передавать ссылочную ячейку или адрес обычной переменной заданного типа, в данном случае int. Остальная часть кода иллюстрирует вызов метода Increment с обоими этими типами аргументов и применение оператора ref к переменной для создания ссылочной ячейки (ref myDelta1). Затем показано использование оператора взятия адреса (&) для формирования соответствующего аргумента. Наконец, метод Increment вызывается снова путем использования ссылочной ячейки, объявленной с помощью привязки let. В последней строке кода демонстрируется использование оператора ! для разыменования ссылочной ячейки для печати.

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

Дополнительные сведения о передаче по ссылке см. в разделе Параметры и аргументы (F#).

Примечание

Программистам на C# следует знать, что в языке F# оператор ref работает иначе, чем в C#.Например, использование ref при передаче аргумента в F# дает результат иной, нежели в C#.

Ссылочные ячейки иизменяющиеся переменные

Ссылочные ячейки и изменяемые переменные часто можно использовать в одних и тех же ситуациях. В некоторых ситуациях, однако, использовать изменяемые переменные нельзя, и вместо них необходимо использовать ссылочные ячейки. В общем случае следует отдавать предпочтение изменяемым переменным, если они принимаются компилятором. Однако в выражениях, формирующих замыкания, компилятор будет сообщать, что изменяемые переменные использовать нельзя. Замыкания — это локальные функции, формируемые определенными выражениями языка F#, такими как лямбда-выражения, выражения последовательностей, вычислительные выражения и каррированные функции, в которых используются частично примененные аргументы. Замыкания, формируемые этими выражениями, сохраняются для дальнейшего определения значения. Этот процесс не совместим с изменяемыми переменными. Следовательно, если в таком выражении требуется изменяемое состояние, нужно использовать ссылочные ячейки. Дополнительные сведения о замыканиях см. в разделе "Замыкания (F#)".

В следующем примере кода демонстрируется сценарий, в котором необходимо использовать ссылочную ячейку.

// 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
    }

В приведенном выше коде ссылочная ячейка finished включена в локальное состояние, т. е. переменные, находящиеся в замыкании, создаются и используются целиком в пределах выражения, в данном случае выражения последовательности. Рассмотрим, что произойдет, если переменные будут нелокальными. Замыкания могут также получать доступ к нелокальному состоянию, однако, когда это происходит, переменные копируются и сохраняются по значению. Этот процесс называется семантикой значений. Это означает, что значения на момент копирования сохраняются, и все последующие изменения переменных на них не отражаются. Если требуется отслеживать изменения нелокальных переменных или, иными словами, требуется замыкание, взаимодействующее с нелокальным состоянием с помощью семантики ссылок, необходимо использовать ссылочную ячейку.

В следующем примере кода демонстрируется использование ссылочных ячеек в замыканиях. В данном случае замыкание образуется в результате частичного применения аргументов функции.

// 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)

См. также

Ссылки

Справочник символов и операторов (F#)

Основные понятия

Параметры и аргументы (F#)

Другие ресурсы

Справочник по языку F#