@@ -334,36 +334,6 @@ \section{Zwischenergebnisse mitführen}
334334korrekt, sondern auch schnell: Sie benutzt keine Hilfsfunktion und
335335macht soviele rekursive Aufrufe wie die Eingabeliste Elemente hat, ihre
336336Laufzeit wächst also \textit {linear }.\index {lineares Wachstum}
337- Dass das so ist, kannst Du sehen, wenn Du die Auswertung eines Aufrufs
338- von \lstinline {invert} nachvollziehst:
339- %
340- \begin {alltt }\small
341- (invert \underline{(list 1 2 3 4)})
342- \(\ldots\evalsto\) (\underline{invert-helper} #<list 1 2 3 4> empty)
343- \(\ldots\evalsto\) (cond ((empty? #<list 1 2 3 4>) ...) ((cons? #<list 1 2 3 4>) ...))
344- \(\ldots\evalsto\) (invert-helper (rest #<list 1 2 3 4>) (cons (first #<list 1 2 3 4>) empty))
345- \(\ldots\evalsto\) (invert-helper #<list 2 3 4> (cons 1 empty))
346- \(\ldots\evalsto\) (invert-helper #<list 2 3 4> #<list 1>)
347- \(\ldots\evalsto\) (cond ((empty? #<list 2 3 4>) ...) ((cons? #<list 2 3 4>) ...))
348- \(\ldots\evalsto\) (invert-helper (rest #<list 2 3 4>) (cons (first #<list 2 3 4>) #<list 1>))
349- \(\ldots\evalsto\) (invert-helper #<list 3 4> (cons 2 #<list 1>))
350- \(\ldots\evalsto\) (invert-helper #<list 3 4> #<list 2 1>)
351- \(\ldots\evalsto\) (cond ((empty? #<list 3 4>) ...) ((cons? #<list 3 4>) ...))
352- \(\ldots\evalsto\) (invert-helper (rest #<list 3 4>) (cons (first #<list 3 4>) #<list 2 1>))
353- \(\ldots\evalsto\) (invert-helper #<list 4> (cons 3 #<list 2 1>))
354- \(\ldots\evalsto\) (invert-helper #<list 4> #<list 3 2 1>)
355- \(\ldots\evalsto\) (cond ((empty? #<list 4>) ...) ((cons? #<list 4>) ...))
356- \(\ldots\evalsto\) (invert-helper (rest #<list 4>) (cons (first #<list 4>) empty))
357- \(\ldots\evalsto\) (invert-helper #<empty-list> (cons 4 #<list 3 2 1>))
358- \(\ldots\evalsto\) (invert-helper #<empty-list> #<list 4 3 2 1>)
359- \(\ldots\evalsto\) (cond ((empty? #<empty-list>) #<list 4 3 2 1>) ((cons? #<empty-list>) ...))
360- \(\evalsto\) #<list 4 3 2 1>
361- \end {alltt }
362- %
363- Die höherere Effizienz hat allerdings auch ihren Preis. Du merkst
364- vielleicht, dass auffällig viele der Erläuterungen hier in
365- Anführungszeichen stehen. Das liegt daran, dass wir gar nicht so
366- einfach erklären können, wie wir die neue Funktion konstruiert haben.
367337
368338Übrigens: Da die Funktion \lstinline {invert} generell nützlich ist,
369339ist sie unter dem Namen
@@ -378,7 +348,7 @@ \section{Schablonen für Funktionen mit Akkumulator}
378348
379349Wir nehmen uns eine Funktion vor, die wir eigentlich schon kennen,
380350nämlich aus Abschnitt~\ref {sec:list-sum } auf Seite
381- \pageref {sec:list-sum }:
351+ \pageref {sec:list-sum }:\label { function:list-sum-acc }
382352%
383353\ begin{lstlisting}
384354; Summe der Elemente einer Liste von Zahlen berechnen
@@ -557,7 +527,7 @@ \section{Schablonen für Funktionen mit Akkumulator}
557527 (list-sum-helper list0 0)))
558528\end {lstlisting }
559529%
560- Als Nächstes ist der \lstinline {empty}-Zweig dran. Hier ist
530+ Als nächstes ist der \lstinline {empty}-Zweig dran. Hier ist
561531\lstinline {sum} die Summe aller Elemente vor \lstinline {list}, und,
562532weil \lstinline {list} leer ist, sind das \emph {alle} Elemente von
563533\lstinline {list0}. Deswegen ist \lstinline {sum} das gewünschte
@@ -1014,110 +984,107 @@ \section{Aktienkurse analysieren}
1014984\section {Kontext und Endrekursion }
1015985\label {sec:iteration }
1016986
1017- FIXME: "` strukturell rekursiv"'
987+ In diesem Abschnitt werfen wir einen Blick darauf, was eigentlich im
988+ Rechner abläuft bei der Auswertung rekursiver Funktionsaufrufe. Dabei
989+ wird ein wichtiger Unterschied zwischen den Funktionen mit Akkumulator
990+ und den "< normalen"> Funktionen davor sichtbar.
1018991
992+ Als Beispiel betrachten wir ein weiteres Mal \lstinline {list-sum},
993+ zunächst in der Version mit Akkumulator aus
994+ Abschnitt~\ref {function:list-sum-acc } auf
995+ Seite~\pageref {function:list-sum-acc }. Am besten ist, wenn Du Dir
996+ selbst im Stepper die Auswertung von
997+ \ begin{lstlisting}
998+ (list-sum (list 1 2 3 4))
999+ \end {lstlisting }
1000+ %
1001+ anschaust. Hier sind die wichtigsten Schritte bei der Auswertung:
1002+ %
1003+ \ begin{lstlisting}
1004+ (list-sum #<list 1 2 3 4>)
1005+ |\evalsto| (accumulate #<list 1 2 3 4> 0)
1006+ |\evalsto| (accumulate (rest #<list 1 2 3 4>) (+ (first #<list 1 2 3 4>) 0))
1007+ |\evalsto| (accumulate #<list 2 3 4> 1)
1008+ |\evalsto| (accumulate #<list 3 4> 3)
1009+ |\evalsto| (accumulate #<list 4> 6)
1010+ |\evalsto| (accumulate #<empty-list> 10)
1011+ |\evalsto| 10
1012+ \end {lstlisting }
1013+ %
1014+ Wir haben den Wert des \lstinline {sum}-Parameters immer untereinander
1015+ geschrieben, und man sieht gut, wie sich das Zwischenergebnis von 0
1016+ schrittweise auf das Endergebnis 10 zubewegt.
10191017
1020- FIXME
1021-
1022- Ein Vergleich der beiden Versionen der Fakultätsfunktion von
1023- S.~\pageref {page:factorial } und S.~\pageref {page:factorial-tail } zeigt, dass
1024- Formulierungen mit und ohne Akkumulator
1025- unterschiedliche Berechnungsprozesse erzeugen. Hier ein Prozess mit
1026- Akkumulator:
1027- %
1028- \begin {alltt }
1029- (! 4)
1030- \(\Longrightarrow\) (!-helper 4 1)
1031- \(\Longrightarrow\) (if (= 4 0) 1 (!-helper (- 4 1) (* 1 4)))
1032- \(\Longrightarrow\) (if #f 1 (!-helper (- 4 1) (* 1 4)))
1033- \(\Longrightarrow\) (!-helper (- 4 1) (* 1 4))
1034- \(\Longrightarrow\) (!-helper 3 4)
1035- \(\Longrightarrow\) (if (= 3 0) 4 (!-helper (- 3 1) (* 4 3)))
1036- \(\Longrightarrow\) (if #f 4 (!-helper (- 3 1) (* 4 3)))
1037- \(\Longrightarrow\) (!-helper (- 3 1) (* 4 3))
1038- \(\Longrightarrow\) (!-helper 2 12)
1039- \(\Longrightarrow\) (if (= 2 0) 12 (!-helper (- 2 1) (* 12 2)))
1040- \(\Longrightarrow\) (if #f 12 (!-helper (- 2 1) (* 12 2)))
1041- \(\Longrightarrow\) (!-helper (- 2 1) (* 12 2))
1042- \(\Longrightarrow\) (!-helper 1 24)
1043- \(\Longrightarrow\) (if (= 1 0) 24 (!-helper (- 1 1) (* 24 1)))
1044- \(\Longrightarrow\) (if #f 24 (!-helper (- 1 1) (* 24 1)))
1045- \(\Longrightarrow\) (!-helper (- 1 1) (* 1 24))
1046- \(\Longrightarrow\) (!-helper 0 24)
1047- \(\Longrightarrow\) (if (= 0 0) 24 (!-helper (- 0 1) (* 24 0)))
1048- \(\Longrightarrow\) (if #t 24 (!-helper (- 0 1) (* 24 0)))
1049- \(\Longrightarrow\) 24
1050- \end {alltt }
1051- %
1052- Demgegenüber hier der Prozess ohne Akkumulator:
1053- %
1054- \begin {alltt }
1055- (! 4)
1056- \(\Longrightarrow\) (if (= 4 0) 1 (* 4 (! (- 4 1))))
1057- \(\Longrightarrow\) (if #f 1 (* 4 (! (- 4 1))))
1058- \(\Longrightarrow\) (* 4 (! (- 4 1)))
1059- \(\Longrightarrow\) (* 4 (! 3))
1060- \(\Longrightarrow\) (* 4 (if (= 3 0) 1 (* 3 (! (- 3 1)))))
1061- \(\Longrightarrow\) (* 4 (if #f 1 (* 3 (! (- 3 1)))))
1062- \(\Longrightarrow\) (* 4 (* 3 (! (- 3 1))))
1063- \(\Longrightarrow\) (* 4 (* 3 (! 2)))
1064- \(\ldots\)
1065- \(\Longrightarrow\) (* 4 (* 3 (* 2 (! 1))))
1066- \(\Longrightarrow\) (* 4 (* 3 (* 2 (if (= 1 0) 1 (* 1 (! (- 1 1)))))))
1067- \(\Longrightarrow\) (* 4 (* 3 (* 2 (if #f ... (* 1 (! (- 1 1)))))))
1068- \(\Longrightarrow\) (* 4 (* 3 (* 2 (* 1 (! (- 1 1))))))
1069- \(\Longrightarrow\) (* 4 (* 3 (* 2 (* 1 (! 0)))))
1070- \(\Longrightarrow\) (* 4 (* 3 (* 2 (* 1 (if (= 0 0) 1 (* 0 (! (- 0 1))))))))
1071- \(\Longrightarrow\) (* 4 (* 3 (* 2 (* 1 (if #t 1 (* 0 (! (- 0 1)))))))))
1072- \(\Longrightarrow\) (* 4 (* 3 (* 2 (* 1 1))))
1073- \(\Longrightarrow\) (* 4 (* 3 (* 2 1)))
1074- \(\Longrightarrow\) (* 4 (* 3 2))
1075- \(\Longrightarrow\) (* 4 6)
1076- \(\Longrightarrow\) 24
1077- \end {alltt }
1078- %
1079- Es ist deutlich sichtbar, dass die Version ohne Akkumulator alle
1080- Multiplikationen bis zum Schluss "< aufstaut"> . Das heißt aber auch,
1081- dass im Laufe des Berechnungsprozesses Ausdrücke auftauchen, die desto
1082- größer werden je größer das Argument von \texttt {! } ist: Bei
1083- \texttt {(! 100) } werden zum Beispiel 100 Multiplikationen aufgestaut.
1084-
1085- Die Version mit Akkumulator hingegen scheint in der Größe der
1086- zwischenzeitlich auftretenden Ausdrücke begrenzt zu sein. Tatsächlich
1087- stellt sich das Wachstum der Version ohne Akkumulator bei der Version
1088- mit Akkumulator nicht ein.
1089-
1090- Der Grund dafür sind die Schablonen: In der Schablone für Funktionen
1091- ohne Akkumulator steht \texttt {(... (proc (- n 1)) ...) }, das
1092- heißt, um den rekursiven Aufruf von \texttt {proc } wird noch
1093- etwas "< herumgewickelt"> , oder, anders gesagt, mit dem Ergebnis des
1094- rekursiven Aufrufs passiert noch etwas. Das, was mit dem Ergebnis
1095- noch passiert, heißt der \textit {Kontext\index {Kontext} } des Aufrufs.
1096- Bei \texttt {! } ist der vollständige Ausdruck \texttt {(* n (! (- n
1097- 1))) }. Wenn aus diesem Ausdruck der rekursive Aufruf \texttt {(! (-
1098- n 1)) } herausgenommen wird, bleibt der Kontext \texttt {(* n
1099- \( \circ \) ) }, wobei $ \circ $ markiert, wo der Aufruf entfernt
1100- wurde. Tatsächlich wird in der Literatur diese Markierung
1101- \textit {Loch\index {Loch} } genannt und \texttt {[] } geschrieben. Der
1102- Kontext \texttt {(* n []) } macht deutlich, dass mit Ergebnis eines
1103- Aufrufs, der später für \texttt {[] } eingesetzt wird, noch \texttt {n }
1104- multipliziert wird. Dementsprechend stauen sich in der
1105- Reduktionsfolge die Multiplikationen mit den verschiedenen Werten von
1106- \texttt {n }.
1107-
1108- Bei der Fakultäts-Funktion mit Akkumulator ist der Ausdruck, zu dem
1109- der Rumpf bei $ \texttt {n} \neq 0 $ reduziert wird, \texttt {(!-helper (- n 1)
1110- (* n acc)) }. Der Kontext des Aufrufs von \texttt {!-helper } innerhalb
1111- dieses Ausdrucks ist \texttt {[] }, also \emph {leer }~-- \emph {nichts }
1112- passiert mehr mit dem Rückgabewert von \texttt {!-helper }, und damit stauen
1113- sich auch bei der Reduktion keine Kontexte an. Solche Funktionaufrufe
1114- ohne Kontext heißen \textit {endrekursiv\index {endrekursiv} }~-- eben,
1115- weil nach dem rekursiven Aufruf "< Ende"> ist.\footnote {Das Konzept des
1116- Aufrufs ohne Kontext ist nicht auf rekursive Aufrufe beschränkt. Im
1117- Englischen heißen solche Aufrufe allgemeiner \textit {tail
1118- calls\index {tail call} } (also ohne "< recursive"> ).}
1119- Die Berechnungsprozesse, die von endrekursiven Aufrufen generiert
1120- werden, heißen auch \textit {iterative\index {Iteration} } Prozesse.
1018+ Wenn Du das "< alte"> \lstlisting {list-sum} in
1019+ Abschnitt~\ref {sec:list-sum } auf Seite~\ref {sec:list-sum } im Stepper
1020+ laufen lässt, sieht das schon optisch ganz anders aus:
1021+ %
1022+ \ begin{lstlisting}
1023+ (list-sum #<list 1 2 3 4>)
1024+ |\evalsto| (+ (first #<list 1 2 3 4>) (list-sum (rest #<list 1 2 3 4>)))
1025+ |\evalsto| (+ 1 (list-sum #<list 2 3 4>))
1026+ |\evalsto| (+ 1 (+ 2 (list-sum #<list 3 4>))
1027+ |\evalsto| (+ 1 (+ 2 (+ 3 (list-sum #<list 4>))))
1028+ |\evalsto| (+ 1 (+ 2 (+ 3 (+ 4 (list-sum #<empty-list>)))))
1029+ |\evalsto| (+ 1 (+ 2 (+ 3 (+ 4 0))))
1030+ |\evalsto| (+ 1 (+ 2 (+ 3 4)))
1031+ |\evalsto| (+ 1 (+ 2 7))
1032+ |\evalsto| (+ 1 9)
1033+ |\evalsto| 10
1034+ \end {lstlisting }
1035+ %
1036+ Hier sieht man, dass die rekursiven Aufrufe die einzelnen Additionen
1037+ "< aufstauen"> , und die eigentliche Arbeit der Addition erst nach dem
1038+ letzten rekursiven Aufruf stattfindet. Dieses "< Aufstauen"> kommt
1039+ daher, dass der rekursive Aufruf in der alten Version innerhalb des
1040+ Aufrufs von \lstinline {+} steht:
1041+ %
1042+ \ begin{lstlisting}
1043+ (+ (first list) (list-sum (rest list)))
1044+ \end {lstlisting }
1045+ %
1046+ Bei der Version mit Akkumulator ist es genau umgekehrt und der
1047+ rekursive Aufruf steht um den Aufruf von \lstinline {+} herum:
1048+ %
1049+ \ begin{lstlisting}
1050+ (accumulate (rest list) (+ (first list) sum))
1051+ \end {lstlisting }
1052+ %
1053+ Wenn der Computer in der alten Version einen rekursiven Aufruf
1054+ auswertet, muss er sich merken, dass nach dem rekursiven Aufruf noch
1055+ eine Addition passieren muss. Entsprechend wird die Kette von
1056+ \lstinline {+}-Aufrufen bei jedem rekursiven Aufruf länger. Dieser
1057+ Aufruf von \lstinline {+} heißt der \textit {Kontext}\index {Kontext} des
1058+ rekursiven Aufrufs~-- er ist um ihn herumgewickelt.
1059+
1060+ Bei der Version mit Akkumulator hat der rekursive Aufruf von
1061+ \lstinline {accumulate} keinen Kontext, dementsprechend staut sich bei
1062+ der Auswertung und im Stepper da auch nichts auf. Ein rekursiver
1063+ Aufruf ohne Kontext heißt \textit {endrekursiv }\index {Endrekursion},
1064+ weil nach dem Aufruf nichts mehr passieren muss, der Aufruf also "< am
1065+ Ende"> steht.
1066+
1067+ Der Begriff "< Endrekursion"> ist etwas unglücklich: Ob ein
1068+ Funktionsaufruf einen Kontext hat oder nicht, hat eigentlich gar
1069+ nichts damit zu tun, ob er rekursiv ist oder nicht. Im Englischen
1070+ gibt es den besseren Begriff \textit {tail call }
1071+ \index {tail call@\textit {tail call }}, der sowohl auf rekursive
1072+ als auch nicht-rekursive Aufrufe zutrifft.
1073+
1074+ Dass der Aufruf im alten \lstinline {list-sum} nicht endrekursiv ist,
1075+ legt schon die Schablone fest, in der das Ergebnis des rekursiven
1076+ Aufrufs noch mit dem ersten Element der Liste kombiniert werden muss.
1077+ Bei der Schablone für Funktionen mit Akkumulator ist das nicht so,
1078+ entsprechend haben die entstehenden rekursiven Aufrufe auch keinen
1079+ Kontext.
1080+
1081+ Die Auswertungsprozesse, die von endrekursiven Aufrufen generiert
1082+ werden, gehen in einer geraden Linie voran und heißen auch
1083+ \textit {iterative\index {Iteration} } Prozesse. In anderen
1084+ Programmiersprachen spricht man auch von
1085+ \textit {Schleifen }\index {Schleife}; viele Programmiererinnen und
1086+ Programmierer benutzen deshalb den Namen \lstinline {loop} statt
1087+ \lstinline {accumulate}.
11211088
11221089\section {Das Phänomen der umgedrehten Liste }
11231090
0 commit comments