Komórki odwołań (F#)
Komórki odwołań to lokalizacje przechowywania, które umożliwiają tworzenie modyfikowalnych wartości z semantyką odwołań.
ref expression
Uwagi
Umieszczenie przed wartością operatora ref spowoduje utworzenie nowej komórki odwołania, która hermetyzuje wartość.Podstawową wartość można będzie wtedy zmieniać, ponieważ jest ona modyfikowalna.
Komórka odwołania zawiera wartość rzeczywistą, a nie sam adres.Podczas tworzenia komórek odwołań za pomocą operatora ref powstaje kopia podstawowej wartości jako hermetyzowana wartość modyfikowalna.
Komórkę odwołania można wyłuskać za pomocą operatora ! (wykrzyknika).
Poniższy przykład kodu ilustruje deklarowanie i używanie komórek odwołań.
// 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
Wynik to 50.
Komórki odwołań są wystąpieniami ogólnego typu rekordu Ref, który deklaruje się w następujący sposób.
type Ref<'a> =
{ mutable contents: 'a }
Typ 'a ref jest synonimem zapisu Ref<'a>.Pierwszy zapis jest stosowany do wyświetlania tego typu w kompilatorze i technologii IntelliSense w środowisku IDE, jednak podstawowa definicja ma postać jak w drugim zapisie.
Operator ref tworzy nową komórkę odwołania.Poniższy fragment kodu stanowi deklarację operatora ref.
let ref x = { contents = x }
W poniższej tabeli przedstawiono funkcje, które są dostępne w komórce odwołania.
Operator, element członkowski lub pole |
Opis |
Typ |
Definicja |
---|---|---|---|
! (operator wyłuskania) |
Zwraca podstawową wartość. |
'a ref -> 'a |
let (!) r = r.contents |
:= (operator przypisania) |
Zmienia podstawową wartość. |
'a ref -> 'a -> unit |
let (:=) r x = r.contents <- x |
ref (operator) |
Hermetyzuje wartość do nowej komórki odwołania. |
'a -> 'a ref |
let ref x = { contents = x } |
Value (właściwość) |
Pobiera lub ustawia podstawową wartość. |
unit -> 'a |
member x.Value = x.contents |
contents (pole rekordu) |
Pobiera lub ustawia podstawową wartość. |
'a |
let ref x = { contents = x } |
Istnieje kilka sposobów dostępu do podstawowej wartości.Wartość zwracana przez operator wyłuskania (!) nie jest przypisywalna.W związku z tym w przypadku modyfikowania podstawowej wartości należy użyć operatora przypisania (:=).
Wartości właściwości Value i pola contents są przypisywalne.W związku z tym można za ich pomocą uzyskać dostęp do podstawowej wartości albo ją zmienić, jak pokazano w poniższym kodzie.
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)
Dane wyjściowe są następujące:
10
10
11
12
Pole contents zapewnia zgodność z innymi wersjami języka ML i podczas kompilacji powoduje generowanie ostrzeżenia.Aby wyłączyć ostrzeżenia, należy użyć opcji kompilatora --mlcompatibility.Aby uzyskać więcej informacji, zobacz Opcje kompilatora (F#).
Przykład
Poniższy fragment kodu ilustruje używanie komórek odwołań w przekazywaniu parametrów.Typ Incrementor ma metodę Increment, która pobiera parametr zawierający w typie element byref.Element byref w typie parametru wskazuje, że obiekty wywołujące muszą przekazywać komórkę odwołania lub adres typowej zmiennej o określonym typie, w tym przypadku int.Pozostały kod ilustruje sposób wywołania metody Increment z oboma tymi typami argumentów oraz ilustruje użycie operatora ref wobec zmiennej w celu utworzenia komórki odwołania (ref myDelta1).Następnie prezentuje użycie operatora adresu obiektu (&) w celu wygenerowania odpowiedniego argumentu.Na koniec jest ponownie wywoływana metoda Increment przy użyciu komórki odwołania, która jest zadeklarowana za pomocą powiązania let.Ostatni wiersz kodu ilustruje użycie operatora ! w celu wyłuskania komórki odwołania na potrzeby drukowania.
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
Aby uzyskać więcej informacji dotyczących przekazywania odwołania, zobacz Parametry i argumenty (F#).
[!UWAGA]
Programiści języka C# powinni wiedzieć, że operator ref działa inaczej w języku F# niż w C#.Na przykład użycie operatora ref podczas przekazywania argumentu ma różne efekty w obu językach.
Komórki odwołań a zmienne modyfikowalne
Komórki odwołań i zmienne modyfikowalne często można stosować w tych samych przypadkach.Istnieją jednak pewne sytuacje, w których nie można używać takich zmiennych i trzeba zamiast nich użyć komórek odwołań.Ogólnie rzecz biorąc, należy stosować zmienne modyfikowalne, jeśli tylko są akceptowane przez kompilator.Jednak w wyrażeniach generujących zamknięcia kompilator zgłosi, że nie można użyć takich zmiennych.Zamknięcia to lokalne funkcje, które są generowane przez niektóre wyrażenia języka F#, takie jak wyrażenia lambda, wyrażenia sekwencji, wyrażenia obliczeń i funkcje rozwinięte używające częściowo zastosowanych argumentów.Zamknięcia wygenerowane przez te wyrażenia są przechowywane do późniejszego obliczenia.Proces ten nie działa na wartościach modyfikowalnych.Dlatego jeśli w takim wyrażeniu jest potrzebny modyfikowalny stan, trzeba użyć komórek odwołań.Aby uzyskać więcej informacji dotyczących zamknięć, zobacz temat Zamknięcia (F#).
Poniższy przykład kodu przedstawia scenariusz, w którym należy użyć komórki odwołania.
// 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
}
W poprzednim fragmencie kodu komórka odwołania finished jest dołączona w stanie lokalnym, tzn. zmienne istniejące w zamknięciu są tworzone i używane wyłącznie w wyrażeniu. W tym przypadku jest to wyrażenie sekwencji.Warto zastanowić się, co się stanie, gdy zmienne nie będą lokalne.Zamknięcia mają dostęp również do stanu nielokalnego, ale w takim wypadku zmienne są kopiowane i przechowywane przez wartość.Ten proces jest nazywany semantyką wartości.Polega on na tym, że wartości w czasie kopiowania są zapisywane, a żadne kolejne modyfikacje zmiennych nie są widoczne.Aby śledzić modyfikacje zmiennych nielokalnych, czyli innymi słowy uzyskać zamknięcie, które wchodzi w interakcje ze stanem nielokalnym przy użyciu semantyki odwołania, należy użyć komórki odwołania.
Poniższe przykłady kodu ilustrują używanie komórek odwołań w zamknięciach.W tym przypadku zamknięcie jest spowodowane częściowym zastosowaniem argumentów funkcji.
// 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)
Zobacz też
Informacje
Odwołanie do symbolu i operatora (F#)