forked from deinprogramm/schreibe-dein-programm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
i1verzw.tex
1945 lines (1839 loc) · 69.3 KB
/
i1verzw.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 lizensiert unter der Creative-Commons-Lizenz
% "Namensnennung - Weitergabe unter gleichen Bedingungen 4.0 International (CC BY-SA 4.https)"
% 0://creativecommons.org/licenses/by-sa/4.0/deed.de
\chapter{Fallunterscheidungen und Verzweigungen}
\label{cha:conditionals}
Computerprogramme müssen bei manchen Daten, die sie
verarbeiten, zwischen verschiedenen Möglichkeiten differenzieren: Ist
die Wassertemperatur warm genug zum Baden? Welche von fünf
Tupperschüsseln ist für eine bestimmte Menge Kartoffelsalat groß
genug? Welches ist die richtige Abzweigung nach Dortmund? Solche
Entscheidungen sind daran festgemacht, dass ein Wert zu einer von mehreren
verschiedenen
Kategorien gehören kann~-- es handelt sich dann um eine sogenannte
\textit{Fallunterscheidung\index{Fallunterscheidung}};
Funktionen operieren auf Daten mit
Fallunterscheidung durch \textit{Verzweigungen\index{Verzweigung}}.
In diesem Kapitel werden wir zunächst zwei besonders einfache Arten
von Fallunterscheidungen behandeln, nämlich \textit{Aufzählungen} und
\textit{Zahlenbereiche}. In Kapitel~\ref{cha:gemischte-daten} werden
wir die Fallunterscheidungen noch einmal aufgreifen und mit
sogenannten \textit{gemischten Daten} programmieren.
\section{Rechnen mit booleschen Werten}
Für die Programme dieses Kapitels benötigen wir eine neue Art von
Daten. Die ergeben sich, wenn wir zum Beispiel zwei Zahlen
vergleichen:
%
\begin{lstlisting}
(< 0 5)
|\evalsto| #t
\end{lstlisting}
%
\lstinline{(< 0 5)} ist die Schreibweise für $0 < 5$. Das
\lstinline{#t} steht für "<true\index{true}"> oder "<wahr\index{wahr}">,
denn die Aussage "<ist $0$ kleiner als $5$"> stimmt ja.
Umgekehrt kommt natürlich nicht \lstinline{#t} heraus:
%
\begin{lstlisting}
(< 5 0)
|\evalsto| #f
\end{lstlisting}
%
Das \lstinline{#f} steht für "<false\index{false}"> oder
"<falsch\index{falsch}">, denn diese Aussage stimmt nicht.
"<Wahr"> und "<falsch"> heißen zusammen \textit{boolesche
Werte\index{boolescher Wert}} oder auch
\textit{Wahrheitswerte\index{Wahrheitswert}}.\footnote{Die booleschen
Werte sind benannt nach \textit{George Boole} (1815--1864), der als
erster einen algebraischen Ansatz für die Behandlung von Logik mit
den Werten "<wahr"> und "<falsch"> formulierte.} Ein Ausdruck, bei
dem ein boolescher Wert herauskommt, heißt dementsprechend auch
\textit{boolescher Ausdruck\index{boolescher Ausdruck}}.
Wir werden boolesche Ausdrücke oft
\textit{Bedingungen\index{Bedingung}} nennen. Wenn eine Bedingung
\lstinline{#t} liefert, werden wir auch die Sprachregelung benutzen, dass
die Bedingung \textit{gilt}~-- beziehungsweise, dass, wenn sie
\lstinline{#f} liefert, sie \textit{nicht gilt}.
\lstinline{<}\index{<@\texttt{<}} ist eine eingebaute Funktion, die
auf "<kleiner als"> testet, also dem mathematischen Operator $<$
entspricht. Ebenso gibt es auch \lstinline{>}\index{>@\texttt{>}} für
"<größer als"> (Mathematik: $>$), \lstinline{=}\index{=@\texttt{=}} für
"<gleich"> (Mathematik: $=$), \lstinline{<=}\index{<@\texttt{<=}} für "<kleiner oder
gleich"> (Mathematik: $\leq$) und \lstinline{>=}\index{>=@\texttt{>=}}
für "<größer oder gleich"> (Mathematik: $\geq$).
Analog zu \lstinline{=} für Zahlen können Zeichenketten mit
\lstinline{string=?}\index{string=?@\texttt{string=?}} verglichen werden:
\begin{lstlisting}
(string=? "Mike" "Mike")
|\evalsto| #t
(string=? "Herbert" "Mike")
|\evalsto| #f
\end{lstlisting}
%
\lstinline{#t} und \lstinline{#f} sind Literale, wie Zahlen. Sie können also
auch in Programmen stehen:
%
\begin{lstlisting}
#t
|\evalsto| #t
#f
|\evalsto| #f
\end{lstlisting}
%
Programme können mit booleschen Werten auch rechnen. Ein Ausdruck der
Form\index{and@\texttt{and}}
%
\begin{lstlisting}
(and |\(e\sb{1}\)| |\(e\sb{2}\)| |\(\ldots\)| |\(e\sb{n}\)|)
\end{lstlisting}
%
ergibt immer dann \lstinline{#t}, wenn alle $e_i$ \lstinline{#t} ergeben, sonst
\lstinline{#f}. Bei zwei Operanden $e_1$ und $e_2$ ergibt
\lstinline{(and $e_1$ $e_2$)} immer dann \lstinline{#t}, wenn $e_1$ \emph{und} $e_2$
\lstinline{#t} ergeben:\label{page:and}
%
\begin{lstlisting}
(and #t #t)
|\evalsto| #t
(and #f #t)
|\evalsto| #f
(and #t #f)
|\evalsto| #f
(and #f #f)
|\evalsto| #f
\end{lstlisting}
%
Entsprechend gibt es Ausdrücke der Form\index{or@\texttt{or}}
%
\begin{lstlisting}
(or |\(e\sb{1}\)| |\(e\sb{2}\)| |\(\ldots\)| |\(e\sb{n}\)|)
\end{lstlisting}
%
die immer dann \lstinline{#t} ergeben, wenn \emph{einer} der $e_i$ \lstinline{#t} ergibt, sonst
\lstinline{#f}. Bei zwei Operanden $e_1$ und $e_2$ ergibt
\lstinline{(or $e_1$ $e_2$)} immer dann \lstinline{#t}, wenn $e_1$ \emph{oder} $e_2$
\lstinline{#t} ergeben:
%
\begin{lstlisting}
(or #t #t)
|\evalsto| #t
(or #f #t)
|\evalsto| #t
(or #t #f)
|\evalsto| #t
(or #f #f)
|\evalsto| #f
\end{lstlisting}
%
Das \lstinline{or} berechnet also ein \textit{inklusives}
Oder\index{inklusives Oder}: \lstinline{(or $e_1$ $e_2$)} bedeutet "<$e_1$ oder
$e_2$ oder beides">, im Gegensatz zum \textit{exklusiven}
Oder\index{exklusives Oder} "<entweder $e_1$ oder
$e_2$">.\footnote{In der Informatik ist mit "<oder"> ohne Zusatz fast
immer das inklusive Oder gemeint. Wer das exklusive Oder meint, sagt
auch "<exklusives Oder">.}
Des Weiteren gibt es noch eine eingebaute Funktion
\lstinline{not}\index{not@\texttt{not}}, die einen booleschen Wert
umdreht beziehungsweise dessen \textit{Negation}\index{Negation}
berechnet, sich also folgendermaßen verhält:
%
\begin{lstlisting}
(not #f)
|\evalsto| #t
(not #t)
|\evalsto| #f
\end{lstlisting}
%
Für boolesche Werte gibt es die Signatur
\lstinline{boolean}\index{boolean@\texttt{boolean}}.
\begin{aufgabeinline}
Schreibe eine Funktion, die von zwei booleschen Werten das exklusive
Oder berechnet.
\end{aufgabeinline}
\section{Verzweigungen}
Um Fallunterscheidungen zu demonstrieren, nehmen wir uns folgende
Beispielaufgabe vor: Wir schreiben eine Funktion, die eine Zahl (auf
Englisch) "<aufsagen"> soll. Sie soll sich so verhalten:
%
\begin{lstlisting}
(say-number 0)
|\evalsto| "zero"
(say-number 1)
|\evalsto| "one"
\end{lstlisting}
%
Der Einfachheit halber beschränken wir uns vorläufig auf die Zahlen
von Null bis Drei. Die Funktion hat folgende Kurzbeschreibung:
%
\begin{lstlisting}
; Zahl zu Text machen
\end{lstlisting}
%
Die Funktion macht aus einer natürlichen Zahl eine Zeichenkette und
hat folgende Signatur:\index{say-number@\texttt{say-number}}
%
\begin{lstlisting}
(: say-number (natural -> string))
\end{lstlisting}
%
Die Signatur sagt leider nichts darüber aus, dass die Funktion nur bis
Drei funktioniert~-- später werden wir noch beschreiben, wie die
Signatur präziser werden kann. Hier wollen wir uns jedoch erst
einmal auf die Funktionsdefinition konzentrieren. Vorher machen wir
aber die obigen Beispiele zu Testfällen:
%
\begin{lstlisting}
(check-expect (say-number 0) "zero")
(check-expect (say-number 1) "one")
\end{lstlisting}
%
Das Gerüst ergibt sich direkt aus der Signatur:
%
\begin{lstlisting}
(define say-number
(lambda (n)
|\ldots|))
\end{lstlisting}
%
Aber wie kommen wir jetzt weiter? Die Eingabe zerfällt ja in vier
Fälle~-- 0, 1, 2 und 3~-- sie bildet somit eine
\textit{Fallunterscheidung\index{Fallunterscheidung}}. Um eine
Fallunterscheidung in der Eingabe einer Funktion verarbeiten zu können,
benötigen wir ein neues Programmelement, die
\textit{Verzweigung\index{Verzweigung}}. Verzweigungen beginnen mit
dem Wort \lstinline{cond} und gehören zu den kompliziertesten
Programmelementen, die wir in diesem Buch benutzen. Aber keine Sorge,
so schlimm wird es nicht. Um eine Verzweigung zu schreiben, müssen
wir wissen, \emph{wie viele} Fälle es gibt. In unserer Aufgabe sind das
vier. Die Verzweigung dafür hat folgende Form:
%
\begin{lstlisting}
(define say-number
(lambda (n)
(cond
(|\ldots| |\ldots|)
(|\ldots| |\ldots|)
(|\ldots| |\ldots|)
(|\ldots| |\ldots|))))
\end{lstlisting}
%
Jedes der Programmstücke \lstinline{(... ...)} ist ein sogenannter
\textit{Zweig\index{Zweig}}. Der erste Teil eines Zweigs ist immer
eine Bedingung, der für den entsprechenden Fall \lstinline{#t} liefern
sollte und für die anderen Fälle \lstinline{#f}. In diesem Fall muss die
Bedingung jedes Zweiges jeweils eine der Zahlen von Null bis Drei
identifizieren:
%
\begin{lstlisting}
(define say-number
(lambda (n)
(cond
((= n 0) |\ldots|)
((= n 1) |\ldots|)
((= n 2) |\ldots|)
((= n 3) |\ldots|))))
\end{lstlisting}
%
Der jeweils zweite Teil des Zweiges ist das gewünschte Ergebnis für
den entsprechenden Fall. Wenn wir das ausfüllen, sieht das Resultat
so aus:
%
\begin{lstlisting}
(define say-number
(lambda (n)
(cond
((= n 0) "zero")
((= n 1) "one")
((= n 2) "two")
((= n 3) "three"))))
\end{lstlisting}
%
Fertig!
\begin{aufgabeinline}
Finde heraus was passiert, wenn Du die Funktion mit einer Zahl
aufrufst, für die sie nicht gemacht ist.
\end{aufgabeinline}
%
Wie oben schon erwähnt, suggeriert die Signatur von
\lstinline{say-number}, dass sie eine beliebige natürliche Zahl
aufsagen kann, auch wenn sie nur bis Drei funktioniert.
Glücklicherweise können wir die Signatur präzisieren, und zwar so:
%
\begin{lstlisting}
(: say-number ((integer-from-to 0 3) -> string))
\end{lstlisting}
%
Die Funktion
\lstinline{integer-from-to}\index{integer-from-to@\texttt{integer-from-to}}
liefert also eine Signatur, die natürliche Zahlen zwischen einer Unter-
und einer Obergrenze beschreibt.\label{function:integer-from-to}
\section{Abdeckung}
\label{sec:testabdeckung}
\index{Abdeckung}
\begin{figure}[tb]
\centering
\includegraphics[width=0.7\textwidth]{i1verzw/coverage}
\caption{Anzeige von Abdeckung in DrRacket}
\label{fig:coverage}
\end{figure}
Vielleicht ist Dir bei der \lstinline{say-number}-Funktion aus dem
vorigen Abschnitt aufgefallen, dass bei dem Programm unmittelbar, nachdem
es gelaufen ist, zwei Zeilen farbig
hinterlegt sind, wie in Abbildung~\ref{fig:coverage} zu sehen. Das liegt
daran, dass diese beiden Zeilen nie ausgewertet wurden, weil die
Tests nur die ersten beiden Fälle überprüfen. Theoretisch
könnte in den beiden letzten Zweigen für $2$ und $3$ noch ein Fehler
stecken, und die Tests würden es nicht bemerken.
Wenn Du folgende Testfälle hinzufügst, geht die farbige Hinterlegung weg:
%
\begin{lstlisting}
(check-expect (say-number 2) "two")
(check-expect (say-number 3) "three")
\end{lstlisting}
%
Probier es aus~-- am besten erst nur den einen, dann den anderen!
Die farbige Hinterlegung zeigt die \textit{Abdeckung} an, wie wir es
schon in Abschnitt~\ref{page:abdeckung0} auf
Seite~\pageref{page:abdeckung0} kurz diskutiert haben: Ein Ausdruck im
Programm, der durch die Tests ausgewertet wird, heißt
\textit{abgedeckt}. Die farbig hinterlegten Ausdrücke sind also nicht
abgedeckt. Der letzte Satz von Konstruktionsanleitung~\ref{ka:tests}
auf Seite~\pageref{ka:tests} sagt genau das: Eine Funktion sollte
durch ihre Tests vollständig abgedeckt werden. Die Abdeckung ist also
eine Minimalanforderung an unsere Tests.
\section{Aufzählungen}
Nächste Beispielaufgabe: Wir wollen Haustiere einteilen in niedliche
und nicht so niedliche. Es gibt in der Welt dieser Aufgabe nur drei
verschiedene Haustiere: Katzen, Hunde und Schlangen. Haustiere
könnten wir in einem Kommentar im Programm so beschreiben:
%
\label{sec:datendefinition}
\begin{lstlisting}
; Ein Haustier ist eins der folgenden:
; - Katze
; - Hund
; - Schlange
\end{lstlisting}
%
Solch ein kurzer Kommentar, der die Daten beschreibt, die in unserer
Aufgabe vorkommen, heißt
\textit{Datendefinition\index{Datendefinition}}. Mehr dazu in
Abschnitt~\ref{sec:datenanalyse}. Die Formulierung der
Datendefinition "<eins der folgenden"> deutet daraufhin, dass auch
diese Sorte Daten in mehrere Fälle zerfällt~-- es handelt sich also
wieder um eine Fallunterscheidung.
Bevor wir die eigentliche Aufgabe lösen (niedlich oder nicht), müssen
wir uns zunächst überlegen, wie wir Haustiere im Programm
repräsentieren: Für die Zwecke dieser Aufgabe reicht es zu wissen, mit
\emph{welcher} der drei Möglichkeiten wir es zu tun haben. Wir
benutzen die einfachste Möglichkeit, Zeichenketten mit dem
entsprechenden Text, also \lstinline{"Katze"}, \lstinline{"Hund"} und
\lstinline{"Schlange"}.
Eine solche Auflistung von Werten bildet eine sogenannte
\textit{Aufzählung\index{Aufzählung}}~-- ein Spezialfall einer
Fallunterscheidung.
Für die Aufzählung der Haustiere gibt es noch keine fertige Signatur,
die müssen wir noch definieren. Die
\textit{Signatur-Definition\index{Signatur-Definition}} sieht so
aus:\index{pet@\texttt{pet}}
\label{sec:pet}\label{page:signature}
%
\begin{lstlisting}
(define pet
(signature (enum "Katze" "Hund" "Schlange")))
\end{lstlisting}
%
Da sind gleich zwei neue Programmelemente drin:
%
\begin{itemize}
\item Wir schreiben
\lstinline{signature}\index{signature@\texttt{signature}} immer, wenn wir eine
neue Signatur erzeugen.
\item \lstinline{enum}\index{enum@\texttt{enum}} (das funktioniert
nur innerhalb eines \lstinline{signature}-Ausdrucks) ist für
Aufzählungen zuständig. In einem \lstinline{enum}-Ausdruck stehen
die Werte, die zur Aufzählung gehören.
\end{itemize}
%
\begin{aufgabeinline}
Die Funktion \lstinline{say-number} aus dem vorigen Abschnitt hat ja als
Signatur-Deklaration die folgende:
\begin{lstlisting}
(: say-number ((integer-from-to 0 3) -> string))
\end{lstlisting}
%
Schreibe explizite Datendefinitionen für Ein- und Ausgabe der Funktion
und mache ihre Signatur mit Hilfe von Aufzählungen präziser!
\end{aufgabeinline}
%
Zurück zu unserer Funktion zur Niedlichkeitsanalyse. Die definierte
Signatur \lstinline{pet} können wir nun in der Signatur-Deklaration
unserer Funktion benutzen. Zusammen mit der Kurzbeschreibung sieht
das so aus:
%
\begin{lstlisting}
; Ist Haustier niedlich?
(: cute? (pet -> boolean))
\end{lstlisting}
%
Das Fragezeichen gehört zum Namen der Funktion und ist eine
Konvention, die wir für die Namen von Funktionen verwenden, die einen
booleschen Wert zurückgeben~-- also solche Funktionen, die eine
Ja-/Nein-Frage beantworten.
Da es nur drei Möglichkeiten für die Eingabe gibt, können wir für alle
drei jeweils einen Test schreiben:
%
\begin{lstlisting}
(check-expect (cute? "Katze") #t)
(check-expect (cute? "Hund") #t)
(check-expect (cute? "Schlange") #f)
\end{lstlisting}
%
Kommen wir zur Funktion selbst. Zunächst einmal das Gerüst:
%
\begin{lstlisting}
(define cute?
(lambda (p)
|\ldots|))
\end{lstlisting}
%
Da es sich bei der Eingabe um eine Aufzählung, also eine
Fallunterscheidung handel, brauchen wir eine Verzweigung im Rumpf. Da
es drei Fälle in der Aufzählung gibt, braucht die Verzweigung
ebenfalls drei Zweige:
%
\begin{lstlisting}
(define cute?
(lambda (p)
(cond
(|\ldots| |\ldots|)
(|\ldots| |\ldots|)
(|\ldots| |\ldots|))))
\end{lstlisting}
%
Als Nächstes müssen wir die Bedingungen schreiben, und die sollten
\lstinline{p} jeweils mit \lstinline{"Katze"}, \lstinline{"Hund"} und
\lstinline{"Schlange"} vergleichen. Dabei könnte man leicht auf die Idee
kommen, \lstinline{(= p "Katze")} etc.\ zu schreiben. Dann kommt
allerdings eine Fehlermeldung etwa so:
%
\begin{lstlisting}
=: Zahl als erstes Argument erwartet, "Katze" bekommen
\end{lstlisting}
%
Die Funktion \lstinline{=} fühlt sich also nur für Zahlen zuständig, für
Zeichenketten müssen wir die Funktion
\lstinline{string=?}\index{string=?@\texttt{string=?}} verwenden:
%
\begin{lstlisting}
(define cute?
(lambda (p)
(cond
((string=? p "Katze") |\ldots|)
((string=? p "Hund") |\ldots|)
((string=? p "Schlange") |\ldots|))))
\end{lstlisting}
%
Bisher ergibt sich alles rein aus der Definition von \lstinline{pet}.
Schließlich müssen wir noch die Antworten ergänzen. Katzen und Hunde
sind niedlich, Schlangen nicht:
%
\begin{lstlisting}
(define cute?
(lambda (p)
(cond
((string=? p "Katze") #t)
((string=? p "Hund") #t)
((string=? p "Schlange") #f))))
\end{lstlisting}
%
Fertig!
\section{Zahlenbereiche}
\label{sec:zahlenbereiche}
\label{sec:heat-water}
Eine weitere häufig vorkommende Art der Fallunterscheidung gibt es bei
Zahlenbereichen.
Um das zu demonstrieren, nehmen wir uns folgende Aufgabe vor: Wir
schreiben eine Funktion, die "<Wasser erhitzt">.
Das heißt, wir fügen einer bestimmten Menge Wasser bei einer
bestimmten Temperatur eine bestimmte Menge Wärme zu. Wir wollen
berechnen, wie warm das Wasser danach ist. Der Einfachheit halber
messen wir die zugefügte Wärme~- genau wie die Temperatur~-- in Grad
Celsius (\si{\degree}C).\footnote{Wir vereinfachen schon sehr stark, aber
Temperaturerhöhung und Wärme sind über die Wärmekapazität verwandt.}
Wir schreiben dazu drei Versionen der
Funktion:
%
\begin{itemize}
\item Die erste, naive Version addiert einfach auf die
Anfangstemperatur die hinzugefügte Wärme.
\item Die zweite Version berücksichtigt, dass Wasser bei 100\si{\degree}C siedet
und nicht heißer werden kann.
\item Die dritte Version berücksichtigt zusätzlich, dass gefrorenem
Wasser von 0\si{\degree}C eine Wärme von 80\si{\degree}C hinzugefügt werden muss, damit es
schmilzt und dann immer noch erst bei 0\si{\degree}C ist.
\end{itemize}
%
Wir berücksichtigen also schrittweise, dass die innere Energie des
Wassers~-- die Wärme~-- nicht dasselbe ist wie dessen Temperatur.
\paragraph{Naive Version} Wir fangen mit der naiven Version an und gehen nach dem Ablauf der
Konstruktionsanleitungen vor. Zuerst kommt also eine
Kurzbeschreibung:
%
\begin{lstlisting}
; Wassertemperatur nach Erhitzen berechnen, naiv
\end{lstlisting}
%
Als Nächstes kommt die Signatur-Deklaration. Die Anfangstemperatur
und die hinzugefügte Wärme sind die Eingaben; sie stellen wir als
reelle Zahlen dar. Heraus kommt die Endtemperatur, auch eine reelle
Zahl. Da wir schon wissen, dass diese Version etwas einfach ist,
hängen wir eine \lstinline{-0} an den Namen:
%
\begin{lstlisting}
(: heat-water-0 (real real -> real))
\end{lstlisting}
%
Nun schreiben wir einige einfache Beispiele als Testfälle hin:
%
\begin{lstlisting}
(check-expect (heat-water-0 -10 20) 10)
(check-expect (heat-water-0 10 20) 30)
(check-expect (heat-water-0 90 20) 110)
\end{lstlisting}
%
Als Nächstes kommt das Gerüst an die Reihe:
%
\begin{lstlisting}
(define heat-water-0
(lambda (temp heat)
|\ldots|))
\end{lstlisting}
%
Der Rumpf ist jetzt kein Hexenwerk, die beiden Eingaben werden
einfach addiert:
%
\begin{lstlisting}
(define heat-water-0
(lambda (temp heat)
(+ temp heat)))
\end{lstlisting}
%
Noch die Tests laufen lassen und fertig!
\paragraph{Siedendes Wasser} Als Nächstes wollten wir berücksichtigen,
dass Wasser nicht über 100\si{\degree}C heiß werden kann. Die Kurzbeschreibung
passen wir nur leicht an; die Signatur bleibt ebenfalls fast
unverändert~-- wir ändern nur den Namen und machen aus der $0$ eine $1$:
%
\begin{lstlisting}
; Wassertemperatur nach Erhitzen berechnen, Sieden berücksichtigen
(: heat-water-1 (real real -> real))
\end{lstlisting}
%
Bei den Tests können wir natürlich die Tests von \lstinline{heat-water-0}
kopieren, aber den letzten müssen wir anpassen:
%
\begin{lstlisting}
(check-expect (heat-water-1 -10 20) 10)
(check-expect (heat-water-1 10 20) 30)
(check-expect (heat-water-1 90 20) 100)
\end{lstlisting}
%
Beim Testen ist es immer sinnvoll, auch Grenzfälle zu testen~--
schaltet die Funktion wirklich um, wenn die 100 erreicht sind. %%Nicht rum!
Dazu dienen folgende zwei Testfälle:
%
\begin{lstlisting}
(check-expect (heat-water-1 99 1) 100)
(check-expect (heat-water-1 99 2) 100)
\end{lstlisting}
%
Da die Signatur die gleiche ist, ist auch das Gerüst identisch:
%
\begin{lstlisting}
(define heat-water-1
(lambda (temp heat)
|\ldots|))
\end{lstlisting}
%
Ab 100\si{\degree}C muss die Funktion ihr Ergebnis also anders berechnen. Oder,
anders gesagt, die Eingaben fallen in zwei verschiedene Klassen: die,
bei denen die Summe unter $100$ liegt und die, bei denen sie darüber
liegt.
Wir benötigen also ein \lstinline{cond} mit zwei
Zweigen:
%
\begin{lstlisting}
(define heat-water-1
(lambda (temp heat)
(cond
(|\ldots| |\ldots|)
(|\ldots| |\ldots|))))
\end{lstlisting}
%
Wir brauchen nun eine Bedingung, die feststellt, ob die Summe aus
\lstinline{temp} und \lstinline{heat} unterhalb von 100 liegt (genauer:
kleiner \emph{oder gleich}, weil 100 gerade so erreichbar ist) und
eine dafür, dass die Summe darüber liegt. Die könnten so aussehen:
%
\begin{lstlisting}
(<= (+ temp heat) 100)
(> (+ temp heat) 100)
\end{lstlisting}
%
Wenn wir diese beiden Bedingungen an die entsprechenden Stellen im
Rumpf setzen, sieht das so aus:
%
\begin{lstlisting}
(define heat-water-1
(lambda (temp heat)
(cond
((<= (+ temp heat) 100) |\ldots|)
((> (+ temp heat) 100) |\ldots|))))
\end{lstlisting}
%
An die Stellen nach den Bedingungen müssen wir Ausdrücke setzen, die
das Ergebnis liefern, das im jeweiligen Fall richtig ist. Das wäre dann:
%
\begin{lstlisting}
(define heat-water-1
(lambda (temp heat)
(cond
((<= (+ temp heat) 100) (+ temp heat))
((> (+ temp heat) 100) 100))))
\end{lstlisting}
%
Fertig!
%
%
%
\begin{aufgabeinline}
Müssen es bei den beiden Bedingungen unbedingt \lstinline{<=} und
\lstinline{>} sein? Was passiert, wenn Du \lstinline{<=} durch \lstinline{<}
ersetzt und das Programm dann laufen lässt? Was passiert, wenn Du
dann auch das \lstinline{>} ersetzt~-- durch \lstinline{>=}? Warum
funktioniert das Programm dann immer noch?
\end{aufgabeinline}
%
\paragraph{Siendendes Wasser und Eis} Kommen wir zur "<Vollversion">.
Zur Erinnerung: Da müssen wir noch berücksichtigen, dass gefrorenem
Wasser von 0\si{\degree}C eine Wärme von 80\si{\degree}C hinzugefügt werden muss, damit es
schmilzt und dann immer noch 0\si{\degree}C hat.
Wir fangen wieder mit der Kurzbeschreibung an:
%
\begin{lstlisting}
; Wassertemperatur nach Erhitzen berechnen, mit Eis & Sieden
\end{lstlisting}
%
Da diese Version die letzte ist, hat sie keine Nummer mehr. Ansonsten
ist die Signatur unverändert:
%
\begin{lstlisting}
(: heat-water (real real -> real))
\end{lstlisting}
%
Die Tests können wir nicht unverändert übernehmen. Gleich der erste
funktioniert nicht mehr:
%
\begin{lstlisting}
(check-expect (heat-water -10 20) 10)
\end{lstlisting}
%
Das Aufwärmen des Wassers von $-10$\si{\degree}C auf $0$\si{\degree}C erfordert nur $10$\si{\degree}C
Wärmezufuhr, dann aber müssen erst einmal 80\si{\degree} weitere Wärme zugeführt
werden, damit es weiter geht. Den Testfall müssen wir also ändern:
%
\begin{lstlisting}
(check-expect (heat-water -10 20) 0)
\end{lstlisting}
%
Die anderen Testfälle \lstinline{heat-water-1} können so bleiben:
%
\begin{lstlisting}
(check-expect (heat-water 10 20) 30)
(check-expect (heat-water 90 20) 100)
(check-expect (heat-water 99 1) 100)
(check-expect (heat-water 99 2) 100)
\end{lstlisting}
%
Ein paar weitere Tests sollten aber noch genau klären, was um den
Nullpunkt herum so passiert und wo genau er überschritten wird:
%
\begin{lstlisting}
(check-expect (heat-water -10 5) -5)
(check-expect (heat-water -5 60) 0)
(check-expect (heat-water -5 90) 5)
(check-expect (heat-water -1 81) 0)
(check-expect (heat-water -1 82) 1)
\end{lstlisting}
%
Wieder sollten wir darüber nachdenken, in welche Fälle unsere
Eingaben zerfallen. Da gibt es drei naheliegende Fälle:
%
\begin{enumerate}
\item Die Anfangstemperatur ist unter $0$\si{\degree}C, es wird also Eis erwärmt.
\item Die Erwärmung würde die Wassertemperatur auf über $100$\si{\degree}C erhöhen.
\item Alles andere~-- das Wasser fängt flüssig an und bleibt durch die
Erwärmung flüssig.
\end{enumerate}
%
Der erste Fall hat außerdem noch drei "<Unterfälle">:
%
\begin{itemize}
\item Die Erwärmumg bleibt unter $0$\si{\degree}C.
\item Die Erwärmung bleibt bei $0$\si{\degree}C "<stecken">
\item Die Erwärmung erhöht die Temperatur über den Nullpunkt hinaus.
\end{itemize}
%
So komplizierte Fallunterscheidungen sind relativ selten. Wenn sie
doch einmal auftauchen, ist besondere Sorgfalt gefragt. Deshalb
exerzieren wir das hier als Beispiel durch.
Fangen wir wieder mit dem Gerüst für die Funktion an:
%
\begin{lstlisting}
(define heat-water
(lambda (temp heat)
|\ldots|))
\end{lstlisting}
%
Wir wissen schon aus der Analyse der Fälle, dass es drei Fälle gibt.
Deshalb brauchen wir auch wieder ein \lstinline{cond} mit drei Zweigen.
%
\begin{lstlisting}
(define heat-water
(lambda (temp heat)
(cond
(|\ldots| |\ldots|)
(|\ldots| |\ldots|)
(|\ldots| |\ldots|))))
\end{lstlisting}
%
Jetzt ergänzen wir Bedingungen, die den drei Fällen entsprechen. Um
in diesem komplizierten Fall die Zuordnung von Fällen und Bedingungen
klar erkennbar zu machen, stehen diese jeweils als Kommentar darüber:
%
\begin{lstlisting}
(define heat-water
(lambda (temp heat)
(cond
; Die Anfangstemperatur ist unter 0|\si{\degree}|C, es wird also Eis erwärmt.
((< temp 0) |\ldots|)
; Erwärmung würde Wassertemperatur auf über 100|\si{\degree}|C erhöhen.
((>= (+ temp heat) 100) |\ldots|)
; Wasser fängt flüssig an und bleibt durch Erwärmung flüssig.
((and (>= temp 0) (< (+ temp heat) 100)) |\ldots|))))
\end{lstlisting}
%
Jetzt können wir uns daran machen, die Zweige auszufüllen. Da der
erste Zweig (das Eis) komplizierter ist, schieben wir den erstmal vor
uns her. Beim zweiten Zweig kommt einfach 100\si{\degree}C raus. Ebenso
einfach ist der dritte Zweig, bei dem das Wasser flüssig bleibt~-- die
Antwort ist dort \lstinline{(+ temp heat)}. Der Zwischenstand sieht so
aus:
%
\begin{lstlisting}
(define heat-water
(lambda (temp heat)
(cond
; Die Anfangstemperatur ist unter 0|\si{\degree}|C, es wird also Eis erwärmt.
((< temp 0) |\ldots|)
; Erwärmung würde Wassertemperatur auf über 100|\si{\degree}|C erhöhen.
((>= (+ temp heat) 100) 100)
; Wasser fängt flüssig an und bleibt durch Erwärmung flüssig.
((and (>= temp 0) (< (+ temp heat) 100))
(+ temp heat)))))
\end{lstlisting}
%
Dass wir die leichten Fälle zuerst bearbeitet haben, mag wie Faulheit
aussehen. Ist es auch~-- aber es ist auch eine sinnvolle Strategie, die
sich aus Mantra~\ref{mantra:schreib} ergibt:
\mantraschreib*
%
\noindent Auch über den ersten Zweig wissen wir etwas, nämlich dass er selbst
eine Fallunterscheidung ist mit drei Fällen. Wir können also das
\lstinline{cond} (wieder einmal) schon hinschreiben:
%
\begin{lstlisting}
(define heat-water
(lambda (temp heat)
(cond
; Anfangstemperatur ist unter 0|\si{\degree}|C, es wird also Eis erwärmt.
((< temp 0)
(cond
(|\ldots| |\ldots|)
(|\ldots| |\ldots|)
(|\ldots| |\ldots|)))
; Erwärmung würde die Wassertemperatur auf über 100|\si{\degree}|C erhöhen.
((>= (+ temp heat) 100) 100)
; Wasser fängt flüssig an und bleibt durch Erwärmung flüssig.
((and (>= temp 0) (< (+ temp heat) 100))
(+ temp heat)))))
\end{lstlisting}
%
Auch hier ist es sinnvoll, die Beschreibungen der Fälle über die
Zweige zu schreiben. Danach ergänzen wir die Bedingungen mit
folgendem Ergebnis:
%
\begin{lstlisting}
(define heat-water
(lambda (temp heat)
(cond
; Anfangstemperatur ist unter 0|\si{\degree}|C, es wird also Eis erwärmt.
((< temp 0)
(cond
; Erwärmumg bleibt unter 0|\si{\degree}|C.
((< (+ temp heat) 0) |\ldots|)
; Erwärmung bleibt bei 0|\si{\degree}|C "stecken".
((and (>= (+ temp heat) 0)
(< (+ temp heat) 80))
|\ldots|)
; Erwärmung erhöht Temperatur über den Nullpunkt hinaus.
((and (>= (+ temp heat) 0)
(>= (+ temp heat) 80))
|\ldots|)))
; Erwärmung würde Wassertemperatur auf über 100|\si{\degree}|C erhöhen.
((>= (+ temp heat) 100) 100)
; Wasser fängt flüssig an und bleibt durch Erwärmung flüssig.
((and (>= temp 0) (< (+ temp heat) 100))
(+ temp heat)))))
\end{lstlisting}
%
Die Bedingungen sind jetzt noch komplizierter, aber erschließen sich
hoffentlich durch genauere Betrachtungen:
%
\begin{itemize}
\item "<Die Erwärmumg bleibt unter $0$\si{\degree}C.">\\
Das heißt, die Summe von
Anfangstemperatur und Erwärmung ist kleiner als $0$\si{\degree}C.
\item "<Die Erwärmung bleibt bei 0\si{\degree}C stecken.">\\
Das heißt, die Summe von Temperatur und Erwärmung muss zwischen $0$\si{\degree}C
und $80$\si{\degree}C liegen.
\item "<Die Erwärmung erhöht die Temperatur über den Nullpunkt hinaus.">\\
Das Summe von Temperatur und Erwärmung geht nicht nur über $0$\si{\degree}C
sondern auch über 80\si{\degree}C hinaus.
\end{itemize}
%
Die Antworten unter diesen Bedingungen sind vergleichsweise einfach zu
ergänzen:
%
\begin{lstlisting}
(define heat-water
(lambda (temp heat)
(cond
; Anfangstemperatur ist unter 0|\si{\degree}|C, es wird also Eis erwärmt.
((< temp 0)
(cond
; Erwärmumg bleibt unter 0|\si{\degree}|C.
((< (+ temp heat) 0) (+ temp heat))
; Erwärmung bleibt bei 0|\si{\degree}|C "stecken".
((and (>= (+ temp heat) 0)
(< (+ temp heat) 80))
0)
; Erwärmung erhöht Temperatur über den Nullpunkt hinaus.
((and (>= (+ temp heat) 0)
(>= (+ temp heat) 80))
(- (+ temp heat) 80))))
; Erwärmung würde Wassertemperatur auf über 100|\si{\degree}|C erhöhen.
((>= (+ temp heat) 100) 100)
; Wasser fängt flüssig an und bleibt durch Erwärmung flüssig.
((and (>= temp 0) (< (+ temp heat) 100))
(+ temp heat)))))
\end{lstlisting}
%
Fertig!\footnote{Allerdings noch nicht ganz richtig: Vielleicht siehst Du,
dass da noch etwas nicht ganz stimmt. Wir kommen später darauf zurück.}
\medskip
Also \emph{fast} fertig. Wenn wir das Programm näher betrachten,
fällt etwas Verbesserungspotenzial auf. Fangen wir an mit der
Bedingung:
%
\begin{lstlisting}
(and (>= (+ temp heat) 0)
(>= (+ temp heat) 80))
\end{lstlisting}
%
Das ist übertrieben: Eine Temperatur über 80\si{\degree}C liegt auch über 0\si{\degree}C.
Wir können das also vereinfachen auf:
%
\begin{lstlisting}
(>= (+ temp heat) 80)
\end{lstlisting}
%
Das ist mathematisch einleuchtend, zur Sicherheit sollten wir aber die
Tests nochmals laufen lassen: Aber sie laufen alle noch erfolgreich
durch.
Bevor wir die Funktion noch weiter vereinfachen, ist es sinnvoll, die
Funktionsweise der Auswertung von \lstinline{cond}-Ausdrücken zu ergründen:
%
\begin{aufgabeinline}
Lasse die \lstinline{heat-water}-Funktion im Stepper laufen und
beobachte, wie~-- vor allem in welcher Reihenfolge~-- die Bedindungen
in einem \lstinline{cond}-Ausdruck ausgewertet werden.
\end{aufgabeinline}
%
Wenn Du diese Aufgabe erledigt hast, wirst Du beobachtet haben, dass DrRacket
die Bedingungen in einem \lstinline{cond}-Ausdruck nacheinander
auswertet. Sobald eine davon \lstinline{#t} ergibt, macht DrRacket mit dem
Ausdruck dieses Zweiges weiter. Die restlichen Zweige werden gar nicht
mehr berücksichtigt, unabhängig davon, ob sie \lstinline{#t} ergeben
könnten oder nicht.
Das können wir ausnutzen, um die Funktion weiter zu vereinfachen.
Dazu betrachten wir die beiden ersten Bedingungen im "<inneren">
\lstinline{cond}:
%
\begin{lstlisting}
(cond
((< (+ temp heat) 0) (+ temp heat))
((and (>= (+ temp heat) 0)
(< (+ temp heat) 80))
0)
|\ldots|)
\end{lstlisting}
%
Wenn die erste Bedingung in diesem Ausschnitt \lstinline{#f} ergeben hat, also nicht stimmt,
dann ist \lstinline{(+ temp heat)} größer oder gleich 0. Das heißt, der
Teilausdruck \lstinline{(>= (+ temp heat) 0)} in der \emph{nächsten}
Bedingung liefert \emph{immer} \lstinline{#t}. Wir können also
vereinfachen:
%
\begin{lstlisting}
(cond
((< (+ temp heat) 0) (+ temp heat))
((and #t
(< (+ temp heat) 80))
0)
|\ldots|)
\end{lstlisting}
%
Ein Ausdruck \lstinline{(and #t $e$)} liefert immer das gleiche Ergebnis
wie \(e\)~-- schau nochmal auf Seite~\pageref{page:and}, um Dich davon
zu überzeugen! Wir können also das innere \lstinline{cond} weiter
vereinfachen zu:
\begin{lstlisting}
(cond
((< (+ temp heat) 0) (+ temp heat))
((< (+ temp heat) 80) 0)
|\ldots|)
\end{lstlisting}
%
\label{page:bedingungen-vereinfachen}
Wir benutzen also Mathematik (genauer gesagt
\textit{Algebra\index{Algebra}}, also die Lehre der Gleichungen), um
unser Programm zu vereinfachen. Algebra ist ein sehr mächtiges
Werkzeug in der Programmierung, und wir werden es noch oft nutzen.
Das fällt oft einfacher, wenn wir gar nicht über die konkrete
Bedeutung der Ausdrücke nachdenken sondern nur Gleichungen benutzen,
wie oft in der Mathematik und entsprechend
Mantra~\ref{mantra:gleichungen} auf Seite~\pageref{mantra:gleichungen}.
Wir sind aber mit dem Vereinfachen noch nicht fertig. Betrachten wir
nun die drei "<äußeren"> Bedingungen:
%
\begin{lstlisting}
(cond
((< temp 0) |\ldots|)
((>= (+ temp heat) 100) |\ldots|)
((and (>= temp 0) (< (+ temp heat) 100)) |\ldots|))
\end{lstlisting}
%
Wenn die erste Bedingungen \lstinline{#f} ergibt, gilt automatisch
\lstinline{(>= temp 0)}. Wir können die letzte Bedingung also
vereinfachen zu:
%
\begin{lstlisting}
(and #t (< (+ temp heat) 100))
\end{lstlisting}
%
Wenn auch die zweite Bedingung \lstinline{#f} ergibt, gilt auch
\lstinline{(< (+ temp heat) 100)}.
Wir können also vereinfachen zu \lstinline{(and #t #t)}
und von da zu \lstinline{#t}.
Wir können die Funktion mit dieser Einsicht vereinfachen und \lstinline{#t}
als Bedingung hinschreiben. (Probier es aus!) Allerdings finden
manche das hässlich: Darum ist es auch möglich, statt \lstinline{#t} das
besondere Wort \lstinline{else}\index{else@\texttt{else}}
("<andernfalls"> auf Englisch) hinzuschreiben.
Der \lstinline{else}-Zweig kommt also zum Zug, wenn alle anderen
"<durchgefallen"> sind. Das Ergebnis sieht
dann so aus:
%