Ref ячейки
Ref клетки обходят некоторые ограничения изменчивости. Фактически, ref-ячейки - это очень простые типы данных, которые оборачивают изменяемое поле в тип записи. Ссылочные ячейки определяются F # следующим образом:
type 'a ref = { mutable contents : 'a }
Библиотека F # содержит несколько встроенных функций и операторов для работы с ячейками ref:
let ref v = { contents = v } (* val ref : 'a -> 'a ref *)
let (!) r = r.contents (* val (!) : 'a ref -> 'a *)
let (:=) r v = r.contents <- v (* val (:=) : 'a ref -> 'a -> unit *)
Функция ref используется для создания ячейки ref,! Оператор используется для чтения содержимого ячейки ref, а оператор: = используется для назначения ячейке ref нового значения. Вот пример в ФСИ:
let x = ref "hello";;
val x : string ref
x;; (* returns ref instance *)
val it : string ref = {contents = "hello";}
!x;; (* returns x.contents *)
val it : string = "hello"
x := "world";; (* updates x.contents with a new value *)
val it : unit = ()
!x;; (* returns x.contents *)
val it : string = "world"
Поскольку ячейки ref размещаются в куче, они могут совместно использоваться несколькими функциями:
open System
let withSideEffects x =
x := "assigned from withSideEffects function"
let refTest() =
let msg = ref "hello"
printfn "%s" !msg
let setMsg() =
msg := "world"
setMsg()
printfn "%s" !msg
withSideEffects msg
printfn "%s" !msg
let main() =
refTest()
Console.ReadKey(true) |> ignore
main()
Функция withSideEffects имеет тип val withSideEffects: строка ref -> unit.
Эта программа выводит следующее:
привет
мир
Назначено из функции withSideEffects
Функция withSideEffects названа таковой, поскольку она имеет побочный эффект, то есть может изменять состояние переменной в других функциях. Клетки Ref должны рассматриваться как огонь. Используйте его осторожно, когда это абсолютно необходимо, но избегайте его в целом. Если вы обнаружите, что используете Ref Cells при переводе кода с C / C ++, то некоторое время игнорируйте эффективность и посмотрите, сможете ли вы обойтись без Ref Cells или, в худшем случае, используя mutable. Вы часто сталкивались с более элегантным и более подходящим алгоритмом
Aliasing Ref Cells
Примечание. Несмотря на то, что в императивном программировании широко используются псевдонимы, у этой практики есть ряд проблем. В частности, это затрудняет отслеживание программ, поскольку состояние любой переменной может быть изменено в любой точке приложения. Кроме того, многопоточные приложения, совместно использующие изменяемое состояние, трудно рассуждать, так как один поток может потенциально изменить состояние переменной в другом потоке, что может привести к ряду незначительных ошибок, связанных с состояниями гонки и мертвыми блокировками.
Ячейка ref очень похожа на указатель C или C ++. Можно указывать две или более ячейки ссылки на один и тот же адрес памяти; изменения в этом адресе памяти изменят состояние всех ссылочных ячеек, указывающих на него. Концептуально этот процесс выглядит следующим образом:
Допустим, у нас есть 3 ячейки ссылки, смотрящие на один и тот же адрес в памяти:
Три ссылки на целое число со значением 7
cell1, cell2 и cell3 все указывают на один и тот же адрес в памяти. Свойство .contents каждой ячейки равно 7. Допустим, в какой-то момент в нашей программе мы выполняем код cell1: = 10, это меняет значение в памяти на следующее:
Три ссылки на целое число со значением 10
При назначении cell1.contents нового значения, переменные cell2 и cell3 также были изменены. Это можно продемонстрировать с помощью fsi следующим образом:
let cell1 = ref 7;;
val cell1 : int ref
let cell2 = cell1;;
val cell2 : int ref
let cell3 = cell2;;
val cell3 : int ref
!cell1;;
val it : int = 7
!cell2;;
val it : int = 7
!cell3;;
val it : int = 7
cell1 := 10;;
val it : unit = ()
!cell1;;
val it : int = 10
!cell2;;
val it : int = 10
!cell3;;
val it : int = 10