Co oznacza postać normalna słaba głowa (WHNF)? Co oznaczają Forma normalna głowy (HNF) i Forma normalna (NF)?
Real World Haskell stwierdza:
Znajoma funkcja seq ocenia wyrażenie do tego, co nazywamy formą głowy normalną (w skrócie HNF). Zatrzymuje się, gdy dotrze do najbardziej zewnętrznego konstruktora („głowy”). Różni się to od postaci normalnej (NF), w której wyrażenie jest całkowicie oceniane.
Usłyszysz także, jak programiści Haskell odnoszą się do normalnej postaci słabej głowy (WHNF). W przypadku normalnych danych słaba postać głowy normalna jest taka sama jak postać głowy normalna. Różnica pojawia się tylko w przypadku funkcji i jest zbyt zawiła, aby nas tutaj martwić.
Przeczytałem kilka zasobów i definicji ( Haskell Wiki i Haskell Mail List oraz bezpłatny słownik ), ale nie rozumiem. Czy ktoś może podać przykład lub podać definicję laika?
Zgaduję, że byłoby to podobne do:
WHNF = thunk : thunk
HNF = 0 : thunk
NF = 0 : 1 : 2 : 3 : []
Jak seq
i ($!)
odnoszą się do WHNF i HNF?
Aktualizacja
Nadal jestem zdezorientowany. Wiem, że niektóre odpowiedzi mówią, by zignorować HNF. Po przeczytaniu różnych definicji wydaje się, że nie ma różnicy między zwykłymi danymi w WHNF i HNF. Wydaje się jednak, że istnieje różnica w funkcji. Jeśli nie było różnicy, dlaczego jest to seq
konieczne foldl'
?
Innym punktem zamieszania jest Wiki Haskell, które stwierdza, że seq
ogranicza się do WHNF i nie zrobi nic w poniższym przykładzie. Następnie mówią, że muszą użyć, seq
aby wymusić ocenę. Czy to nie wymusza na HNF?
Typowy kod przepełnienia stosu początkujących:
myAverage = uncurry (/) . foldl' (\(acc, len) x -> (acc+x, len+1)) (0,0)
Ludzie, którzy rozumieją normalną postać sekwoi i słabej głowy (whnf), mogą natychmiast zrozumieć, co się tutaj dzieje. (acc + x, len + 1) jest już w whnf, więc seq, który redukuje wartość do whnf, nic na to nie robi. Ten kod będzie budował thunks, tak jak w oryginalnym przykładzie zwijania, będą one po prostu wewnątrz krotki. Rozwiązaniem jest tylko wymuszenie elementów krotki, np
myAverage = uncurry (/) . foldl' (\(acc, len) x -> acc `seq` len `seq` (acc+x, len+1)) (0,0)
źródło
Odpowiedzi:
Spróbuję wyjaśnić w prosty sposób. Jak zauważyli inni, głowa w normalnej formie nie dotyczy Haskella, więc nie będę tego tutaj rozważał.
Normalna forma
Wyrażenie w normalnej formie jest w pełni oceniane i żadne podwyrażenie nie może być dalej oceniane (tj. Nie zawiera niezbadanych zespołów).
Wszystkie te wyrażenia są w normalnej formie:
Te wyrażenia nie są w normalnej formie:
Słaba głowa normalna forma
Wyrażenie w postaci normalnej słabej głowy zostało ocenione do najbardziej zewnętrznego konstruktora danych lub abstrakcji lambda ( głowa ). Podwyrażenia mogły, ale nie musiały być ocenione . Dlatego każde wyrażenie normalnej formy jest również w słabej formie normalnej głowy, chociaż odwrotnie nie ma to miejsca w ogóle.
Aby ustalić, czy wyrażenie ma słabą głowę w normalnej formie, musimy spojrzeć tylko na najbardziej zewnętrzną część wyrażenia. Jeśli jest to konstruktor danych lub lambda, ma on normalną słabą głowę. Jeśli jest to aplikacja funkcyjna, nie jest.
Te wyrażenia są w normalnej formie słabej głowy:
Jak wspomniano, wszystkie wyżej wymienione wyrażenia w postaci normalnej są również w słabej postaci normalnej.
Te wyrażenia nie są w normalnej formie słabej głowy:
Przepełnienie stosu
Ocena wyrażenia do postaci normalnej słabej głowy może wymagać uprzedniej oceny innych wyrażeń w WHNF. Na przykład, aby ocenić
1 + (2 + 3)
WHNF, musimy najpierw ocenić2 + 3
. Jeśli ocena pojedynczego wyrażenia prowadzi do zbyt wielu z tych zagnieżdżonych ocen, wynikiem jest przepełnienie stosu.Dzieje się tak, gdy budujesz duże wyrażenie, które nie generuje żadnych konstruktorów danych ani lambd, dopóki duża jego część nie zostanie oceniona. Są one często spowodowane przez tego rodzaju użycie
foldl
:Zwróć uwagę, jak musi sięgać dość głęboko, zanim zdoła uzyskać wyraz w normalnej formie słabej głowy.
Możesz się zastanawiać, dlaczego Haskell nie zmniejsza z wyprzedzeniem wewnętrznych wyrażeń? Wynika to z lenistwa Haskella. Ponieważ ogólnie nie można założyć, że każde podwyrażenie będzie potrzebne, wyrażenia są oceniane z zewnątrz w.
(GHC ma analizator dokładności, który wykryje pewne sytuacje, w których podwyrażenie jest zawsze potrzebne, i może następnie ocenić je z wyprzedzeniem. Jest to jednak tylko optymalizacja i nie powinieneś na nim polegać, aby uchronić Cię przed przepełnieniem).
Z drugiej strony tego rodzaju wyrażenie jest całkowicie bezpieczne:
Aby uniknąć budowania tych dużych wyrażeń, gdy wiemy, że wszystkie podwyrażenia będą musiały zostać ocenione, chcemy zmusić wewnętrzne części do oceny przed czasem.
seq
seq
to specjalna funkcja używana do wymuszania oceny wyrażeń. Jego semantykaseq x y
oznacza, że ilekroćy
jest oceniana jako słaba głowa w normalnej formie,x
jest również oceniana w postaci słabej głowa w normalnej formie.Jest to między innymi miejsca stosowane w definicji
foldl'
ścisłego wariantufoldl
.Każda iteracja
foldl'
zmusza akumulator do WHNF. W ten sposób unika się tworzenia dużego wyrażenia, a zatem unika się przepełnienia stosu.Ale jak wspomina przykład w HaskellWiki, nie oszczędza cię to we wszystkich przypadkach, ponieważ akumulator jest oceniany tylko do WHNF. W tym przykładzie akumulator jest krotką, więc wymusi jedynie ocenę konstruktora krotki, a nie
acc
lublen
.Aby tego uniknąć, musimy sprawić, aby ocena konstruktora krotek wymusiła ocenę
acc
ilen
. Robimy to za pomocąseq
.źródło
\x -> 1 + 1
jest z WHNF, ale nie z HNF.seq
argumenty?:set +s
. Możesz wtedy zobaczyć, żefoldl' f
ostatecznie przydziela więcej thunks niżfoldl' f'
.Część opisująca lenistwo w Haskell Wikibooks w sekcji Normalne formy Thunks and Weak Head Normal zapewnia bardzo dobry opis WHNF wraz z tym pomocnym przedstawieniem:
źródło
Programy Haskell są wyrażeniami i są uruchamiane przez wykonanie oceny .
Aby ocenić wyrażenie, zastąp wszystkie aplikacje funkcyjne ich definicjami. Kolejność, w jakiej to robisz, nie ma większego znaczenia, ale nadal jest ważna: zacznij od aplikacji zewnętrznej i przejdź od lewej do prawej; nazywa się to leniwą oceną .
Przykład:
Ocena zostaje zatrzymana, gdy nie ma już więcej aplikacji funkcyjnych do zastąpienia. Wynik ma postać normalną (lub zmniejszoną postać normalną , RNF). Bez względu na to, w jakiej kolejności oceniasz wyrażenie, zawsze będziesz mieć taką samą normalną formę (ale tylko wtedy, gdy ocena się zakończy).
Istnieje nieco inny opis leniwej oceny. Mianowicie mówi, że wszystko należy oceniać tylko do słabej głowy w normalnej formie . Istnieją dokładnie trzy przypadki wyrażenia w WHNF:
constructor expression_1 expression_2 ...
(+) 2
lubsqrt
\x -> expression
Innymi słowy, głowa wyrażenia (tj. Najbardziej zewnętrzna aplikacja funkcji) nie może być dalej oceniana, ale argument funkcji może zawierać nieocenione wyrażenia.
Przykłady WHNF:
Notatki
źródło
seq
wfoldl'
życie oceny od WHNF do HNF?seq expr1 expr2
oceni pierwsze wyrażenieexpr1
do WHNF przed oceną drugiego wyrażeniaexpr2
.Dobre wyjaśnienie z przykładami podano na stronie http://foldoc.org/Weak+Head+Normal+Form Forma normalna głowy upraszcza nawet fragmenty wyrażenia wewnątrz abstrakcji funkcji, podczas gdy „słaba” forma normalna głowy zatrzymuje się przy abstrakcjach funkcji .
Ze źródła, jeśli masz:
to jest w normalnej formie słabej głowy, ale nie w normalnej formie głowy ... ponieważ możliwa aplikacja utknęła w funkcji, której nie można jeszcze ocenić.
Rzeczywista normalna forma głowy byłaby trudna do skutecznego wdrożenia. Wymagałoby to szukania wewnątrz funkcji. Zaletą słabej głowy normalnej formy jest to, że nadal można implementować funkcje jako nieprzezroczyste, a zatem jest ona bardziej kompatybilna z kompilowanymi językami i optymalizacją.
źródło
WHNF nie chce oceniać ciała lambdów, więc
seq
chce, aby jego pierwszy argument był w WHNF, więcocenia na
zamiast tego, co będzie używać HNF
źródło
Zasadniczo, załóżmy, że masz jakieś thunk,
t
.Teraz, jeśli chcemy ocenić
t
WHNF lub NHF, które są takie same, z wyjątkiem funkcji, stwierdzilibyśmy, że otrzymujemy coś takiegot1 : t2
gdziet1
it2
są gromady. W tym przypadkut1
byłby to twój0
(a raczej0
niezły bzdurny brak dodatkowego rozpakowania)seq
i$!
ewaluować WHNF. Zauważ, żeźródło