2011-12-20 6 views
27

To może być teraz trochę niewyraźne, ale zastanawiałem się przez chwilę. Zgodnie z moją wiedzą z ! można upewnić parametrem konstruktora danych jest oceniane przed wartością jest skonstruowany:Zalety ścisłych pól w typach danych

data Foo = Bar !Int !Float 

Często myślałem, że lenistwo to wielka rzecz. Teraz, kiedy przechodzę przez źródła, widzę ścisłe pola częściej niż ! -less wariant.

Co jest tego zaletą i dlaczego nie powinienem pozostawić go tak leniwym?

Odpowiedz

32

O ile nie przechowujesz dużych obliczeń w polach Int i Float, znaczny narzut może narastać dzięki wielu trywialnym obliczeniom budującym się w tony. Na przykład, jeśli wielokrotnie dodajesz 1 do leniwego pola Float w typie danych, zużywa ono coraz więcej pamięci, aż w rzeczywistości wymusisz na polu, obliczając to.

Często chcesz przechowywać kosztowne obliczenia w polu. Ale jeśli wiesz, że nie zrobisz czegoś takiego z wyprzedzeniem, możesz oznaczyć to pole jako ścisłe i unikać ręcznego dodawania wszędzie, aby uzyskać pożądaną efektywność.

Jako dodatkowy bonus, kiedy podano flagę -funbox-strict-fields GHC rozpakuje surowe pola typów danych bezpośrednio do samego typu danych, co jest możliwe, ponieważ wie, że zawsze będą oceniane, a zatem nie thunk musi być przydzielone; w tym przypadku wartość pręta zawierałaby słowa maszynowe zawierające Int i Float bezpośrednio wewnątrz wartości pręta w pamięci, zamiast zawierać dwa wskaźniki do brył, które zawierają dane.

Lenistwo jest bardzo użyteczną rzeczą, ale w niektórych przypadkach po prostu przeszkadza i utrudnia obliczenia, szczególnie w przypadku małych pól, na które zawsze patrzy się (a zatem wymuszane) lub które są często modyfikowane, ale nigdy nie bardzo kosztowne obliczenia. Surowe pola pomagają przezwyciężyć te problemy bez konieczności modyfikacji wszystkich zastosowań typu danych.

To, czy jest bardziej powszechne niż leniwe pola, zależy od rodzaju kodu, który czytasz; prawdopodobnie nie zobaczysz, że jakiekolwiek funkcjonalne struktury drzew używają na przykład ścisłych pól, na przykład, ponieważ bardzo korzystają z lenistwa.

Powiedzmy masz AST z konstruktora dla operacji infiksowych:

data Exp = Infix Op Exp Exp 
     | ... 

data Op = Add | Subtract | Multiply | Divide 

nie chcesz, aby pola Exp surowe, jak stosując politykę jak oznaczałoby to, że cała AST jest oceniany za każdym razem, gdy patrzysz na węzeł najwyższego poziomu, co oczywiście nie jest tym, co chcesz czerpać z lenistwa. Jednak pole Op nigdy nie będzie zawierało kosztownych obliczeń, które chcesz odłożyć do późniejszego terminu, a obciążenie związane z operacją thunk per infix może stać się drogie, jeśli masz naprawdę głęboko zagnieżdżone parsowanie. Tak więc dla konstruktora infiksów, musisz ustawić pole Op jako surowe, ale pozostawić dwa pola leniwy Exp.

Tylko typy jednego konstruktora mogą być rozpakowane.

+1

Czy AST nie powinno być tak surowe? – Lanbo

+0

Rozszerzyłem moją odpowiedź o kilka opracowań, używając AST jako przykładu. – ehird

4

Istnieje pewien napowietrzenie związane z leniwością - kompilator musi utworzyć odgłos dla wartości przechowującej obliczenia, aż do uzyskania wyniku. Jeśli wiesz, że zawsze będziesz potrzebował rezultatu wcześniej czy później, to może mieć sens wymuszenie oceny wyniku.

4

Lazyness przychodzi kosztem, inaczej każdy język by to miał.

Koszt to 2-krotnie:

  1. może potrwać dłużej, aby skonfigurować thunk (czyli opis tego, co ma być obliczana, gdy będzie obliczana w końcu) niż zrobić operację od razu.
  2. Uuvaluated thangs, które idą jako nie ścisłe argumenty do innych thunks, które idą jako nie ścisłe argumenty do jeszcze innych thunks itp. Będą zużywać coraz więcej pamięci. Niestety, te miksy mogą również zawierać odniesienia do pamięci, która nie jest już dostępna, tj. Pamięć, która może zostać uwolniona, gdy tylko ocena zostanie poddana ocenie, zapobiegając w ten sposób, aby garbage collector mógł wykonać swoją pracę. Przykładem może być thunk, który ma aktualizować pewną wartość w drzewie. Powiedzmy, że ta wartość ma wartość 100 MB innych wartości. Jeśli nie ma już odniesienia do starego drzewa, pamięć ta jest marnowana, o ile ocena nie jest oceniana.
5

W uzupełnieniu do informacji przekazywanych przez innych odpowiedzi, należy pamiętać:

Do mojej wiedzy z ! można upewnić parametrem konstruktora danych jest oceniane przed wartością jest skonstruowany

to ciekawe patrzeć jak deep the parameter is evaluated - to jak z seq i $! ocenianego do WHNF.

Biorąc pod uwagę typy danych

data Foo = IntFoo !Int | FooFoo !Foo | BarFoo !Bar 
data Bar = IntBar Int 

wyrażenie

let x' = IntFoo $ 1 + 2 + 3 
in x' 

oceniona WHNF wytwarza wartość IntFoo 6 (== pełni ocenić, == NF).
Ponadto wyrażenie

let x' = FooFoo $ IntFoo $ 1 + 2 + 3 
in x' 

oceniona WHNF wytwarza wartość FooFoo (IntFoo 6) (== całkowicie oceniane == NF).
Jednakże wyrażenie to

let x' = BarFoo $ IntBar $ 1 + 2 + 3 
in x' 

oceniona WHNF wytwarza wartość BarFoo (IntBar (1 + 2 + 3)) (! = W pełni ocenić,! = NF).

Główne: Surowość parametru !Bar niekoniecznie pomoże, jeśli konstruktorzy danych z Bar nie zawierają surowe kryteria siebie.

+0

Bardzo jasne przykłady! Czy mogę sprawdzić, jak głęboko oceniany jest parametr w ghci lub innych narzędziach? – wenlong

+1

@wenlong Zobacz kolejną [odpowiedź na wizualizację thunks] (http://stackoverflow.com/questions/16767028/any-way-to-visualiza-a-thunk-function-lub-how-to-view-a-function -for-a-general), w szczególności narzędzie ['ghc-vis'] (http://felsin9.de/nnis/ghc-vis/#basic-usage) – mucaho