Lesen Sie vor dem Downvoting den folgenden Absatz
Ich poste die Antwort für diejenigen, die diesen Ansatz möglicherweise besser für ihre Denkweise finden. Die Antwort enthält möglicherweise redundante Informationen und Gedanken, aber es ist das, was ich brauchte, um das Problem anzugehen. Da dies eine weitere Antwort auf dieselbe Frage ist, ist es offensichtlich, dass sie sich erheblich mit den anderen Antworten überschneidet, erzählt jedoch die Geschichte, wie ich dieses Konzept verstehen könnte.
In der Tat begann ich, diese Notizen als persönliche Aufzeichnung meiner Gedanken aufzuschreiben, während ich versuchte, dieses Thema zu verstehen. Ich habe den ganzen Tag gebraucht, um den Kern zu berühren, wenn ich ihn wirklich habe.
Mein langer Weg zum Verständnis dieser einfachen Übung
Einfacher Teil: Was müssen wir bestimmen?
Was passiert mit dem folgenden Beispielaufruf?
foldl f z [1,2,3,4]
kann mit dem folgenden Diagramm visualisiert werden (das auf Wikipedia ist , aber ich habe es zuerst auf einer anderen Antwort gesehen ):
_____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
(Als Randnotiz, wenn die Verwendung der foldl
einzelnen Anwendungen von f
nicht ausgeführt wird und die Ausdrücke genau so angezeigt werden, wie ich sie oben geschrieben habe; im Prinzip könnten sie berechnet werden, wenn Sie von unten nach oben gehen, und genau das foldl'
tut es.)
Die Übung fordert uns im Wesentlichen heraus, foldr
anstatt foldl
die Schrittfunktion (also verwenden wir s
statt f
) und den anfänglichen Akkumulator (also verwenden wir ?
statt z
) entsprechend zu ändern . Die Liste bleibt gleich, sonst wovon reden wir?
Der Aufruf an foldr
muss so aussehen:
foldr s ? [1,2,3,4]
und das entsprechende Diagramm ist dieses:
_____what does the last call return?
/
s
/ \
1 s
/ \
2 s
/ \
3 s
/ \
4 ? <-
Der Anruf führt zu
s 1 (s 2 (s 3 (s 4 ?)))
Was sind s
und ?
? Und was sind ihre Typen? Es sieht so aus, als wäre s
es eine Funktion mit zwei Argumenten, ähnlich f
, aber lassen Sie uns nicht zu Schlussfolgerungen springen. Lassen wir auch ?
einen Moment beiseite und beobachten, dass dies z
ins Spiel kommen muss, sobald es 1
ins Spiel kommt. Wie kann jedoch z
der Aufruf der Funktion "Vielleicht zwei Argumente" s
, nämlich der Aufruf , ins Spiel kommen s 1 (…)
? Wir können diesen Teil des Rätsels lösen, indem s
wir ein wählen, das 3 Argumente anstelle der 2, die wir zuvor erwähnt haben, akzeptiert, so dass der äußerste Aufruf dazu s 1 (…)
führt, dass eine Funktion ein Argument annimmt , an das wir übergeben können z
!
Dies bedeutet, dass wir den ursprünglichen Aufruf möchten, der auf erweitert wird
f (f (f (f z 1) 2) 3) 4
gleichwertig sein mit
s 1 (s 2 (s 3 (s 4 ?))) z
oder mit anderen Worten, wir wollen die teilweise angewendete Funktion
s 1 (s 2 (s 3 (s 4 ?)))
äquivalent zu der folgenden Lambda-Funktion sein
(\z -> f (f (f (f z 1) 2) 3) 4)
Wieder sind die "einzigen" Stücke, die wir brauchen, s
und ?
.
Wendepunkt: Funktionszusammensetzung erkennen
Lassen Sie uns das vorherige Diagramm neu zeichnen und rechts schreiben, was jeder Aufruf s
entsprechen soll:
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 ? <-
Ich hoffe, aus der Struktur des Diagramms geht hervor, dass sich (…)
auf jeder Zeile die rechte Seite der darunter liegenden Zeile befindet. Besser ist es die Funktion, die vom vorherigen (unten) Aufruf an zurückgegeben wurde s
.
Es sollte auch klar sein, dass ein Aufruf s
mit Argumenten x
und y
die (vollständige) Anwendung y
auf die teilweise Anwendung f
auf das einzige Argument ist x
. Da die teilweise Anwendung von f
to x
als Lambda geschrieben werden kann (\z -> f z x)
, führt die vollständige Anwendung y
auf das Lambda (\z -> y (f z x))
, das ich in diesem Fall umschreiben würde als y . (\z -> f z x)
; die Wörter in einen Ausdruck zu übersetzen, den s
wir bekommen
s x y = y . (\z -> f z x)
(Dies ist s x y z = y (f z x)
dasselbe wie das Buch, wenn Sie die Variablen umbenennen.)
Das letzte Bit ist: Was ist der anfängliche "Wert" ?
des Akkumulators? Das obige Diagramm kann umgeschrieben werden, indem die verschachtelten Aufrufe zu Kompositionsketten erweitert werden:
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 ? <-
Wir sehen hier, dass s
sich aufeinanderfolgende Teilanwendungen von einfach "stapeln" f
, aber das y
in s x y = y . (\z -> f z x)
legt nahe, dass die Interpretation von s 4 ?
(und wiederum allen anderen) eine führende Funktion verfehlt, die mit dem Lambda ganz links zusammengesetzt werden soll.
Das ist nur unsere ?
Aufgabe: Es ist Zeit, ihm einen Grund für seine Existenz zu geben und nicht nur einen Platz im Aufruf an einzunehmen foldr
. Was können wir wählen, um die resultierenden Funktionen nicht zu ändern? Antwort: id
Das Identitätselement in Bezug auf den Kompositionsoperator (.)
.
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
Die gesuchte Funktion ist also
myFoldl f z xs = foldr (\x g a -> g (f a x)) id xs z
step = curry $ uncurry (&) <<< (flip f) *** (.)