F #: niech mutable vs. ref

83

Po pierwsze, przyznaję, że to pytanie może być powtórzeniem; po prostu daj mi znać.

Jestem ciekaw, jaka jest ogólna „najlepsza praktyka” w sytuacjach, gdy pożądana jest zmienność. Wydaje się, że F # oferuje do tego dwie możliwości: let mutablepowiązanie, które wydaje się działać jak zmienne w „większości” języków, oraz komórkę odniesienia (utworzoną za pomocą reffunkcji), która wymaga jawnego wyłuskiwania.

Istnieje kilka przypadków, w których jedna jest „zmuszony” do jednej lub drugiej: NET współdziałanie ma tendencję do korzystania z zmienny <-, aw workflow obliczeń należy używać refz :=. Więc te przypadki są dość jasne, ale jestem ciekawy, co zrobić, tworząc własne zmienne zmienne poza tymi scenariuszami. Jaką przewagę ma jeden styl nad drugim? (Być może pomocny byłby dalszy wgląd w implementację).

Dzięki!

J Cooper
źródło
4
Zauważ, że w F # wersji 4 mutable może być używany tam, gdzie potrzebowałeś ref. blogs.msdn.com/b/fsharpteam/archive/2014/11/12/…
James Moore

Odpowiedzi:

134

Mogę tylko poprzeć to, co powiedział gradbot - kiedy potrzebuję mutacji, wolę let mutable.

Odnośnie implementacji i różnic między tymi dwiema refkomórkami - są zasadniczo implementowane przez bardzo prosty rekord, który zawiera zmienne pole rekordu. Możesz je łatwo napisać samodzielnie:

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 }

Istotną różnicą między tymi dwoma podejściami jest to, że let mutableprzechowuje zmienną wartość na stosie (jako zmienną modyfikowalną w C #), podczas gdy refprzechowuje zmienną wartość w polu rekordu przydzielonego na sterty. Może to mieć pewien wpływ na wydajność, ale nie mam żadnych liczb ...

Dzięki temu refmożna aliasować wartości zmienne, które używają - co oznacza, że ​​można utworzyć dwie wartości odwołujące się do tej samej wartości mutowalnej:

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
źródło
2
Przypomnijmy: bycie na stosie lub na stercie jest szczegółem implementacji i nie jest dokładnie związane z pytaniem (ale mimo wszystko doskonała odpowiedź)
Bruno Brant
5
Twierdziłbym, że wiedza o tym, czy coś wiąże się z narzutem związanym z alokacją i gromadzeniem sterty, czy też nie, jest niezwykle istotna przy podejmowaniu decyzji o tym, jaka jest najlepsza praktyka.
jackmott
@jackmott sprawdź ten artykuł Erica Lipperta zatytułowany The Stack Is An Implementation Detail
jaromey
5
@jaromey tak, rozumiem, że zasadniczo nie zgadzam się z Ericem w tej kwestii. Nie możesz odkładać na bok kwestii wydajności, rozważając, co jest „najlepszą praktyką”. Często twierdzi się, że wydajność nie ma znaczenia, ale tak wiele programów działa wolno z powodu śmierci tysiąca szczegółów implementacji.
jackmott
18

Powiązane pytanie: „Wspomniałeś, że lokalne zmienne wartości nie mogą być przechwytywane przez zamknięcie, więc musisz zamiast tego użyć ref. Powodem tego jest to, że zmienne wartości przechwycone w zamknięciu muszą być przydzielone na stercie (ponieważ zamknięcie jest przydzielane na stos). ” z F # zmiennych zmiennoprzecinkowych vs pól obiektów

Myślę, że let mutablejest preferowany w stosunku do komórek odniesienia. Osobiście używam komórek referencyjnych tylko wtedy, gdy są potrzebne.

Większość kodu, który piszę, nie używa zmiennych mutowalnych dzięki rekurencji i wywołaniom tail. Jeśli mam grupę zmiennych danych, używam rekordu. Dla obiektów, których używam, let mutableaby tworzyć prywatne zmienne zmienne. Tak naprawdę używam komórek referencyjnych tylko do zamknięć, ogólnie wydarzeń.

gradbot
źródło
9

Jak opisano w tym artykule na blogu MSDN w sekcji Uproszczone użycie zmiennych wartości , nie potrzebujesz już komórek ref dla lambd. Więc generalnie już ich nie potrzebujesz.

Andrii
źródło
4

Ten artykuł autorstwa Briana może dostarczyć odpowiedzi.

Mutable są łatwe w użyciu i wydajne (bez zawijania), ale nie można ich przechwytywać w lambdach. Komórki ref można przechwytywać, ale są one pełne i mniej wydajne (? - nie jesteś tego pewien).

Mau
źródło
„(…) i mniej wydajne” - prawdopodobnie typ otoki zajmuje więcej pamięci.
JMCF125,
2
To się zmieniło, ponieważ w większości przypadków można przechwycić mutację F # 4.0, a potrzeba ref jest teraz znacznie mniejsza.
Abel
3

Możesz zajrzeć do sekcji Zmienne dane w wikibooku.

Dla wygody podajemy kilka odpowiednich cytatów:

Słowo kluczowe mutable jest często używane z typami rekordów do tworzenia modyfikowalnych rekordów

Zmienne modyfikowalne są w pewnym stopniu ograniczone: zmienne mutable są niedostępne poza zakresem funkcji, w której zostały zdefiniowane. W szczególności oznacza to, że nie można odwoływać się do zmiennej w podfunkcji innej funkcji.

Komórki ref omijają niektóre ograniczenia mutable. W rzeczywistości komórki ref są bardzo prostym typem danych, który zawija zmienne pole w typie rekordu.

Ponieważ komórki referencyjne są przydzielane na stercie, mogą być współużytkowane przez wiele funkcji

danlei
źródło