Ссылочные ячейки (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#)