Przed przystąpieniem do głosowania w dół przeczytaj poniższy akapit
Publikuję odpowiedź dla tych osób, które mogą uznać to podejście za lepiej dopasowane do ich sposobu myślenia. Odpowiedź prawdopodobnie zawiera zbędne informacje i myśli, ale właśnie tego potrzebowałem, aby rozwiązać problem. Co więcej, ponieważ jest to kolejna odpowiedź na to samo pytanie, oczywiste jest, że w znacznym stopniu pokrywa się ona z innymi odpowiedziami, jednak opowiada o tym, jak mogłem uchwycić tę koncepcję.
Rzeczywiście, zacząłem zapisywać te notatki jako osobisty zapis moich przemyśleń, próbując zrozumieć ten temat. Zajęło mi cały dzień, zanim dotknąłem jej rdzenia, jeśli naprawdę to mam.
Moja długa droga do zrozumienia tego prostego ćwiczenia
Łatwa część: co musimy ustalić?
Co dzieje się z następującym przykładem wywołania
foldl f z [1,2,3,4]
można zwizualizować za pomocą następującego diagramu (który jest na Wikipedii , ale pierwszy raz zobaczyłem go w innej odpowiedzi ):
_____results in a number
/
f f (f (f (f z 1) 2) 3) 4
/ \
f 4 f (f (f z 1) 2) 3
/ \
f 3 f (f z 1) 2
/ \
f 2 f z 1
/ \
z 1
(Na marginesie, gdy używanie foldl
każdej aplikacji f
nie jest wykonywane, a wyrażenia są rozumiane tak, jak napisałem je powyżej; w zasadzie można je obliczyć w miarę przechodzenia od dołu do góry i właśnie to foldl'
robi.)
Ćwiczenie zasadniczo wymaga od nas użycia foldr
zamiast foldl
odpowiedniej zmiany funkcji step (więc używamy s
zamiast f
) i akumulatora początkowego (więc używamy ?
zamiast z
); lista pozostaje taka sama, w przeciwnym razie o czym mówimy?
Wezwanie do foldr
musi wyglądać następująco:
foldr s ? [1,2,3,4]
a odpowiedni diagram jest następujący:
_____what does the last call return?
/
s
/ \
1 s
/ \
2 s
/ \
3 s
/ \
4 ? <-
Wezwanie kończy się
s 1 (s 2 (s 3 (s 4 ?)))
Jakie są s
i ?
? A jakie są ich typy? Wygląda na s
to, że jest to funkcja dwuargumentowa, podobnie f
, ale nie przechodźmy do wniosków. Ponadto, zostawmy ?
na chwilę na bok i niech zaobserwować, że z
musi wchodzić w grę, gdy tylko 1
wchodzi w grę; jak jednak może z
wejść do gry w wywołaniu funkcji może dwuargumentowej s
, a mianowicie w wywołaniu s 1 (…)
? Możemy rozwiązać tę część zagadki, wybierając opcję, s
która przyjmuje 3 argumenty, a nie 2, o których wspominaliśmy wcześniej, tak aby najbardziej zewnętrzne wywołanie s 1 (…)
spowodowało, że funkcja przyjmowała jeden argument, do którego możemy przejść z
!
Oznacza to, że chcemy pierwotnego wywołania, które rozszerza się do
f (f (f (f z 1) 2) 3) 4
być równoważne
s 1 (s 2 (s 3 (s 4 ?))) z
lub, innymi słowy, chcemy częściowo zastosowanej funkcji
s 1 (s 2 (s 3 (s 4 ?)))
być odpowiednikiem następującej funkcji lambda
(\z -> f (f (f (f z 1) 2) 3) 4)
Ponownie, jedyne, czego potrzebujemy, to s
i ?
.
Punkt zwrotny: rozpoznaj kompozycję funkcji
Przerysujmy poprzedni diagram i napiszmy po prawej stronie, czemu każde wywołanie s
ma odpowiadać:
s s 1 (…) == (\z -> f (f (f (f z 1) 2) 3) 4)
/ \
1 s s 2 (…) == (\z -> f (f (f z 2) 3) 4)
/ \
2 s s 3 (…) == (\z -> f (f z 3) 4)
/ \
3 s s 4 ? == (\z -> f z 4)
/ \
4 ? <-
Mam nadzieję, że ze struktury diagramu jasno wynika, że (…)
w każdym wierszu znajduje się prawa strona linii poniżej; lepiej, jest to funkcja zwrócona z poprzedniego (poniżej) wywołania funkcji s
.
Powinno być również jasne, że wywołanie do s
z argumentami x
i y
jest (pełnym) zastosowaniem y
do częściowego zastosowania f
do jedynego argumentu x
. Ponieważ częściowe zastosowanie f
to x
można zapisać jako lambda (\z -> f z x)
, pełne zastosowanie y
do niego daje lambdę (\z -> y (f z x))
, którą w tym przypadku przepisałbym jako y . (\z -> f z x)
; przetłumaczenie słów na wyrażenie, s
które otrzymujemy
s x y = y . (\z -> f z x)
(To jest to samo s x y z = y (f z x)
, co książka, jeśli zmienisz nazwy zmiennych).
Ostatni bit to: jaka jest początkowa „wartość” ?
akumulatora? Powyższy diagram można przepisać, rozszerzając zagnieżdżone wywołania, aby stały się łańcuchami kompozycji:
s s 1 (…) == (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2) . (\z -> f z 1)
/ \
1 s s 2 (…) == (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2)
/ \
2 s s 3 (…) == (\z -> f z 4) . (\z -> f z 3)
/ \
3 s s 4 ? == (\z -> f z 4)
/ \
4 ? <-
Widzimy tutaj, że s
po prostu „gromadzi” kolejne częściowe zastosowania f
, ale y
in s x y = y . (\z -> f z x)
sugeruje, że interpretacja s 4 ?
(i kolejno wszystkich innych) pomija funkcję wiodącą, którą należy skomponować z lambdą znajdującą się najbardziej po lewej stronie.
To tylko nasza ?
funkcja: nadszedł czas, aby dać mu powód do jego istnienia, oprócz zajmowania miejsca w wezwaniu foldr
. Co możemy wybrać, aby nie zmieniać wynikowych funkcji? Odpowiedź: id
, ten element neutralny w stosunku do operatora kompozycji (.)
.
s s 1 (…) == id . (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2) . (\z -> f z 1)
/ \
1 s s 2 (…) == id . (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2)
/ \
2 s s 3 (…) == id . (\z -> f z 4) . (\z -> f z 3)
/ \
3 s s 4 id == id . (\z -> f z 4)
/ \
4 id
Tak więc szukaną funkcją jest
myFoldl f z xs = foldr (\x g a -> g (f a x)) id xs z
step = curry $ uncurry (&) <<< (flip f) *** (.)