To nie jest bardzo dobre wytłumaczenie.
"wypłynął" oznacza po prostu, że:
\x -> let y = ... in z
jeśli ...
nie wspomina x to może być wypłynął z lambda:
let y = ... in \x -> z
co oznacza, że zostanie obliczone tylko raz, , co może zaoszczędzić dużo czasu, jeśli koszt jest wysoki: ...
. GHC jest jednak konserwatywne, jeśli chodzi o takie optymalizacje, ponieważ mogą one wprowadzić kosmiczne wycieki. (Chociaż to robi zrobić dla drugiej definicji, jeśli podasz mu podpis typu, jak Daniel Fischer wskazuje w swojej odpowiedzi.)
Nie chodzi jednak o automatyczną optymalizację. Pierwszy fragment kodu definiuje fib'
poza lambdą, podczas gdy drugi definiuje go wewnątrz (lambda jest niejawna w fib x = ...
, co jest równoważne fib = \x -> ...
), o czym mówi cytat.
Nawet to nie ma znaczenia; Istotne jest to, że w pierwszym fragmencie, map fib' [0 ..]
występuje poza lambdą, a więc jego wynik jest dzielony pomiędzy wszystkie aplikacje lambda (w tym kodzie "lambda" powstaje z częściowej aplikacji (!!)
). W tym drugim przypadku jest on wewnątrz lambda, a więc prawdopodobnie będzie ponownie obliczony dla każdej aplikacji fib
.
Rezultat końcowy jest taki, że poprzednia implementacja buforuje wartości i jest o wiele bardziej wydajna niż ta druga. Zauważ, że wydajność pierwszego fragmentu jest zależna od faktu, że fib'
nie podlega bezpośredniemu nawrotowi, ale zamiast tego przez fib
, a co za tym idzie, zyskuje dzięki memoizacji.
Jest to związane z rozszerzeniem eta; ten drugi fragment jest rozwinięciem eta pierwszego. Ale oświadczenie, które zacytowałeś, nie wyjaśnia w ogóle, co się dzieje.
Należy zauważyć, że jest to zachowanie specyficzne dla implementacji, a nie część semantyki Haskella. Jednak wszystkie rozsądne implementacje będą zachowywać się w ten sposób.
Ta odpowiedź i odpowiedź Daniela Fischera są obecnie rekurencyjne. – misterbee
@misterbee: na szczęście tylko programiści Haskell będą je czytać, a my jesteśmy leniwi, prawda? – leftaroundabout