F #: mutable vs. ref

81

Erstens erkenne ich die Möglichkeit an, dass diese Frage ein Duplikat sein könnte; Lass es mich wissen.

Ich bin gespannt, was die allgemeine "Best Practice" für Situationen ist, in denen Veränderlichkeit gewünscht wird. F # scheint hierfür zwei Möglichkeiten zu bieten: die let mutableBindung, die in "den meisten" Sprachen wie Variablen zu funktionieren scheint, und die Referenzzelle (mit der refFunktion erstellt), für deren Verwendung eine explizite Dereferenzierung erforderlich ist.

Es gibt einige Fälle, in denen einer in den einen oder anderen "gezwungen" wird: .NET Interop verwendet in der Regel veränderlich mit <-, und in Workflow-Berechnungen muss refmit verwendet werden :=. Diese Fälle sind also ziemlich eindeutig, aber ich bin gespannt, was ich tun soll, wenn ich meine eigenen veränderlichen Variablen außerhalb dieser Szenarien erstelle. Welchen Vorteil hat ein Stil gegenüber dem anderen? (Vielleicht würde ein weiterer Einblick in die Implementierung helfen.)

Vielen Dank!

J Cooper
quelle
4
Beachten Sie, dass in F # Version 4 Mutable verwendet werden kann, wenn Sie früher eine Referenz benötigten. blogs.msdn.com/b/fsharpteam/archive/2014/11/12/…
James Moore

Antworten:

131

Ich kann nur unterstützen, was gradbot gesagt hat - wenn ich eine Mutation brauche, bevorzuge ich let mutable.

In Bezug auf die Implementierung und Unterschiede zwischen den beiden refZellen werden im Wesentlichen durch einen sehr einfachen Datensatz implementiert, der ein veränderbares Datensatzfeld enthält. Sie können sie leicht selbst schreiben:

type ref<'T> =  // '
  { mutable value : 'T } // '

// the ref function, ! and := operators look like this:
let (!) (a:ref<_>) = a.value
let (:=) (a:ref<_>) v = a.value <- v
let ref v = { value = v }

Ein bemerkenswerter Unterschied zwischen den beiden Ansätzen besteht darin, dass let mutableder veränderbare Wert auf dem Stapel gespeichert wird (als veränderbare Variable in C #), während refder veränderbare Wert in einem Feld eines Heap-zugewiesenen Datensatzes gespeichert wird. Dies kann sich auf die Leistung auswirken, aber ich habe keine Zahlen ...

Dank dessen können veränderbare Werte, die refverwendet werden, als Alias verwendet werden. Dies bedeutet, dass Sie zwei Werte erstellen können, die auf denselben veränderlichen Wert verweisen:

let a = ref 5  // allocates a new record on the heap
let b = a      // b references the same record
b := 10        // modifies the value of 'a' as well!

let mutable a = 5 // mutable value on the stack
let mutable b = a // new mutable value initialized to current value of 'a'
b <- 10           // modifies the value of 'b' only!
Tomas Petricek
quelle
2
Nur zur Erinnerung: Auf dem Stapel oder auf dem Haufen zu sein, ist ein Implementierungsdetail und nicht genau relevant für die Frage (aber dennoch eine ausgezeichnete Antwort)
Bruno Brant
5
Ich würde argumentieren, dass es äußerst relevant ist, zu wissen, ob etwas den Overhead der Heap-Zuweisung und -Sammlung verursacht oder nicht, wenn man sich für eine Best Practice entscheidet.
Jackmott
@jackmott Schauen Sie sich diesen Artikel von Eric Lippert mit dem Titel The Stack Is An Implementation Detail an
jaromey
5
@ Jaromey Ja, ich verstehe, dass ich Eric in diesem Punkt grundsätzlich nicht zustimme. Sie können Leistungsüberlegungen nicht außer Acht lassen, wenn Sie überlegen, was eine „Best Practice“ ist. Es wird oft behauptet, dass die Leistung noch keine Rolle spielt. So viel Software ist aufgrund des Todes von tausend Implementierungsdetails langsam.
Jackmott
18

Verwandte Frage: "Sie haben erwähnt, dass lokale veränderbare Werte nicht von einem Abschluss erfasst werden können. Daher müssen Sie stattdessen ref verwenden. Der Grund dafür ist, dass veränderbare Werte, die im Abschluss erfasst wurden, auf dem Heap zugewiesen werden müssen (da der Abschluss am zugewiesen ist der Haufen). " von F # ref-veränderlichen vars vs Objektfeldern

Meiner Ansicht nach let mutable wird gegenüber Referenzzellen bevorzugt. Ich persönlich benutze Referenzzellen nur, wenn sie benötigt werden.

Der meiste Code, den ich schreibe, verwendet dank Rekursion und Tail-Aufrufen keine veränderlichen Variablen. Wenn ich eine Gruppe veränderbarer Daten habe, verwende ich einen Datensatz. Für Objekte verwende ich let mutableprivate veränderbare Variablen. Ich benutze Referenzzellen nur für Schließungen, im Allgemeinen für Ereignisse.

gradbot
quelle
8

Wie in diesem MSDN-Blog-Artikel im Abschnitt Vereinfachte Verwendung veränderlicher Werte beschrieben , benötigen Sie keine Ref-Zellen mehr für Lambdas. Im Allgemeinen brauchen Sie sie also überhaupt nicht mehr.

Andrii
quelle
4

Dieser Artikel von Brian könnte eine Antwort geben.

Mutables sind einfach zu verwenden und effizient (keine Verpackung), können jedoch nicht in Lambdas erfasst werden. Ref-Zellen können erfasst werden, sind jedoch ausführlich und weniger effizient (? - nicht sicher).

Mau
quelle
"(...) und weniger effizient" - wahrscheinlich benötigt ein Wrapper-Typ mehr Speicher.
JMCF125
2
Dies hat sich geändert, da F # 4.0-Mutable in den meisten Fällen erfasst werden kann und der Bedarf an Ref jetzt viel geringer ist.
Abel
3

Vielleicht möchten Sie einen Blick auf den Abschnitt Mutable Data im Wikibook werfen.

Der Einfachheit halber sind hier einige relevante Zitate:

Das veränderbare Schlüsselwort wird häufig mit Datensatztypen verwendet, um veränderbare Datensätze zu erstellen

Mutable-Variablen sind etwas eingeschränkt: Mutables sind außerhalb des Funktionsumfangs, in dem sie definiert sind, nicht zugänglich. Dies bedeutet insbesondere, dass es nicht möglich ist, eine veränderbare Funktion in einer Unterfunktion einer anderen Funktion zu referenzieren.

Ref-Zellen umgehen einige der Einschränkungen von Mutables. Ref-Zellen sind in der Tat sehr einfache Datentypen, die ein veränderliches Feld in einen Datensatztyp einschließen.

Da ref-Zellen auf dem Heap zugeordnet sind, können sie für mehrere Funktionen gemeinsam genutzt werden

danlei
quelle