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 foldlkażdej aplikacji fnie 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 foldrzamiast foldlodpowiedniej zmiany funkcji step (więc używamy szamiast f) i akumulatora początkowego (więc używamy ?zamiast z); lista pozostaje taka sama, w przeciwnym razie o czym mówimy?
Wezwanie do foldrmusi 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ą si ?? A jakie są ich typy? Wygląda na sto, że jest to funkcja dwuargumentowa, podobnie f, ale nie przechodźmy do wniosków. Ponadto, zostawmy ?na chwilę na bok i niech zaobserwować, że zmusi wchodzić w grę, gdy tylko 1wchodzi w grę; jak jednak może zwejść 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ę, sktó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 si ?.
Punkt zwrotny: rozpoznaj kompozycję funkcji
Przerysujmy poprzedni diagram i napiszmy po prawej stronie, czemu każde wywołanie sma 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 sz argumentami xi yjest (pełnym) zastosowaniem ydo częściowego zastosowania fdo jedynego argumentu x. Ponieważ częściowe zastosowanie fto xmożna zapisać jako lambda (\z -> f z x), pełne zastosowanie ydo 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, sktó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 spo prostu „gromadzi” kolejne częściowe zastosowania f, ale yin 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) *** (.)