-
Notifications
You must be signed in to change notification settings - Fork 24
/
i1hop.tex
2457 lines (2315 loc) · 83.4 KB
/
i1hop.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
% Diese Datei ist Teil des Buchs "Schreibe Dein Programm!"
% Das Buch ist lizenziert unter der Creative-Commons-Lizenz
% "Namensnennung - Weitergabe unter gleichen Bedingungen 4.0 International (CC BY-SA 4.0)"
% https://creativecommons.org/licenses/by-sa/4.0/deed.de
\chapter{Higher-Order-Programmierung}
\label{cha:higher-order}
Der renommierte Informatiker Paul Hudak wurde einst gefragt, was die
drei wichtigsten Dinge beim Programmieren seien. Er antwortete:
"<Abstraktion, Abstraktion, Abstraktion">. In der Tat ist das
Mantra~\ref{mantra:abstraktion} auf Seite~\pageref{mantra:abstraktion}
eins der wichtigsten:
\mantraabstraktion*
\noindent In diesem Kapitel werden wir dieses Mantra konsequent anwenden. Dabei
kommt oft eine besondere Art Funktion heraus: Funktionen, die andere
Funktionen als Eingabe akzeptieren oder als Ausgabe liefern. Solche
Funktionen heißen auch \textit{Funktionen höherer
Ordnung}\index{Funktion!höherer Ordnung} oder
\textit{Higher-Order-Funktion}.\index{Higher-Order-Funktion} Um die
resultierende Art der Programmierung~-- die
\textit{Higher-Order-Programmierung}~-- geht es in diesem Kapitel.
\section{Fußball-Fakten ermitteln}
\mentioncode{higher-order/soccer.rkt}
%
Eine nahezu unerschöpfliche Quelle für Diskussionen stellen die
Fußballergebnisse dar. Hier stellen sich so bedeutsame Fragen wie:
\begin{itemize}
\item Wie hat diese Saison Bayern München gegen 1.~FC Kaiserslautern
auswärts gespielt?\footnote{Wir wissen, das ist schon eine Weile her.}
\item Wie viele Tore sind in dieser Saison gefallen?
\item Welche Mannschaft ist abstiegsgefährdet?
\end{itemize}
%
All diese Informationen speisen sich aus Ergebnissen der Spiele einer
Saison. Ein Spiel können wir folgendermaßen charakterisieren:
%
\begin{lstlisting}
; Ein Spiel hat folgende Eigenschaften:
; - Spieltag
; - Heimmannschaft
; - Heimmannschaft-Tore
; - Gastmannschaft
; - Gastmannschaft-Tore
\end{lstlisting}
%
Es handelt sich sichtlich um zusammengesetzte Daten. Wir müssen uns
überlegen, welche Signaturen zu den einzelnen Bestandteilen passen.
Spieltag und Tore sind allesamt natürliche Zahlen. Eine Mannschaft können
wir durch ihren Namen als Zeichenkette repräsentieren. Das ergibt
folgende Record-Definition:
%$
\indexvariable{game}
\begin{lstlisting}
(define-record game
make-game game?
(game-matchday natural)
(game-home-team string)
(game-home-goals natural)
(game-guest-team string)
(game-guest-goals natural))
\end{lstlisting}
%
Die Signatur \lstinline{string} in einer Signatur ist ziemlich
nichtssagend. (Das ist speziell bei \lstinline{string} oft so.) Wenn
wir zum Beispiel später noch Spielerinnen-Namen aufnehmen wollten,
wären das vielleicht auch Zeichenketten. Um das in Signaturen zu
unterscheiden, benutzen wir folgende Signaturdefinition:
%
\begin{lstlisting}
; Eine Mannschaft ist durch ihren Namen identifiziert.
(define team (signature string))
\end{lstlisting}
%
Außerdem ersetzen wir in der Definition von \lstinline{game} jeweils
\lstinline{string} durch \lstinline{game}.
Es folgen hier beispielhaft die Ergebnisse des ersten Spieltags der Bundesliga-Saison
2009/2010:
\begin{lstlisting}
(define game1 (make-game 1 "Wolfsburg" 2 "Stuttgart" 0))
(define game2 (make-game 1 "Mainz" 2 "Bayer 04" 2))
(define game3 (make-game 1 "Hertha" 1 "Hannover" 0))
(define game4 (make-game 1 "Bremen" 2 "Frankfurt" 3))
(define game5 (make-game 1 "Nürnberg" 1 "Schalke" 2))
(define game6 (make-game 1 "Dortmund" 1 "1. FC Köln" 0))
(define game7 (make-game 1 "Hoffenheim" 1 "Bayern" 1))
(define game8 (make-game 1 "Bochum" 3 "Gladbach" 3))
(define game9 (make-game 1 "Freiburg" 1 "Hamburg" 1))
(define day1
(list game1 game2 game3 game4 game5 game6 game7 game8 game9))
\end{lstlisting}
%
Eine recht einfache Frage ist die Bestimmung der Punktzahl, welche die
Gastgebermannschaft in einem bestimmten Spiel erzielt hat. Die
Punktzahl ist zwar eine natürliche Zahl, es gibt aber nur drei
Möglichkeiten: $0$, $1$ und $3$. Wir können also eine präzisere Signatur
als \lstinline{points} definieren:
%
\begin{lstlisting}
; Punktzahl in Spiel
(define points
(signature (enum 0 1 3)))
\end{lstlisting}
%
Unsere Funktion für die Bestimmung der Punktzahl hat Kurzbeschreibung,
Signatur, Tests und Gerüst wie folgt:\indexvariable{home-points}
%
\begin{lstlisting}
; Punktzahl für Heimmannschaft berechnen
(: home-points (game -> points))
(check-expect (home-points game1) 3)
(check-expect (home-points game2) 1)
(check-expect (home-points game3) 3)
(check-expect (home-points game4) 0)
(define home-points
(lambda (game)
...))
\end{lstlisting}
%
Entsprechend der Signatur der Ausgabe fallen die Spiele in drei
unterschiedliche Klassen, das heißt wir brauchen eine Verzweigung mit
drei Zweigen entsprechend den drei möglichen Punktzahlen:
%
\begin{lstlisting}
(define home-points
(lambda (game)
(cond
(... 3)
(... 0)
(... 1))))
\end{lstlisting}
%
Die Bedingungen der drei Zweige kommen aus den Fußballregeln~-- wir
müssen die Tore von Heim- und Gastmannschaft vergleichen:
%
\begin{lstlisting}
(define home-points
(lambda (game)
(cond
((> (game-home-goals game) (game-guest-goals game)) 3)
((< (game-home-goals game) (game-guest-goals game)) 0)
((= (game-home-goals game) (game-guest-goals game)) 1))))
\end{lstlisting}
%
Das funktioniert schon korrekt, ist aber noch unelegant.
Das \lstinline{(game-home-goals game)} ist dreimal wiederholt~-- wir
sollten eine lokale Definition einführen, damit es nur einmal
dasteht. Ebenso für den Aufruf von \lstinline{game-guest-goals}. Das
sieht dann so aus:\label{func:home-points}
%
\indexvariable{home-points}
\begin{lstlisting}
(define home-points
(lambda (game)
(define goals1 (game-home-goals game))
(define goals2 (game-guest-goals game))
(cond
((> goals1 goals2) 3)
((< goals1 goals2) 0)
((= goals1 goals2) 1))))
\end{lstlisting}
%
Du könntest berechtigterweise fragen, warum die lokalen Variablen
\lstinline{goals1} und \lstinline{goals2} heißen, und nicht
\lstinline{home-goals} und \lstinline{guest-goals} heißen. Das wird
bei der nächsten Funktion (hoffentlich) klar, welche die Punkte
ausrechnet, die dem Gästeteam zustehen:
%
\begin{lstlisting}
; Punktzahl für Gastmannschaft berechnen
(: guest-points (game -> points))
(define guest-points
(lambda (game)
(define goals1 (game-guest-goals game))
(define goals2 (game-home-goals game))
(cond
((> goals1 goals2) 3)
((< goals1 goals2) 0)
((= goals1 goals2) 1))))
\end{lstlisting}
%
Diese beiden Funktionen sind weitgehend identisch, der einzige
Unterschied ist die Definition der lokalen Variablen
\lstinline{goals1} und \lstinline{goals2}. Eine solche Duplizierung
von Code ist immer schlecht, vor allem, wenn sich etwas
ändert. Theoretisch könnte der Fußballbund die Regeln für die Vergabe
von Punkten ändern, und dann müssten wir beide Funktionen in
gleicher Weise anpassen. Die Lösung für das Problem zeigt sich
dadurch, dass wir aus dem gemeinsamen Code der beiden Funktionen
eine neue Funktion machen, entsprechend dem Abstraktions-Mantra:
\mantraabstraktion*
\noindent Um zu abstrahieren, kopieren wir eine der beiden Funktionsdefinitionen und
geben ihr einen neuen Namen, in diesem Fall \lstinline{compute-points}:
%
\begin{lstlisting}
(define compute-points
(lambda (game)
(define goals1 (game-guest-goals game))
(define goals2 (game-home-goals game))
(cond
((> goals1 goals2) 3)
((< goals1 goals2) 0)
((= goals1 goals2) 1))))
\end{lstlisting}
%
Nun identifizieren wir in der kopierten Funktion die Stellen, die sich
bei den beiden ursprünglichen Funktionen unterscheiden und ersetzen
diese Stellen durch neue Namen. Hier sind das gerade
\lstinline{game-guest-goals} und \lstinline{game-home-goals}, wir
ersetzen sie durch die Namen \lstinline{get-goals-1} und
\lstinline{get-goals-2}:
%
\begin{lstlisting}
(define compute-points
(lambda (game)
(define goals1 (get-goals-1 game))
(define goals2 (get-goals-2 game))
(cond
((> goals1 goals2) 3)
((< goals1 goals2) 0)
((= goals1 goals2) 1))))
\end{lstlisting}
%
Diese neuen Variablen sind noch ungebunden, wir müssen sie deshalb
noch im \lstinline{lambda} unterbringen:
%
\begin{lstlisting}
(define compute-points
(lambda (get-goals-1 get-goals-2 game)
(define goals1 (get-goals-1 game))
(define goals2 (get-goals-2 game))
(cond
((> goals1 goals2) 3)
((< goals1 goals2) 0)
((= goals1 goals2) 1))))
\end{lstlisting}
%
Damit können wir die bisherigen Definitionen von
\lstinline{home-points} und \lstinline{guest-points} ersetzen durch
die folgenden:
%
\indexvariable{home-points}
\indexvariable{guest-points}
\begin{lstlisting}
(define home-points
(lambda (game)
(compute-points game-home-goals game-guest-goals game)))
(define guest-points
(lambda (game)
(compute-points game-guest-goals game-home-goals game)))
\end{lstlisting}
%
Diese neuen Definitionen lassen die Gemeinsamkeiten und Unterschiede
beider Funktionen klar erkennen und vermeiden die doppelte Definition
der Fußball-Punkteregeln.
Der neuen Funktion fehlt noch eine Signatur. Die Funktion akzeptiert
drei Argumente. Das letzte hat die Signatur \lstinline{game}. Die
ersten beiden werden aus den Funktionen \lstinline{game-home-goals}
und \lstinline{game-guest-goals} bestückt, die jeweils die Signatur
\lstinline{(game -> natural)} haben. Daraus können wir die Signatur
für \lstinline{compute-points} zusammensetzen:
%
\begin{lstlisting}
(: compute-points ((game -> natural) (game -> natural) game -> points))
\end{lstlisting}
%
In der Signatur tauchen mehrere Pfeile auf, weil die Funktion
\lstinline{compute-points} ihrerseits zwei Funktionen als Argumente
akzeptiert. Solche Funktionen mit mehreren Pfeilen in der Signatur
heißen \textit{Funktionen höherer Ordnung\index{Funktion!höherer
Ordnung}} oder
\textit{Higher-Order-Funktionen\index{Higher-Order-Funktion}}.
Die Abstraktion bei der Entwicklung von \lstinline{compute-points}
hätten wir auch etwas anders anstellen können: Wir haben bei
\lstinline{compute-points} die beiden Parameter
\lstinline{get-goals-1} und \lstinline{get-goals-2} zu dem schon
bestehenden \lstinline{lambda} hinzugefügt. Wir können stattdessen
auch ein neues \lstinline{lambda} einfügen. (Bei Definitionen, in
denen noch kein \lstinline{lambda} steht, müssten wir das sowieso.)
Das sieht dann so aus:
%
\indexvariable{make-compute-points}
\begin{lstlisting}
(define make-compute-points
(lambda (get-goals-1 get-goals-2)
(lambda (game)
(define goals1 (get-goals-1 game))
(define goals2 (get-goals-2 game))
(cond
((> goals1 goals2) 3)
((< goals1 goals2) 0)
((= goals1 goals2) 1)))))
\end{lstlisting}
%
Die Signatur dieser Funktion unterscheidet sich leicht von der
Signatur von \lstinline{compute-points}. Sie akzeptiert nicht mehr drei
Argumente sondern nur zwei, aber liefert dafür eine Funktion, die von
einem Spiel die Punkte liefert:
%
\begin{lstlisting}
(: make-compute-points ((game -> natural) (game -> natural)
-> (game -> points)))
\end{lstlisting}
%
Mit der Funktion \lstinline{make-compute-points} können wir ebenfalls alternative Definitionen von
\lstinline{home-points} und \lstinline{guest-points} schreiben. Auf den ersten Blick ist
das umständlich, wenn wir das genauso machen wie mit
\lstinline{compute-points}, da wir noch mehr Klammern brauchen:
%
\indexvariable{guest-points}
\indexvariable{guest-points}
\begin{lstlisting}
(define home-points
(lambda (game)
((make-compute-points game-home-goals game-guest-goals) game)))
(define guest-points
(lambda (game)
((make-compute-points game-guest-goals game-home-goals) game)))
\end{lstlisting}
%
Vielleicht fällt Dir aber das Muster auf, das diese beiden
Definitionen gemeinsam haben:
%
\begin{lstlisting}
(define f
(lambda (x)
(g x)))
\end{lstlisting}
%
Das heißt, wenn das Programm \lstinline{f} aufruft, ruft diese
Funktion direkt \lstinline{g} auf, mit der gleichen Eingabe:
\lstinline{f} und \lstinline{g} sind also äquivalent, und wir könnten
genauso gut schreiben:
%
\begin{lstlisting}
(define f g)
\end{lstlisting}
%
Das gleiche Prinzip können wir auch auf \lstinline{home-points} und
\lstinline{guest-points} anwenden:
%
\indexvariable{guest-points}
\indexvariable{guest-points}
\begin{lstlisting}
(define home-points
(make-compute-points game-home-goals game-guest-goals))
(define guest-points
(make-compute-points game-guest-goals game-home-goals))
\end{lstlisting}
%
\textbf{Wichtig:} Diese beiden Definitionen müssen \emph{nach} der
Definition von \lstinline{compute-points} stehen, da davor
\lstinline{compute-points} noch nicht definiert ist.
Man kann auch schön an der Signatur von
\lstinline{make-compute-points} sehen, dass diese als Resultat eine
Funktion mit Signatur \lstinline{(game -> points)} liefert, das ist
gerade die gewünschte Signatur von \lstinline{home-points} und
\lstinline{guest-points}.
Vielleicht hast Du Dich über den Namen \lstinline{make-compute-points}
gewundert: Während die Funktion \lstinline{compute-points} direkt eine Punktzahl
berechnet, macht \lstinline{make-compute-points} eine Funktion, welche
die Punktzahl berechnet: Darum der Präfix \lstinline{make-}. Es
handelt sich also um eine Art "<Funktionsfabrik">.
\begin{aufgabeinline}
Wir hätten das neue \lstinline{lambda} auch unterhalb des alten
\lstinline{lambda} einfügen können statt oberhalb. Welche Signatur
hätte die Funktion \lstinline{make-compute-points} dann? Warum ist
das keine so gute Idee?
\end{aufgabeinline}
Die Abstraktionstechnik, die wir für die Konstruktion der Funktionen
\lstinline{compute-points} und \lstinline{make-compute-points}
verwendet haben, wenden wir in diesem Buch noch öfter an. Sie
verdient deshalb eine eigene Konstruktionsanleitung:
\begin{konstruktionsanleitung}{Abstraktion}
\label{ka:abstraktion}
Wenn Du zwei Definitionen geschrieben hast, die inhaltlich verwandt
sind und viele Ähnlichkeiten aufweisen, abstrahiere wie folgt:
%
\begin{enumerate}
\item Kopiere eine der beiden Definitionen und gib ihr einen neuen
Namen.
\item Ersetze die Stellen, bei denen sich die beiden Definitionen
unterscheiden, jeweils durch eine neue Variable.
\item Füge die neuen Variablen als Parameter zum \lstinline{lambda}
der Definition hinzu oder füge ein neues \lstinline{lambda} mit
diesen Parametern ein. Du musst gegebenenfalls rekursive Aufrufe
der Funktion anpassen.
\item Schreibe eine Signatur für die neue Funktion.
\item Ersetze die beiden alten Definitionen durch Aufrufe der neuen
Definition.
\end{enumerate}
\end{konstruktionsanleitung}
\section{Higher-Order-Funktionen auf Listen}
\mentioncode{higher-order/list.rkt}
%
Als Nächstes wollen wir herausbekommen, welche Spiele einer Saison
unentschieden ausgingen. Dazu ist zunächst eine Funktion notwendig,
die feststellt, ob \emph{ein} bestimmtes Spiel unentschieden war:
%
\indexvariable{game-draw?}
\begin{lstlisting}
; Ist Spiel unentschieden?
(: game-draw? (game -> boolean))
(define game-draw?
(lambda (g)
(= 1 (home-points g))))
\end{lstlisting}
%
Die Spiele der Saison liegen in einer Liste, wir schreiben also eine
Funktion, die aus einer Liste von Spielen die
unentschiedenen herausholt. Kurzbeschreibung, Signatur und Gerüst
folgen der Konstruktionsanleitung für Funktionen auf Listen.
Hier ist die Schablone:\indexvariable{drawn-games}
%
\begin{lstlisting}
; Unentschiedene Spiele rausfiltern
(: drawn-games ((list-of game) -> (list-of game)))
(check-expect (drawn-games day1) (list game2 game7 game8 game9))
(define drawn-games
(lambda (list)
...))
\end{lstlisting}
%
Die Funktion hat eine Liste als Eingabe, es kommt also die
entsprechende Schablone zur Anwendung:
%
\begin{lstlisting}
(define drawn-games
(lambda (list)
(cond
((empty? list) ...)
((cons? list)
... (first list) ...
... (drawn-games (rest list)) ...))))
\end{lstlisting}
%
Der rekursive Aufruf liefert die unentschiedenen Spiele des Rests, wir
müssen also nur noch beim ersten Spiel entscheiden, ob es
unentschieden ausging:
%
\begin{lstlisting}
(define drawn-games
(lambda (list)
(cond
((empty? list) ...)
((cons? list)
... (game-draw? (first list)) ...
... (drawn-games (rest list)) ...))))
\end{lstlisting}
%
Die Funktion \lstinline{game-draw?} liefert einen booleschen Wert,
den können wir eigentlich nur in eine binäre Verzweigung stecken:
%
\begin{lstlisting}
(define drawn-games
(lambda (list)
(cond
((empty? list) ...)
((cons? list)
(if (game-draw? (first list))
...
...)
... (drawn-games (rest list)) ...))))
\end{lstlisting}
%
Abhängig davon, ob das Spiel unentschieden ist oder nicht, bringen wir
es im Ergebnis unter:
%
\indexvariable{drawn-games}
\begin{lstlisting}
(define drawn-games
(lambda (list)
(cond
((empty? list) empty)
((cons? list)
(if (game-draw? (first list))
(cons (first list) (drawn-games (rest list)))
(drawn-games (rest list)))))))
\end{lstlisting}
%
Analog zu \lstinline{drawn-games} schreiben wir nun eine
Funktion, die alle Spiele aus einer Liste extrahiert, die die Heimmannschaft
gewonnen hat. Dazu brauchen wir eine Funktion analog zu
\lstinline{game-draw?}:
%
\indexvariable{home-won?}
\begin{lstlisting}
; Hat die Heimmannschaft gewonnen?
(: home-won? (game -> boolean))
(check-expect (home-won? game1) #t)
(check-expect (home-won? game2) #f)
(check-expect (home-won? game4) #f)
(define home-won?
(lambda (game)
(= 3 (home-points game))))
\end{lstlisting}
%
Die Funktion \lstinline{home-won-games} entsteht analog zu
\lstinline{drawn-games}.\enlargethispage{1ex}
%
\indexvariable{home-won-games}
\begin{lstlisting}
; Spiele herausfiltern, bei denen die Heimmannschaft gewann
(: home-won-games ((list-of game) -> (list-of game)))
(check-expect (home-won-games day1) (list game1 game3 game6))
\end{lstlisting}
\begin{lstlisting}
(define home-won-games
(lambda (list)
(cond
((empty? list) empty)
((cons? list)
(if (home-won? (first list))
(cons (first list)
(home-won-games (rest list)))
(home-won-games (rest list)))))))
\end{lstlisting}
%
Die beiden Funktionen \lstinline{drawn-games} und
\lstinline{home-won-games} sind bis auf den Namen fast identisch. Der
einzige Unterschied ist, dass an der Stelle von \lstinline{game-draw?}
in der zweiten Funktion \lstinline{home-won?} steht. Darüber können
wir nach Konstruktionsanleitung~\ref{ka:abstraktion} auf
Seite~\pageref{ka:abstraktion} abstrahieren. Dazu kopieren wir die
letzte Funktion und denken uns einen neuen Namen aus~-- da es in
beiden Fällen ums "<extrahieren"> geht, nehmen wir
\lstinline{extract-games}:
%
\begin{lstlisting}
(define extract-games
(lambda (list)
(cond
((empty? list) empty)
((cons? list)
(if (home-won? (first list))
(cons (first list)
(extract-games (rest list)))
(extract-games (rest list)))))))
\end{lstlisting}
%
Als Nächstes müssen wir die Stelle, die sich bei beiden Definitionen
unterscheidet (\lstinline{game-draw?} beziehungsweise
\lstinline{home-won?}), durch eine neue Variable bezeichnen. Die nennen wir
\lstinline{f?}:
%
\begin{lstlisting}
(define extract-games
(lambda (list)
(cond
((empty? list) empty)
((cons? list)
(if (f? (first list))
(cons (first list)
(extract-games (rest list)))
(extract-games (rest list)))))))
\end{lstlisting}
%
Als Nächstes müssen wir die neue Variable als Parameter zum
\lstinline{lambda} hinzufügen. Aufpassen: Die beiden rekursiven
Aufrufe müssen ebenfalls um den Parameter erweitert werden:
%
\indexvariable{extract-games}
\begin{lstlisting}
(define extract-games
(lambda (f? list)
(cond
((empty? list) empty)
((cons? list)
(if (f? (first list))
(cons (first list)
(extract-games f? (rest list)))
(extract-games f? (rest list)))))))
\end{lstlisting}
%
(Es ist Geschmackssache, ober der neue Parameter vor oder hinter die
alten kommt.)
Die Funktionsdefinition ist damit fertig, aber wir müssen noch eine
Signatur schreiben. Wir fangen mit der Signatur von
\lstinline{home-won-games} an und erweitern um den zusätzlichen
Parameter:
%
\begin{lstlisting}
(: extract-games (... (list-of game) -> (list-of game)))
\end{lstlisting}
%
Der zusätzliche Parameter ist \lstinline{f?}, den wir für
\lstinline{game-draw?} und \lstinline{home-won?} eingesetzt haben.
Diese beiden Funktionen haben die Signatur
\lstinline{(game -> boolean)}, das können wir also für die Ellipse
einsetzen:
%
\begin{lstlisting}
(: extract-games ((game -> boolean) (list-of game) -> (list-of game)))
\end{lstlisting}
%
Nun können wir Aufrufe der alten Funktionen durch Aufrufe der neuen
ersetzen. Wir tun das, indem wir die Testfälle für
\lstinline{drawn-games} und \lstinline{home-won-games} anpassen:
%
\begin{lstlisting}
(check-expect (extract-games game-draw? day1)
(list game2 game7 game8 game9))
(check-expect (extract-games home-won? day1)
(list game1 game3 game6))
\end{lstlisting}
%
Fertig!
Vielleicht hast Du das Gefühl, das Muster der Funktion
\lstinline{extract-games} schon gesehen zu haben. In der Tat sieht
die Funktion~\lstinline{live-dillos} in
Abschnitt~\ref{page:live-dillos} auf
Seite~\ref{page:live-dillos} ebenfalls den Funktionen
\lstinline{drawn-games} und \lstinline{home-won-games} sehr ähnlich.
Hier ist sie noch einmal zur Erinnerung:
%
\indexvariable{live-dillos}
\begin{lstlisting}
(: live-dillos ((list-of dillo) -> (list-of dillo)))
(define live-dillos
(lambda (dillos)
(cond
((empty? dillos) empty)
((cons? dillos)
(if (dillo-alive? (first dillos))
(cons (first dillos)
(live-dillos (rest dillos)))
(live-dillos (rest dillos)))))))
\end{lstlisting}
%
Und tatsächlich~-- wenn wir \lstinline{dillos} in \lstinline{list}
umbenennen und den gleichen Abstraktionsprozess wie bei
\lstinline{drawn-games} und \lstinline{home-won-games} durchlaufen,
dann entsteht die identische Funktionsdefinition zu
\lstinline{extract-games}. Allerdings gibt es dann immer noch einen
Unterschied~-- die Signaturen sind unterschiedlich.
Die Funktion \lstinline{extract-games} kann nur Listen von Spielen
verarbeiten, während \lstinline{live-dillos} eine Liste von
Gürteltieren akzeptiert. Können auch darüber~-- einmal
\lstinline{game}, das andere Mal \lstinline{dillo}~-- abstrahieren?
Können wir! Dazu sollten wir die Funktion noch umbenennen, wir wählen
\lstinline{extract-list} statt \lstinline{extract-games}. Vor allem
aber müssen wir die Signatur ändern, die bisher auf \lstinline{game}
abonniert ist. Auf Signaturebene abstrahieren wir durch
Signaturvariablen und ersetzen einfach mal probehalber jedes
\lstinline{game} durch \lstinline{%a}:
%
\begin{lstlisting}
(: extract-list ((%a -> boolean) (list-of %a) -> (list-of %a)))
\end{lstlisting}
%
Diese Funktion können wir nun versuchen zu verstehen: Sie akzeptiert
eine Liste von \lstinline{%a}s und eine Funktion, die \lstinline{%a}s
akzeptiert. Man kann daran fast schon sehen, was die Funktion macht:
Das einzige, was sie mit der Funktion anfangen kann, ist sie auf die
Elemente der Liste anzuwenden~-- sonst gibt es ja weit und breit keine
anderen \lstinline{%a}s. Dass die Funktion einen booleschen Wert
produziert, suggeriert schon, dass anhand dieses Werts unterschieden
wird. Die Signatur liefert also wertvolle Informationen darüber, was
die Funktion macht~-- wenn auch nicht alle.
%
\begin{aufgabeinline}
Schreibe eine andere sinnvolle Funktion mit der gleichen Signatur
wie \lstinline{extract-list}!
\end{aufgabeinline}
%
Wir betrachten noch eine weitere Anwendung von
\lstinline{extract-list}: Wir wollen aus einer Liste von Spielen die
Spiele extrahieren, an denen eine bestimmte Mannschaft teilgenommen
hat. Auch dazu definieren wir eine Hilfsfunktion, die feststellt, ob
eine Mannschaft bei einem Spiel die Heim- oder die Gastmannschaft ist:\label{func:plays-game}
%
\indexvariable{plays-game?}
\begin{lstlisting}
; Spielt Mannschaft bei Spiel?
(: plays-game? (team game -> boolean))
(check-expect (plays-game? "Wolfsburg" game1) #t)
(check-expect (plays-game? "Stuttgart" game1) #t)
(check-expect (plays-game? "Hannover" game1) #f)
(define plays-game?
(lambda (team game)
(or (string=? team (game-home-team game))
(string=? team (game-guest-team game)))))
\end{lstlisting}
%
Nehmen wir an, wir wollen alle Spiele mit Beteiligung von Nürnberg
extrahieren, indem wir die Funktion \lstinline{extract-list}
verwenden. Dafür brauchen wir eine Funktion mit
Signatur~\lstinline{(game -> boolean)}. Die können wir folgendermaßen
definieren:
%
\indexvariable{plays-nürnberg?}
\begin{lstlisting}
; Spielt Nürnberg mit?
(: plays-nürnberg? (game -> boolean))
(check-expect (plays-nürnberg? game1) #f)
(check-expect (plays-nürnberg? game5) #t)
(define plays-nürnberg?
(lambda (game)
(plays-game? "Nürnberg" game)))
\end{lstlisting}
%
Damit können wir zum Beispiel alle Nürnberg-Spiele aus der Saison
extrahieren:
%
\begin{lstlisting}
(extract-list plays-nürnberg? season-2009/2010)
\end{lstlisting}
%
Soweit so gut. Aber was, wenn wir auch die Spiele des HSV extrahieren
wollen? Wir könnten eine Funktion \lstinline{plays-hamburg?}
definieren, aber wenn wir viele solcher Anfragen stellen, wird es auf
Dauer umständlich, für jeden Verein extra eine Definiton zu schreiben.
Einfacher ist es, stattdessen die Funktion, die wir an
\lstinline{extract-list} übergeben, "<ad hoc"> mit einem
\lstinline{lambda} zu konstruieren:\label{code:extract-list-plays-game}
%
\begin{lstlisting}
(extract-list (lambda (game) (plays-game? "Nürnberg" game))
season-2009/2010)
(extract-list (lambda (game) (plays-game? "Hamburg" game))
season-2009/2010)
\end{lstlisting}
%
Wir sind von der Signatur her vorgegangen: \lstinline{extract-list}
erwartet eine Funktion mit einem Parameter der Signatur
\lstinline{game}, also schreiben wir ein \lstinline{lambda} mit einem
entsprechenden Parameter. Wir wollen alle Spiele extrahieren, bei
denen Nürnberg beziehungsweise Hamburg dabei ist, also schreiben wir
den entsprechenden Ausdruck in den Rumpf.
%
\begin{aufgabeinline}
Schreibe eine Funktion \lstinline{games-playing}, die alle Spiele
aus einer Liste extrahiert, bei denen eine bestimmte Mannschaft dabei war.
\end{aufgabeinline}
%
Die Funktion \lstinline{extract-list} ist so praktisch, dass sie unter
dem Namen \lstinline{filter}\indexvariable{filter} fest
eingebaut ist.\label{func:filter}
\section{Listen umwandeln}
Bestimmte Fußballstatistiken beschäftigen sich damit, wieviele Tore in
einem Spiel, an einem Spieltag oder in einer ganzen Saison geschossen
wurden. Grundlage dafür ist die Anzahl Tore eines einzelnen Spiels.
Hier sind Kurzbeschreibung, Signatur, Tests und Gerüst dafür:
%
\begin{lstlisting}
; Gesamttore eines Spiels berechnen
(: total-goals (game -> natural))
(check-expect (total-goals game1) 2)
(check-expect (total-goals game2) 4)
(define total-goals
(lambda (game)
...))
\end{lstlisting}
%
Als Schablone machen wir davon Gebrauch, dass die Eingabe
zusammensetzte Daten sind:
%
\begin{lstlisting}
(define total-goals
(lambda (game)
... (game-matchday game) ...
... (game-home-team game) ...
... (game-home-goals game) ...
... (game-guest-team game) ...
... (game-guest-goals game) ...))
\end{lstlisting}
%
Für die Gesamt-Torzahl benötigen wir nur
\lstinline{(game-home-goals game)}
und
\lstinline{(game-guest-goals game)}, die addiert werden:
%
\indexvariable{total-goals}
\begin{lstlisting}
(define total-goals
(lambda (game)
(+ (game-home-goals game)
(game-guest-goals game))))
\end{lstlisting}
%
Als Nächstes wollen wir die Gesamttore jeweils aller Spiele eines
Spieltags herausbekommen. Dazu wollen wir aus einer Liste der Spiele
eine Liste der zugehörigen Gesamttore machen:
%
\begin{lstlisting}
; Gesamttore in einer Liste von Spielen berechnen
(: list-total-goals ((list-of game) -> (list-of natural)))
\end{lstlisting}
%
Das erste Element des Ergebnisses soll die Toranzahl des ersten Spiels
der Eingabeliste sein, das zweite Element die Toranzahl des zweiten
Spiels undsoweiter. Dieser Test illustriert die Funktionsweise:
%
\begin{lstlisting}
(check-expect (list-total-goals day1)
(list 2 4 1 5 3 1 2 6 2))
\end{lstlisting}
%
Es folgt das Gerüst mit der obligatorischen Schablone für Funktionen
auf Listen:
%
\begin{lstlisting}
(define list-total-goals
(lambda (list)
(cond
((empty? list) ...)
((cons? list)
... (first list)
... (list-total-goals (rest list)) ...))))
\end{lstlisting}
%
Falls die Eingabe eine leere Liste ist, so kann auch die Ausgabe nur
leer sein. Im anderen Zweig steht in der Schablone das erste Spiel:
von dem muss die Funktion die Toranzahl ermitteln, für den Rest ist
das schon passiert. Die beiden Ergebnisse kombinieren wir mit
\lstinline{cons}:
%
\indexvariable{list-total-goals}
\begin{lstlisting}
(define list-total-goals
(lambda (list)
(cond
((empty? list) empty)
((cons? list)
(cons (total-goals (first list))
(list-total-goals (rest list)))))))
\end{lstlisting}
%
\begin{aufgabeinline}
Benutze die Funktion \lstinline{list-sum} aus
Abschnitt~\ref{sec:list-sum} auf Seite~\pageref{sec:list-sum}, um
eine Funktion zu schreiben, welche die durchschnittliche Toranzahl
einer Liste von Spielen berechnet.
\end{aufgabeinline}
%
Fans sind vielleicht weniger an Gesamttoren interessiert als an den
Toren ihrer Mannschaft. Damit wir uns da einen Überblick verschaffen
können, benötigen wir wieder erst einmal eine Funktion, welche für ein
einzelnes Spiel die Anzahl der Tore einer bestimmten Mannschaft berechnet:
%
\begin{lstlisting}
; Tore einer Mannschaft aus einem Spiel berechnen
(: team-goals (team game -> natural))
(check-expect (team-goals "Wolfsburg" game1) 2)
(check-expect (team-goals "Stuttgart" game1) 0)
\end{lstlisting}
%
Was soll passieren, wenn die gewünschte Mannschaft bei dem Spiel nicht
dabei war? Ein Fehler:
%
\begin{lstlisting}
(check-error (team-goals "Hannover" game1))
\end{lstlisting}
%
Gerüst und Schablone machen wieder davon Gebrauch, dass
\lstinline{game} zusammengesetzte Daten sind:
%
\begin{lstlisting}
(define team-goals
(lambda (team game)
... (game-matchday game) ...
... (game-home-team game) ...
... (game-home-goals game) ...
... (game-guest-team game) ...
... (game-guest-goals game) ...))
\end{lstlisting}
%
Außerdem gibt es eine Fallunterscheidung in den Daten, je nachdem, ob
die gesuchte Mannschaft die Heim- oder die Gastmannschaft ist, was zu
einer Verzweigung mit zwei Zweigen führt:
%
\indexvariable{team-goals}
\begin{lstlisting}
(define team-goals
(lambda (team game)
(cond
((string=? team (game-home-team game))
(game-home-goals game))
((string=? team (game-guest-team game))
(game-guest-goals game)))))
\end{lstlisting}
%
Jetzt wollen wir auch diese Funktion benutzen, um eine ganze Liste von
Spielen, bei denen eine bestimmte Mannschaft mitspielt, in die Liste
der Tore dieser Mannschaft verwandelt. Signatur und Kurzbeschreibung
sehen so aus:
%
\begin{lstlisting}
; Tore einer Mannschaft aus einer Liste von Spielen auflisten
(: list-team-goals (team (list-of game) -> (list-of natural)))
\end{lstlisting}
%
\begin{aufgabeinline}
Schreibe einen Testfall, der eine Liste der Tore von Hamburg in
einer Saison berechnet. Benutze dazu \lstinline{filter}!
\end{aufgabeinline}
%
Gerüst und Schablone sehen so aus:
%
\begin{lstlisting}
(define list-team-goals
(lambda (team list)
(cond
((empty? list) ...)
((cons? list)
... (first list) ...
... (list-team-goals team (rest list)) ...))))
\end{lstlisting}
%
Und wieder kommt bei einer leeren Liste als Eingabe eine leere Liste
heraus, und wir wenden im \lstinline{cons}-Fall die zuvor geschriebene
Hilfsfunktion auf das erste Element an und bauen mit \lstinline{cons}
die Ergebnis-Liste:
%
\indexvariable{list-team-goals}
\begin{lstlisting}
(define list-team-goals
(lambda (team list)
(cond
((empty? list) empty)
((cons? list)
(cons (team-goals team (first list))
(list-team-goals team (rest list)))))))
\end{lstlisting}
%
Fertig! Allerdings ruft das Abstraktions-Mantra: Die beiden Funktionen
\lstinline{list-total-goals} und \lstinline{list-team-goals} ähneln
sich stark. Sie unterscheiden sich nur darin, dass erstere
\lstinline{total-goals} auf dem ersten Element der Liste aufruft
und die zweite \lstinline{team-goals}.
Um zu abstrahieren, gehen wir wieder nach
Konstruktionsanleitung~\ref{ka:abstraktion} auf
Seite~\pageref{ka:abstraktion} vor. Wir kopieren eine der beiden
Funktionen (wir nehmen die erste) und geben ihr einen neuen Namen:
%
\begin{lstlisting}
(define list-apply
(lambda (list)
(cond
((empty? list) empty)
((cons? list)
(cons (total-goals (first list))
(list-apply (rest list)))))))
\end{lstlisting}
%
Die Funktion heißt \lstinline{list-apply}, weil sie eine Funktion auf
alle Elemente einer Liste anwendet.
Als Nächstes müssen wir eine Variable dort einführen, wo die beiden
Funktionen sich unterscheiden, nämlich bei \lstinline{total-goals}.
Wir nennen die Funktion einfach \lstinline{f}:
%
\begin{lstlisting}
(define list-apply
(lambda (list)