forked from deinprogramm/schreibe-dein-programm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathi1gem.tex
1331 lines (1240 loc) · 45.3 KB
/
i1gem.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{Gemischte Daten}
\label{cha:gemischte-daten}
Manchmal kommen an einer Stelle in unserem Problem verschiedene
Klassen der gleichen Sorte Daten vor:
%
\begin{itemize}
\item Ein Tier kann ein Gürteltier oder ein Papagei sein.
\item Eine Koordinate kann eine kartesische Koordinate oder eine
Polarkoordinate sein.
\item Ein Essen kann ein Frühstück, Mittagessen oder Abendessen sein.
\end{itemize}
%
Solche Daten heißen \textit{gemischte Daten\index{gemischte
Daten}}, und es handelt sich um eine weitere Form von
Falluntscheidungen, neben den aus Kapitel~\ref{cha:conditionals}
bekannten Aufzählungen und Zahlenbereichen.
Obwohl die Daten verschiedenartig sind, unterstützen sie doch
gemeinsame Operationen: Das Gewicht eines Tiers kann sowohl für
Gürteltiere als auch Papageien berechnet werden, der Abstand vom
Ursprung kann für beide Koordinatendarstellungen berechnet werden, die
Anzahl der Gänge kann für jede Art Essen bestimmt werden.
\section{Gemischte Daten}
\label{sec:mixed-data}
\label{sec:animal}
\mentioncode{gemischte-daten/animal.rkt}
%
In der Einleitung war die Rede von Papageien:\index{Papagei} die benutzen wir, um
gemischte Daten einzuführen. Vorher müssen wir jedoch Papageien mit den bekannten
Mitteln definieren. Wir erweitern dafür die Datei mit dem Gürteltier
aus dem vorigen Kapitel.
Genau wie bei Gürteltieren interessiert uns bei Papageien das Gewicht,
aber wir nehmen an, da Papageien in der Regel nicht auf texanischen
Highways überfahren werden, dass sie immer lebendig sind. Außerdem
betrachten wir ausschließlich sprechende Papageien, die jeweils einen
einzelnen Satz sagen können. Hier die Datendefinition:
%
\begin{lstlisting}
; Ein Papagei hat folgende Eigenschaften:
; - Gewicht in Gramm
; - Satz, den er sagt
\end{lstlisting}
%
Hier die dazu passende Record-Definition:
%
\indexvariable{parrot}
\begin{lstlisting}
(define-record parrot
make-parrot
(parrot-weight natural)
(parrot-sentence string))
\end{lstlisting}
%
\ldots{} und die passenden Signaturen:
%
\begin{lstlisting}
(: make-parrot (natural string -> parrot))
(: parrot-weight (parrot -> natural))
(: parrot-sentence (parrot -> string))
\end{lstlisting}
%
Hier zwei Beispiele für Papageien mit Kommentaren, die die Beziehung
zwischen Daten und Information beschreiben:
%
\begin{lstlisting}
(define parrot1 (make-parrot 10000 "Der Gärtner war's.")) ; 10kg, Miss Marple
(define parrot2 (make-parrot 5000 "Ich liebe Dich.")) ; 5kg, Romantiker
\end{lstlisting}
%
Du kannst Dir vielleicht vorstellen, dass Papageien und
Gürteltiere sich in einem Programm begegnen, also \emph{gemischt}
vorkommen. Papageien und Gürteltiere gehören zum gemeinsamen
Oberbegriff \textit{Tier}\index{Tier}. Dafür könnte eine
Beschreibung so aussehen:
\medskip
\noindent Ein \textit{Tier} ist eins der folgenden:
%
\begin{itemize}
\item ein Gürteltier
\item ein Papagei
\end{itemize}
%
Die Formulierung "<eins der folgenden"> ist Dir schon aus
Abschnitt~\ref{sec:datendefinition} und
Konstruktionsanleitung~\ref{ka:fallunterscheidung} auf
Seite~\pageref{ka:fallunterscheidung} bekannt: Sie deutet auf eine
Fallunterscheidung\index{Fallunterscheidung} hin.
Da aber Gürteltiere und Papageien nicht als nur jeweils ein Wert
repräsentiert sind, handelt es sich nicht um eine Aufzählung: Die
beiden Fälle der Fallunterscheidung beschreiben unterschiedliche
\textit{Klassen}\index{Klasse} von Tieren, jede mit ihrer eigenen
Signatur. Damit liegt eine neue Organisation von Daten vor:
\textit{gemischte Daten}.\index{gemischte Daten} Entsprechend ist es
durchaus sinnvoll, nach dem "<Gewicht eines Tiers"> zu fragen oder
"<ein Tier zu füttern">, was wir im folgenden auch vorhaben.
Die Beschreibung des Begriffs "<Tier"> ist bereits als Datendefinition
geeignet, und muss für Inklusion im Programm nur als Kommentar
umformatiert werden:
%
\begin{lstlisting}
; Ein Tier ist eins der folgenden:
; - Gürteltier
; - Papagei
\end{lstlisting}
%
Bei zusammengesetzen Daten kann die Datendefinition in eine
Record-Definition überführt werden. In diesem Fall ist für jede
einzelne Klasse
Tier jeweils schon eine Record-Definition da. Wenn wir Tiere im
allgemeinen in
Funktionen verarbeiten wollen, brauchen wir allerdings eine Signatur
für Tiere. Zum Beispiel wollen wir eine Funktion schreiben, die für jedes
Tier das Gewicht ermittelt. Diese Funktion könnte folgende Signatur haben:
%
\begin{lstlisting}
(: animal-weight (animal -> natural))
\end{lstlisting}
%%% HK: das \textbf war wohl ein Irrtum?
Wir brauchen also eine Definition für die Signatur \lstinline{animal}.
Diese sieht folgendermaßen aus:
%
\begin{lstlisting}
(define animal
(signature
(mixed dillo parrot)))
\end{lstlisting}
%
Das \lstinline{signature} kennen wir von den Fallunterscheidungen aus
Abschnitt~\ref{page:signature} auf Seite~\pageref{page:signature}.
Das \lstinline{mixed}\indexvariable{mixed} ist neu und steht
für "<gemischte Daten">, also Fallunterscheidungen, bei denen jeder
Fall seine eigene Signatur hat. Du kannst die obige Definition lesen als
"<Tiere sind gemischt aus Gürteltieren und Papageien">; das klingt
aber auf Deutsch hölzern, weshalb wir für die Datendefinition bei der
Formulierung "<eins der folgenden"> bleiben. Mit der Definition steht
die Signatur \lstinline{animal} zur Verfügung. Wir haben schon mit der
Signatur für \lstinline{animal-weight} vorgegriffen. Hier ist sie noch
einmal zusammen mit einer Kurzbeschreibung:
%
\begin{lstlisting}
; Gewicht eines Tiers feststellen
(: animal-weight (animal -> natural))
\end{lstlisting}
%
Diese Funktion sollte entsprechend für Gürteltiere \emph{und}
Papageien funktionieren, wir brauchen also Testfälle für beide:
%
\begin{lstlisting}
(check-expect (animal-weight dillo1) 55000)
(check-expect (animal-weight dillo2) 58000)
(check-expect (animal-weight parrot1) 10000)
(check-expect (animal-weight parrot2) 5000)
\end{lstlisting}
%
Das Gerüst sieht so aus:
%
\begin{lstlisting}
(define animal-weight
(lambda (animal)
...))
\end{lstlisting}
%
Tiere bilden auch eine Fallunterscheidung in den Daten, mit zwei
Fällen: Gürteltiere und Papageien. Im Rumpf der Funktion brauchen wir
also eine Verzweigung mit zwei Zweigen:
%
\begin{lstlisting}
(define animal-weight
(lambda (animal)
(cond
(... ...)
(... ...))))
\end{lstlisting}
%
Wir brauchen als Nächstes zwei Bedingungen~-- eine, die Gürteltiere
und eine, die Papageien identifiziert. Dafür erweitern wir die
Record-Definitionen um ein neues Element, das \textit{Prädikat}\index{Prädikat}.
Die Prädikate werden uns erlauben, die Bedingungen zu
schreiben. Die Record-Definitionen sehen dann so aus:
%
\indexvariable{dillo}
\indexvariable{parrot}
\begin{lstlisting}
(define-record dillo
make-dillo
dillo? ; |\(\Longleftarrow\)| Prädikat
(dillo-weight natural)
(dillo-alive? boolean))
(define-record parrot
make-parrot
parrot? ; |\(\Longleftarrow\)| Prädikat
(parrot-weight natural)
(parrot-sentence string))
\end{lstlisting}
%
Es sind zwei neue Namen hinzugekommen, \lstinline{dillo?} und
\lstinline{parrot?}. Das sind die Prädikate, und sie haben folgenden
Signaturen:
%
\begin{lstlisting}
(: dillo? (any -> boolean))
(: parrot? (any -> boolean))
\end{lstlisting}
%
Das \lstinline{any} ist auch neu und ist die Signatur für einen
beliebigen Wert: Ein Prädikat kann also auf absolut alles angewendet
werden. Die beiden Prädikate unterscheiden Gürteltiere
beziehungsweise Papageien von anderen Werten:
%
\begin{lstlisting}
(dillo? dillo1)
|\evalsto| #t
(dillo? parrot1)
|\evalsto| #f
(parrot? dillo1)
|\evalsto| #f
(parrot? parrot1)
|\evalsto| #t
(dillo? 5)
|\evalsto| #f
(parrot? "foo")
|\evalsto| #f
\end{lstlisting}
%
Im allgemeinen ist ein Prädikat eine Funktion mit der Signatur
\lstinline{(any -> boolean)}, die für eine bestimmte Sorte von Werten
\lstinline{#t} liefert, für alles andere aber \lstinline{#f}. In
diesem Fall identifizieren die Prädikate \lstinline{dillo?}
und \lstinline{parrot?} jeweils die Werte, die zu einem bestimmten
Record-Typ gehören. Prädikate müssen nicht zu einem Record-Typ
gehören, gelegentlich werden wir welche selber schreiben.
Eine Reihe von Prädikaten sind vordefiniert~-- dazu mehr
auf Seite~\pageref{scheme:predicates}.
\begin{aufgabeinline}
Schreibe ein Prädikat \lstinline{animal?} für Tiere,
\lstinline{dillo}- oder
\lstinline{parrot}-Records.
\end{aufgabeinline}
%
\begin{feature}{\texttt{define-record} (mit Prädikaten)}{scheme:define-record-predicates}
Eine \lstinline{define-record}-Form\indexvariable{define-record}
hat folgende allgemeine Gestalt:
%
\begin{lstlisting}
(define-record |\(t\)|
|\(c\)|
|\(p\)|
(|\(\mathit{sel}\sb{1}\)| |\(\mathit{sig}\sb{1}\)|)
|\(\ldots\)|
(|\(\mathit{sel}\sb{n}\)| |\(\mathit{sig}\sb{n}\)|))
\end{lstlisting}
%
Diese Form definiert einen Record-Typ mit $n$ Feldern.
Dabei sind $t$, $c$, $\mathit{sel}_1 \ldots \mathit{sel}_n$ allesamt Variablen, für die
\lstinline{define-record} Definitionen anlegt:
%
\begin{itemize}
\item $t$ ist der Name der Record-Signatur.
\item $c$ ist der Name des Konstruktors mit
folgender Signatur:
%
\begin{lstlisting}
(: |\(c\)| (|\(\mathit{sig}\sb{1}\)| |\(\ldots\)| |\(\mathit{sig}\sb{n}\)| -> |\(t\)|))
\end{lstlisting}
\item $p$ ist der Name des Prädikats, der Records diesen Typs von
anderen Werten unterscheidet. Das Prädikat kann auch weggelassen
werden.
Das Prädikat hat folgende Signatur:
\begin{lstlisting}
(: |\(p\)| (any -> boolean))
\end{lstlisting}
\item $\mathit{Sel}_1, \ldots, \mathit{sel}_n$ sind die Namen der Selektoren für die Felder
des Record-Typs. Der Selektor $\mathit{sel}_i$ hat folgende Signatur:
%
\begin{lstlisting}
(: |\(\mathit{sel}\sb{i}\)| (|\(t\)| -> |\(\mathit{sig}\sb{i}\)|))
\end{lstlisting}
\end{itemize}
%
\end{feature}
%
Die Prädikate \lstinline{dillo?} und \lstinline{parrot?} können wir
in der Schablone benutzen:
%
\begin{lstlisting}
(define animal-weight
(lambda (animal)
(cond
((dillo? animal) ...)
((parrot? animal) ...))))
\end{lstlisting}
%
Im ersten Zweig~-- dem Zweig für Gürteltiere~-- kommt nun
\lstinline{dillo-weight} zum Einsatz, im zweiten Zweig~-- für
Papageien~-- ist \lstinline{parrot-weight} zuständig:
%
\indexvariable{animal-weight}
\begin{lstlisting}
(define animal-weight
(lambda (animal)
(cond
((dillo? animal) (dillo-weight animal))
((parrot? animal) (parrot-weight animal)))))
\end{lstlisting}
%
Abbildung~\ref{scheme:define-record-predicates} ist eine
aktualisierte Beschreibung der Form von
\lstinline{define-record}, bei der auch Prädikate
berücksichtigt sind
Aus diesem Beispiel ergibt sich eine Konstruktionsanleitung für
gemischte Daten. Zunächst die Datenanalyse:
\begin{konstruktionsanleitung}{Gemischte Daten: Datenanalyse}
\label{ka:gemischt-datenanalyse}
Gemischte Daten sind Fallunterscheidungen, bei denen jeder Fall eine
eigene Klasse von Daten mit eigener Signatur ist.
Schreibe bei gemischten Daten eine Signatur-Definition der folgenden Form unter die
Datendefinition:
%
\begin{lstlisting}
(define |\(\mathit{sig}\)|
(signature
(mixed |\(\mathit{sig}\sb{1}\)| |\(\mathit{sig}\sb{2}\)| |\(\ldots\)| |\(\mathit{sig}\sb{n}\)|)))
\end{lstlisting}
$\mathit{Sig}$ ist die Signatur für die neue Datensorte; $\mathit{sig}_1$ bis $\textit{sig}_n$
sind die Signaturen, aus denen die neue
Datensorte zusammengemischt ist.
\end{konstruktionsanleitung}
%
\noindent Die Konstruktionsanleitung für die Schablone ist der aus
der Konstruktionsanleitung~\ref{ka:fallunterscheidung-schablone} für
Fallunterscheidungen auf
Seite~\pageref{ka:fallunterscheidung-schablone} ähnlich:
%
\begin{konstruktionsanleitung}{Gemischte Daten als Eingabe:
Schablone}
\label{ka:gemischt-eingabe-schablone}
Eine Schablone für eine Funktion und deren Testfälle, die gemischte
Daten akzeptiert, kannst Du folgendermaßen konstruieren:
%
\begin{itemize}
\item Schreibe Tests für jeden der Fälle.
\item Schreibe eine \lstinline{cond}-Verzweigung als Rumpf in die
Schablone, die genau $n$ Zweige hat~-- also genau soviele Zweige,
wie es Fälle in der Datendefinition beziehungsweise der Signatur gibt.
\item Schreibe für jeden Zweig eine Bedingung, der den entsprechenden
Fall identifiziert.
\item Vervollständige die Zweige, indem Du eine Datenanalyse für
jeden einzelnen Fall vornimmst und entsprechende Hilfsfunktionen
und Konstruktionsanleitungen benutzt.
Die übersichtlichsten Programme entstehen meist, wenn für jeden Fall
separate Hilfsfunktionen definiert sind.\label{page:separate-mixed-procs}
\end{itemize}
%
\end{konstruktionsanleitung}
%
Eine Konstruktionsanleitung oder Schablone für gemischte Daten
\emph{als Ausgabe} ist unnötig~-- Du benutzt einfach die Schablone
des entsprechenden Falls.
Beachte den Unterschied zwischen \lstinline{enum} und
\lstinline{mixed}, die leicht zu verwechseln sind: \lstinline{enum} steht
für "<einer der folgenden \emph{Werte}">, während \lstinline{mixed} für
"<gehörend zu einer der folgenden \emph{Signaturen}"> steht.
\begin{aufgabeinline}
Denk Dir sinnvolle Datendefinitionen für Frühstück, Mittagessen und
Abendessen aus (ein \ldots{}essen besteht aus \ldots), so dass es
Sinn ergibt, für jede Sorte Essen die Frage "<Was für ein Gemüse ist
enthalten?"> zu stellen. Schreibe zunächst für jede Sorte Essen
eine Funktion, die das enthaltene Gemüse liefert. Schreibe dann
eine Datendefinition für den Oberbegriff "<Essen">; ein Essen kann
ein Frühstück, ein Mittagessen oder ein Abendessen sein. Schreibe
eine Funktion, welche das enthaltene Gemüse für ein Essen liefert.
\end{aufgabeinline}
\section{Gemischte Daten als Ausgabe}
\label{sec:feed-animal}
Wir können einen Papagei ähnlich wie ein Gürteltier füttern~-- nur die
Portion ist kleiner, wir nehmen 50~g an. Kurzbeschreibung und Signatur:\indexvariable{feed-parrot}
%
\begin{lstlisting}
; Papagei mit 50 g Futter füttern
(: feed-parrot (parrot -> parrot))
\end{lstlisting}
%
Testfälle:
%
\begin{lstlisting}
(check-expect (feed-parrot parrot1)
(make-parrot 10050 "Der Gärtner war's."))
(check-expect (feed-parrot parrot2)
(make-parrot 5050 "Ich liebe Dich."))
\end{lstlisting}
%
Gerüst:
%
\begin{lstlisting}
(define feed-parrot
(lambda (parrot)
...))
\end{lstlisting}
%
Die Schablone entsteht aus der Kombination der Schablonen für
zusammengesetzte Daten
als Eingabe (Konstruktionsanleitung~\ref{ka:zusammengesetzt-eingabe-schablone} auf
Seite \pageref{ka:zusammengesetzt-eingabe-schablone}) und als Ausgabe
(Konstruktionsanleitung~\ref{ka:zusammengesetzt-ausgabe-schablone} auf
Seite \pageref{ka:zusammengesetzt-ausgabe-schablone}):
%
\begin{lstlisting}
(define feed-parrot
(lambda (parrot)
(make-parrot ... ...)
... (parrot-weight parrot) ...
... (parrot-sentence parrot) ...))
\end{lstlisting}
%
\ldots{} und schließlich der vollständige Rumpf:
%
\begin{lstlisting}
(define feed-parrot
(lambda (parrot)
(make-parrot (+ (parrot-weight parrot) 50)
(parrot-sentence parrot))))
\end{lstlisting}
%
Nun können wir eine Funktion schreiben, die ein beliebiges Tier
akzeptiert und es füttert. Hier sind Kurzbeschreibung und Signatur:
%
\begin{lstlisting}
; Tier füttern
(: feed-animal (animal -> animal))
\end{lstlisting}
%
Die Funktion soll sich genauso verhalten wie \lstinline{feed-dillo}
beziehungsweise \lstinline{feed-parrot}. Das können wir direkt als
Tests ausdrücken:
%
\begin{lstlisting}
(check-expect (feed-animal parrot1) (feed-parrot parrot1))
(check-expect (feed-animal parrot2) (feed-parrot parrot2))
(check-expect (feed-animal dillo1) (feed-dillo dillo1))
(check-expect (feed-animal dillo2) (feed-dillo dillo2))
\end{lstlisting}
%
Das Gerüst sieht so aus:
%
\begin{lstlisting}
(define feed-animal
(lambda (animal)
...))
\end{lstlisting}
%
Da die Eingabe gemischt ist, brauchen wir eine Verzweigung mit soviel
Zweigen wie \lstinline{animal} Fälle hat, also zwei, die wir gleich
mit den passenden Bedingungen versehen:
%
\begin{lstlisting}
(define feed-animal
(lambda (animal)
(cond
((dillo? animal) ...)
((parrot? animal) ...))))
\end{lstlisting}
%
Schließlich müssen wir noch die Antworten ergänzen, für die wir die
Funktionen benutzen, die wir schon geschrieben haben:
%
\indexvariable{feed-animal}
\begin{lstlisting}
(define feed-animal
(lambda (animal)
(cond
((dillo? animal) (feed-dillo animal))
((parrot? animal) (feed-parrot animal)))))
\end{lstlisting}
%
Das Beispiel zeigt, dass wir keine spezielle Konstruktionsanleitung
für gemischte Daten als Ausgabe brauchen: Wir müssen nur darauf
achten, den jeweils richtigen Fall zu liefern.
%
\begin{aufgabeinline}
Schreibe eine Funktion \lstinline{run-over-animal}, die ein Tier
überfährt!
\end{aufgabeinline}
\section{Die Lebensmittelampel}
\mentioncode{gemischte-daten/zuckerampel.rkt}
%
In diesem Abschnitt nehmen wir uns noch ein weiteres Beispiel für
gemischte Daten vor, diesmal von vorneherein unter Benutzung der
Konstruktionsanleitung aus dem vorigen Abschnitt.
Die Lebensmittelampel ist eine Kennzeichnung auf Lebensmittelverpackungen,
die den Gehalt von gesundsheitsrelevanten Nährstoffen in den
Ampelfarben ausweist: Grün für niedrigen Gehalt, Gelb für mittleren Gehalt und
Rot für hohen Gehalt. Bei Zucker zum Beispiel sieht die Ampel so aus, bezogen
auf 100~g eines Lebensmittels:
%
\begin{center}
\begin{tabular}{l|l|l}
grün & niedriger Gehalt & weniger als 5 g\\
gelb & mittlerer Gehalt & zwischen 5 g und 22,5 g\\
rot & hoher Gehalt & mehr als 22,5 g
\end{tabular}
\end{center}
%
Trotz der Bemühungen der Europäischen Union sind die
Bezeichnungen uneinheitlich. Technisch gesehen ist die Ampel
redundant, wenn der Zuckergehalt in Gramm angegeben ist.
Manchmal ist allerdings der Zuckergehalt auch separat für Fruktose und
Glukose angegeben.
Ein Programm könnte aber den Umgang erleichtern, indem die
Angaben auf einer Lebensmittelpackung~-- Zucker in Gramm insgesamt,
Fruktose und Glukose separat sowie die Ampel~-- in die einheitliche
Ampel-Form bringt.
Schreiben wir also ein solches Programm. Zunächst
die Datenanalyse:
%
\begin{itemize}
\item Die Zuckermenge in Gramm ist eine (rationale) Zahl.
\item Zuckeranteile bestehen aus der Menge von Fruktose und Glukose.
\item Eine Zuckerampel ist rot, gelb oder grün.
\item Der Zuckergehalt kann entweder als Zuckermenge, Zuckeranteile
oder Zuckerampel angegeben werden.
\end{itemize}
%
Hier ist die Datendefinition für die zusammengesetzen Zuckeranteile:
%
\begin{lstlisting}
; Zuckeranteile bestehen aus:
; - Fruktose-Menge (in g)
; - Glukose-Menge (in g)
\end{lstlisting}
%
Daraus ergibt sich direkt die Record-Definition mit zwei Komponenten,
jeweils rationale Zahlen:
%
\indexvariable{sugars}
\begin{lstlisting}
(define-record sugars
make-sugars
sugars?
(sugars-fructose-g rational)
(sugars-glucose-g rational))
\end{lstlisting}
%
Hier einige Beispiele, zusammen mit Kommentaren, die die Beziehung
zwischen Daten und Information beschreiben:
%
\begin{lstlisting}
(define s1 (make-sugars 1 1)) ; 1 g Fruktose, 1 g Glukose
(define s2 (make-sugars 2 3)) ; 2 g Fruktose, 3 g Glukose
(define s3 (make-sugars 5 5)) ; 5 g Fruktose, 5 g Glukose
(define s4 (make-sugars 10 2.5)) ; 10 g Fruktose, 2.5 g Glukose
(define s5 (make-sugars 10 13)) ; 10 g Fruktose, 13 g Glukose
(define s6 (make-sugars 15 10)) ; 15 g Fruktose, 10 g Glukose
\end{lstlisting}
%
Bei der Ampel selbst handelt es sich um eine einfache
Fallunterscheidung:
%
\begin{lstlisting}
; Eine Ampel ist einer der folgenden Werte:
; - rot
; - gelb
; - grün
\end{lstlisting}
%
Das ist eine Aufzählung, wir geben entsprechend der dazu passenden
Signatur einen Namen:
%
\indexvariable{traffic-light}
\begin{lstlisting}
(define traffic-light
(signature
(enum "red" "yellow" "green")))
\end{lstlisting}
%
Die Angabe über den Zuckergehalt kann jede der drei oben genannten Formen
annehmen:
%
\begin{lstlisting}
; Ein Zuckergehalt ist eins der folgenden:
; - Gewicht in Gramm
; - Zuckeranteile
; - Ampelbezeichnung
\end{lstlisting}
%
Wieder ist an der Formulierung erkennbar, dass es sich um gemischte
Daten handelt, und zwar mit drei Fällen. Das übersetzen wir in ein
Signatur-Definition mit \lstinline{mixed} und ebenfalls drei Fällen:
%
\indexvariable{sugar-content}
\begin{lstlisting}
(define sugar-content
(signature
(mixed rational
sugars
traffic-light)))
\end{lstlisting}
%
Das Beispiel zeigt, dass die Fälle einer Definition für gemischte Daten
nicht allesamt Records sein müssen. Es ist allerdings wichtig, dass
die Fälle \emph{disjunkt} sind, also jeder Wert eindeutig einem der
Fälle zugeordnet werden kann: Sonst wäre es nicht möglich, eine sinnvolle
Verzweigung zu schreiben, welche die Fälle unterscheidet.
Nun zu unserer Funktion zur Ermittlung der Ampelbezeichnung für den
Zuckergehalt. Hier sind Kurzbeschreibung und Signatur:
%
\begin{lstlisting}
; Ampelbezeichnung für Zuckergehalt ermitteln
(: sugar-traffic-light (sugar-content -> traffic-light))
\end{lstlisting}
%
Wir brauchen ziemlich viele Testfälle, um alle Fälle von
Zuckergehalt abzudecken sowie die Eckfälle der Tabelle von oben.
%
\begin{lstlisting}
(check-expect (sugar-traffic-light 2) "green")
(check-expect (sugar-traffic-light 5) "yellow")
(check-expect (sugar-traffic-light 10) "yellow")
(check-expect (sugar-traffic-light 12.5) "yellow")
(check-expect (sugar-traffic-light 23) "red")
(check-expect (sugar-traffic-light s1) "green")
(check-expect (sugar-traffic-light s2) "yellow")
(check-expect (sugar-traffic-light s3) "yellow")
(check-expect (sugar-traffic-light s4) "yellow")
(check-expect (sugar-traffic-light s5) "red")
(check-expect (sugar-traffic-light s6) "red")
(check-expect (sugar-traffic-light "green") "green")
(check-expect (sugar-traffic-light "yellow") "yellow")
(check-expect (sugar-traffic-light "red") "red")
\end{lstlisting}
%
Als Nächstes ist, wie immer, das Gerüst dran:
%
\begin{lstlisting}
(define sugar-traffic-light
(lambda (sugar-content)
...))
\end{lstlisting}
%
Als Nächstes wenden wir die Schablone für Funktionen an, die gemischte
Daten akzeptieren. Wir brauchen eine Verzweigung mit sovielen Zweigen
wie \lstinline{sugar-content} Fälle hat, also drei:
%
\begin{lstlisting}
(define sugar-traffic-light
(lambda (sugar-content)
(cond
(... ...)
(... ...)
(... ...))))
\end{lstlisting}
%
Als Nächstes brauchen wir Tests für die drei Fälle. Für den zweiten
Fall ist das einfach, da es sich um \lstinline{sugars}-Records handelt:
da gibt es das Prädikat \lstinline{sugars?}. Beim ersten Fall handelt es
sich aber um eine rationale Zahl, beim dritten um eine Zeichenkette~--
beides eingebaute Datensorten. Für diese gibt es die eingebauten
Prädikate \lstinline{rational?} und \lstinline{string?}~--
Abbildung~\ref{scheme:predicates} zählt noch mehr eingebaute
Prädikate auf.
%
\begin{feature}{Eingebaute Prädikate}{scheme:predicates}
\index{Prädikate!eingebaut}\index{eingebaute Prädikate}
Folgende Prädikate sind eingebaut:
\begin{itemize}
\item \lstinline{number?}\indexvariable{number?} testet, ob ein Wert eine Zahl ist.
\item \lstinline{real?}\indexvariable{real?} testet, ob ein Wert eine reelle Zahl ist.
\item \lstinline{rational?}\indexvariable{rational?} testet, ob ein Wert eine rationale Zahl ist.
\item \lstinline{natural?}\indexvariable{natural?} testet, ob ein Wert eine natürliche Zahl ist.
\item \lstinline{string?}\indexvariable{string?} testet, ob ein Wert eine Zeichenkette ist.
\item \lstinline{boolean?}\indexvariable{boolean?} testet, ob ein Wert ein boolescher Wert ist.
\end{itemize}
\end{feature}
%
Mit dieser Information gewappnet können wir die Tests ergänzen:
%
\begin{lstlisting}
(define sugar-traffic-light
(lambda (sugar-content)
(cond
((rational? sugar-content) ...)
((sugars? sugar-content) ...)
((string? sugar-content) ...))))
\end{lstlisting}
%
Im ersten Zweig handelt es sich nicht nur um eine rationale Zahl,
sondern auch um eine Fall"-unterscheidung mit drei Fällen entsprechend
der Tabelle vom Anfang:
%
\begin{lstlisting}
(define sugar-traffic-light
(lambda (sugar-content)
(cond
((rational? sugar-content)
(cond
(... ...)
(... ...)
(... ...)))
((sugars? sugar-content) ...)
((string? sugar-content) ...))))
\end{lstlisting}
%
Als Nächstes ergänzen wir Tests entsprechend der Tabelle:
%
\begin{lstlisting}
(define sugar-traffic-light
(lambda (sugar-content)
(cond
((rational? sugar-content)
(cond
((< sugar-content 5) ...)
((and (>= sugar-content 5) (<= sugar-content 12.5)) ...)
((> sugar-content 12.5) ...)))
((sugars? sugar-content) ...)
((string? sugar-content) ...))))
\end{lstlisting}
%
Schließlich müssen wir noch die Antworten eintragen:
%
\indexvariable{sugar-traffic-light}
\begin{lstlisting}
(define sugar-traffic-light
(lambda (sugar-content)
(cond
((rational? sugar-content)
(cond
((< sugar-content 5) "green")
((and (>= sugar-content 5) (<= sugar-content 12.5)) "yellow")
((> sugar-content 12.5) "red")))
((sugars? sugar-content) ...)
((string? sugar-content) ...))))
\end{lstlisting}
%
Das verschachtelte \lstinline{cond} ist etwas unübersichtlich: In
Konstruktionsanleitung~\ref{ka:gemischt-eingabe-schablone} auf
Seite~\pageref{page:separate-mixed-procs} ist bereits vermerkt, dass
es sinnvoll ist, diesen Zweig in eine separate Hilfsfunktion
auszulagern. Hier sind Kurzbeschreibung und Signatur für diese
Hilfsfunktion:
%
\begin{lstlisting}
; Zuckeranteil in g in Ampel umwandeln
(: sugar-weight->traffic-light (rational -> traffic-light))
\end{lstlisting}
%
Die Testfälle lassen sich aus den Testfällen für
\lstinline{sugar-traffic-light} durch einfaches Kopieren und Umbenennen
gewinnen:
%
\begin{lstlisting}
(check-expect (sugar-weight->traffic-light 2) "green")
(check-expect (sugar-weight->traffic-light 5) "yellow")
(check-expect (sugar-weight->traffic-light 10) "yellow")
(check-expect (sugar-weight->traffic-light 12.5) "yellow")
(check-expect (sugar-weight->traffic-light 23) "red")
\end{lstlisting}
%
Gerüst:
%
\begin{lstlisting}
(define sugar-weight->traffic-light
(lambda (sugar-weight)
...))
\end{lstlisting}
%
Den Rumpf haben wir ja schon geschrieben, wir müssen ihn nur noch
hierher bewegen und dann \lstinline{sugar-content} in
\lstinline{sugar-weight} umbenennen.
Du kannst dazu in DrRacket
nach einem Klick auf "<Syntaxprüfung"> mit einem
Rechtsklick auf den Parameter \lstinline{sugar-content} ein Menü aufklappen, das unter
anderem die Auswahl "<\lstinline{sugar-content} umbenennen"> anbietet. \drscheme{} sorgt dann
dafür, dass alle zugehörigen Vorkommen von \lstinline{sugar-content} in gleicher Weise
umbenannt werden.\label{def:sugar-weight-traffic-light}
%
\indexvariable{sugar-weight->traffic-light}
\begin{lstlisting}
(define sugar-weight->traffic-light
(lambda (sugar-weight)
(cond
((< sugar-weight 5) "green")
((and (>= sugar-weight 5) (<= sugar-weight 22.5)) "yellow")
((> sugar-weight 12.5) "red"))))
\end{lstlisting}
%
Zurück zur Funktion \lstinline{sugar-traffic-light}. Dort benutzen wir die
neu definierte Hilfsfunktion:
%
\begin{lstlisting}
(define sugar-traffic-light
(lambda (sugar-content)
(cond
((rational? sugar-content)
(sugar-weight->traffic-light sugar-content))
((sugars? sugar-content) ...)
((string? sugar-content) ...))))
\end{lstlisting}
%
Beim nächsten Zweig geht es um den Fall \lstinline{sugars}.
Das sind zusammengesetzte Daten, wir schreiben in den Zweig dafür also
die Schablone mit den Aufrufen der Selektoren:
%
\begin{lstlisting}
(define sugar-traffic-light
(lambda (sugar-content)
(cond
((rational? sugar-content)
(sugar-weight->traffic-light sugar-content))
((sugars? sugar-content)
... (sugars-fructose-g sugar-content) ...
... (sugars-glucose-g sugar-content) ...)
((string? sugar-content) ...))))
\end{lstlisting}
%
Wir müssen Fruktose- und Glukose-Anteil addieren und die Summe
entsprechend der Tabelle vom Anfang in eine Ampelfarbe
umwandeln. Das kann wieder
\lstinline{sugar-weight->traffic-light}
erledigen:
%
\begin{lstlisting}
(define sugar-traffic-light
(lambda (sugar-content)
(cond
((rational? sugar-content)
(sugar-weight->traffic-light sugar-content))
((sugars? sugar-content)
(sugar-weight->traffic-light
(+ (sugars-fructose-g sugar-content)
(sugars-glucose-g sugar-content))))
((string? sugar-content) ...))))
\end{lstlisting}
%
Bleibt der letzte Fall~-- der ist zum Glück trivial, da es sich schon
um eine Farbe handelt, die muss \lstinline{sugar-traffic-light} nur
zurückgeben:
%
\indexvariable{sugar-traffic-light}
\begin{lstlisting}
(define sugar-traffic-light
(lambda (sugar-content)
(cond
((rational? sugar-content)
(sugar-weight->traffic-light sugar-content))
((sugars? sugar-content)
(sugar-weight->traffic-light
(+ (sugars-fructose-g sugar-content)
(sugars-glucose-g sugar-content))))
((string? sugar-content) sugar-content))))
\end{lstlisting}
%
Fertig!
\section{Fehler repräsentieren}
\label{sec:slope}
\label{sec:maybe}
\mentioncode{gemischte-daten/slope.rkt}
%
Dieser Abschnitt beschreibt eine weitere häufig vorkommende Anwendung
für gemischte Daten. Es geht um Situationen, wo eine Funktion
keine sinnvolle Antwort weiß.
Als Beispiel schreiben wir eine Funktion, welche die Steigung einer
Geraden auf der Ebene berechnet anhand der Koordinaten zweier Punkte
darauf. Die Funktion akzeptiert vier Argumente: Die X- und die Y-Koordinate des
ersten Punkts und danach die X- und die Y-Koordinate des zweiten
Punkts. Wir fangen mit Kurzbeschreibung, Signatur, einigen Tests
und einem Gerüst an:
%
\begin{lstlisting}
; Steigung einer Gerade berechnen
(: slope (real real real real -> real))
(check-expect (slope 0 0 1 1) 1)
(check-expect (slope 0 0 2 1) 1/2)
(check-expect (slope 1 2 3 5) 3/2)
(define slope
(lambda (x1 y1 x2 y2)
...))
\end{lstlisting}
%
Die Formelsammlung sagt, dass wir zur Berechnung der Steigung die
Differenz der Y-Koordinaten durch die Differenz der X-Koordinaten
teilen müssen:
%
\begin{lstlisting}
(define slope
(lambda (x1 y1 x2 y2)
(/ (- y2 y1)
(- x2 x1))))
\end{lstlisting}
%
Fertig, könnte man meinen. Es gibt aber Linien, die keine Steigung
haben In der REPL sieht das so aus:
%
\begin{alltt}
> (slope 0 0 0 1)
{\color{red}/: durch 0 geteilt}
\end{alltt}
%
Die Meldung ist ein bisschen unfreundlich und birgt mehrere Probleme:
%
\begin{itemize}
\item Die Meldung besagt zwar, was das Programm gemacht hat (durch $0$
geteilt), aber benennt nicht die Ursache des Problems: dass die
Gerade keine Steigung hat.
\item Das Programm bricht einfach ab, ohne dass es eine Möglichkeit
bekommt, mit dem Problem umzugehen.
\item Dass das Programm einfach abbrechen kann, ist aus der Signatur
nicht ersichtlich, die suggeriert, dass die Funktion immer eine
reelle Zahl zurückliefert.
\end{itemize}
%
Alle drei Probleme können wir auf einmal mit gemischten Daten lösen.
Dazu müssen wir zunächst dafür sorgen, dass die Funktion einen
sinnvollen Wert zurückliefert, bevor sie durch $0$ teilen würde.
Dadurch werden die Eingaben in zwei Klassen aufgeteilt~-- es gibt eine
Steigung oder eben nicht. Wir benutzen binäre Verzweigung,
um diese beiden Klassen zu unterscheiden:
%
\begin{lstlisting}
(define slope
(lambda (x1 y1 x2 y2)
(if (= (- x2 x1) 0)
...
(/ (- y2 y1)
(- x2 x1)))))
\end{lstlisting}
%
Nur müssen wir anstelle der Ellipse irgendetwas ins Programm
schreiben. Wir können nicht einfach irgendeine reelle Zahl nehmen, da
diese ja auch eine legitime Steigung sein könnte. Stattdessen
brauchen wir einen Wert, den wir von den legitimen Zahlen
unterscheiden können.
Wir könnten eine spezielle Zeichenkette oder
\lstinline{#f} benutzen. (Siehe dazu Aufgabe~\ref{aufgabe:no-slope}
auf Seite~\pageref{aufgabe:no-slope}.)
Wir empfehlen aber, stattdessen einen eigenen Record-Typ zu
definieren. Dieser Record-Typ hat keine Felder, das heißt, er kann nur benutzt
werden, um einen einzigen Wert herzustellen, nämlich
\lstinline{(make-no-slope)}:
%
\indexvariable{no-slope}
\begin{lstlisting}
; Es gibt keine Steigung
(define-record no-slope
make-no-slope
no-slope?)
\end{lstlisting}
%
(Es handelt sich also um eine
degenerierte Version von zusammengesetzten Daten ohne Bestandteile.)
Den Wert von \lstinline{(make-no-slope)} können wir aber durch das
Prädikat \lstinline{no-slope?}
von allen anderen Werten unterscheiden und benutzen ihn, wenn es keine
Steigung gibt:
%
\indexvariable{slope}
\begin{lstlisting}
(define slope
(lambda (x1 y1 x2 y2)
(if (= (- x2 x1) 0)
(make-no-slope)
(/ (- y2 y1)
(- x2 x1)))))
\end{lstlisting}
%
Jetzt können wir einen Testfall definieren: