-
Notifications
You must be signed in to change notification settings - Fork 24
/
i1zus.tex
1638 lines (1529 loc) · 55.5 KB
/
i1zus.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
% 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{Zusammengesetzte Daten}
\label{cha:zusammengesetzte-daten}
Viele Informationen, die in Programmen repräsentiert werden, haben
mehrere Bestandteilen:
%
\begin{itemize}
\item Ein Festessen besteht aus Vorspeise, Hauptgang und Nachspeise.
\item Eine Uhrzeit besteht aus Stunden und Minuten.
\item Eine Tür besteht aus Türblatt und Türgriff.
\end{itemize}
%
Es werden also mehrere Dinge zu einem \textit{zusammengesetzt}.
Eine andere Betrachtungsweise ist, dass ein einzelnes
Ding \textit{mehrere Eigenschaften} hat:
%
\begin{itemize}
\item Ein Filzstift hat Dicke und Farbe.
\item Eine Katze hat Alter und Geschlecht.
\item Ein Lautsprecher hat Minimal- und Maximalfrequenz.
\end{itemize}
%
Um solche Informationen abzubilden, führen wir in diesem Kapitel eine neue Art
Daten ein, die \textit{zusammengesetzten
Daten}\index{zusammengesetzten Daten}.
\section{Computer konfigurieren}
\label{sec:computer-konfigurieren}
\mentioncode{zusammengesetzte-daten/computer.rkt}
%
Viele Computergeschäfte erlauben, bestimmte Komponenten
eines neues Computers auszuwählen, zum Beispiel Prozessor,
Festplatte und Größe des RAM"=Hauptspeichers:
%
\begin{center}
\medskip
\includegraphics[height=0.25\textheight]{zusammengesetzte-daten/computer}
\medskip
\end{center}
%
Anders gesagt, ein Computer\index{Computer} \emph{besteht aus}:
%
\begin{itemize}
\item Prozessor
\item RAM
\item Festplatte
\end{itemize}
%
Natürlich besteht ein Computer auch noch aus anderen Teilen, die
aber (zumindest in diesem Beispiel) immer gleich oder irrelevant sind.
In einer Bestellung müssen also nur diese drei Bestandteile
enthalten sein. Wir nehmen an, dass es beim Prozessor nur auf den Namen
("<Athlon">, "<Xeon">, "<Cell">, \ldots) ankommt, beim RAM nur auf die
Größe in Gigabyte, und auch bei der Festplatte nur auf die Größe in
Gigabyte.
Wir können daraus eine amtliche Datendefinition machen:
%
\begin{lstlisting}
; Ein Computer besteht aus:
; - Prozessor
; - Hauptspeicher-Kapazität in Gbyte
; - Festplatten-Kapazität in Gbyte
\end{lstlisting}
%
Wichtig ist hier die Formulierung "<besteht aus">, die auf
zusammengesetzte Daten hindeutet. Die Daten, die aus so einer
Datendefinition für zusammengesetzte Daten entstehen, können als
Tabelle dargestellt werden:
%
\begin{center}
Computer\qquad
\begin{tabular}[c]{r|l}
\textbf{Feld} & \textbf{Komponente}\\\hline
Prozessor & \verb|"Cell"|\\
RAM & 8\\
Festplatte & 250
\end{tabular}
\end{center}
%
Diese Tabelle steht demnach für einen Computer mit Cell-Prozessor, 8
Gigabyte RAM und einer 250-Gigabyte-Festplatte. Sie hat also mehrere
Bestandteile und ist damit zusammengesetzt. Die Computerfirma wird
viele Computer nach diesem Schema ausliefern, bei denen allesamt
jeweils Prozessor, RAM und Festplatte in der Bestellung stehen wird. Solche
Informationen, die alle dem gleichen Schema folgen, können also nach
"<Typ"> sortiert werden, wobei der Typ
festlegt, um was für eine Art Information es geht und aus welchen
Teilen sie zusammengesetzt ist. In der obigen Tabelle ist das
\textit{Feld} die Allgemeinbezeichnung für ein Bestandteil, das
alle Computer haben. Die \textit{Komponente} ist das konkrete
Bestandteil eines einzelnen Computers.
Zusammengesetzte Daten bilden die Lehrsprachen durch
sogenannte \textit{Records}\index{Records} ab. Jeder Record gehört
zu einem bestimmten
\textit{Record-Typ\index{Record-Typ}}, der festlegt, was für eine
Sorte Information repräsentiert wird und welche Felder die Records
des Record-Typs haben.
Der Record-Typ für Computer sieht feste Felder\index{Feld}
vor ("<Prozessor">, "<RAM">, "<Festplatte">). Ein einzelner Record
dieses Typs besteht aus Komponenten\index{Komponente}, eine pro
Feld. (In diesem Fall \lstinline{"Cell"}, $8$ und $250$.)
Record-Typen müssen in einem Programm explizit mit Hilfe einer
speziellen \textit{Record-Definition}\index{Record-Definition} definiert werden, die mit
\lstinline{define-record} beginnt. Hier ist die
Record-Definition zur Datendefinition für
Computer:\indexvariable{define-record}
%
\begin{lstlisting}
(define-record computer
make-computer
(computer-processor string)
(computer-ram natural)
(computer-hard-drive natural))
\end{lstlisting}
%
Diese etwas komplizierte Form erläutern wir Schritt für Schritt. Weil sie gleich mehrere
Funktionen definiert, die mit Records zu tun haben, heißt
sie \lstinline{define-record}. Nach
\lstinline{define-record} steht der Name der Signatur für
die Werte des Record-Typs,
\lstinline{computer}. Diese Signatur nennen wir ab sofort die
\textit{Record-Signatur}.\index{Record-Signatur}
Als Nächstes in der Record-Definition kommt \lstinline{make-computer},
der Name einer Funktion, die für die \textit{Konstruktion} eines
Computer"=Records zuständig ist. Analog zum Zusammenbauen eines
Computers mit Cell-Prozessor, 8 Gigabyte RAM und 250 Gigabyte
Festplatte wird der dazugehörige Record mit der Funktion
\lstinline{make-computer} folgendermaßen hergestellt:
%
\begin{lstlisting}
(make-computer "Cell" 8 250)
|\evalsto| #<record:computer "Cell" 8 250>
\end{lstlisting}
%
\lstinline{Make-computer} hat folgende Signatur:
%
\begin{lstlisting}
(: make-computer (string natural natural -> computer))
\end{lstlisting}
%
Die drei Eingaben sind der Reihe nach Prozessor, RAM und Festplatte.
\lstinline{Make-computer} macht daraus einen Wert der Sorte
\lstinline{computer} der Computer-Records. Da \lstinline{make-computer}
einen Computer=Record "<konstruiert">, heißt die Funktion auch
\textit{Konstruktor\index{Konstruktor}}.
Mit der Schreibweise
%
\begin{lstlisting}
#<record:... ...>
\end{lstlisting}
%
werden Record-Typ und ihre Komponenten in der REPL sichtbar.
Hier sind einige Beispiele für Computer-Records, mit Kommentaren,
welche die Beziehung zwischen Daten und Information (siehe
Abschnitt~\ref{sec:information-daten} auf
Seite~\pageref{sec:information-daten}) herstellt:
%
\begin{lstlisting}
; Cell, 4 Gbyte RAM, 1000 Gbyte Festplatte
(define gamer (make-computer "Cell" 4 1000))
gamer
|\evalsto| #<record:computer "Cell" 4 1000>
; Xeon, 2 Gbyte RAM, 500 Gbyte Festplatte
(define workstation (make-computer "Xeon" 2 500))
workstation
|\evalsto| #<record:computer "Xeon" 2 500>
\end{lstlisting}
%
Umgekehrt zur Konstruktion nehmen manche Bastler aus dem Computer die
Einzelteile wieder heraus, zum Beispiel, um sie in einem anderen
Computer zu verbauen. Für dieses Herausnehmen sind die Funktionen
\lstinline{computer-processor}, \lstinline{computer-ram} und
\lstinline{computer-hard-drive} zuständig, die von der
Record-Definition definiert werden:
%
\begin{lstlisting}
(computer-processor gamer)
|\evalsto| "Cell"
(computer-ram gamer)
|\evalsto| 4
(computer-hard-drive gamer)
|\evalsto| 1000
\end{lstlisting}
%
Diese drei Funktionen heißen \textit{Selektoren\index{Selektor}}. Sie haben
folgende Signaturen:
%
\begin{lstlisting}
(: computer-processor (computer -> string))
(: computer-ram (computer -> natural))
(: computer-hard-drive (computer -> natural))
\end{lstlisting}
%
Genau genommen sind diese Signaturen redundant: In der
Record-Definition steht ja schon, dass die Felder die Signaturen
\lstinline{string}, \lstinline{natural} und \lstinline{natural} sind. Jeder
Selektor hat \lstinline{computer} als Eingabe und die jeweilige
Feld-Signatur als Ausgabe. Auch die Signatur-Deklaration für den
Konstruktor ist redundant.
%
\begin{aufgabeinline}
Wie ist der Zusammenhang zwischen der Record-Definition und der
Signatur des Konstruktors?
\end{aufgabeinline}
%
Trotzdem ist es zumindest am Anfang hilfreich, sich die Arbeitsweise
von Konstruktor und Selektoren anhand ihrer Signaturen klarzumachen.
Wir schreiben sie darum in diesem Kapitel noch hin, danach nicht mehr.
Mit Hilfe des Konstruktors und der Selektoren können wir weitergehende
Funktionen definieren.
Für den Anfang könnte das
eine Funktion sein, die den Gesamtspeicher eines Computers berechnet,
also Hauptspeicher und Festplattenspeicher zusammen.
Eine solche Funktion müsste Kurzbeschreibung und Signatur wie folgt
haben:\indexvariable{total-memory}
%
\begin{lstlisting}
; Gesamtspeicher berechnen
(: total-memory (computer -> natural))
\end{lstlisting}
%
Hier sind unsere Erwartungen an \lstinline{total-memory}, als Testfälle
formuliert:
%
\begin{lstlisting}
(check-expect (total-memory workstation) 502)
(check-expect (total-memory gamer) 1004)
\end{lstlisting}
%
Das Gerüst ist wie folgt:
%
\begin{lstlisting}
(define total-memory
(lambda (c)
...))
\end{lstlisting}
%
Um etwas aus dem Record zu berechnen, muss \lstinline{total-memory} (und
so gut wie jede andere Funktion auch) die Bestandteile betrachten. Es
ist deshalb sinnvoll, die Schablone mit den Aufrufen der Selektoren zu
bestücken.
%
\begin{lstlisting}
(define total-memory
(lambda (c)
... (computer-processor c) ...
... (computer-ram c) ...
... (computer-hard-drive c) ...))
\end{lstlisting}
%
Jetzt wo die Schablone fertig ist, können wir uns mit dem Inhalt der
Aufgabe beschäftigen: Der Prozessor hat nichts mit der
Speichermenge zu tun, wir können den entsprechenden Selektoraufruf
also wieder löschen:
%
\begin{lstlisting}
(define total-memory
(lambda (c)
... (computer-ram c) ...
... (computer-hard-drive c) ...))
\end{lstlisting}
%
Der Gesamtspeicher ist die Summe der beiden Komponenten:
%
\indexvariable{total-memory}
\begin{lstlisting}
(define total-memory
(lambda (c)
(+ (computer-ram c)
(computer-hard-drive c))))
\end{lstlisting}
%
\lstinline{Total-memory} ist ein Beispiel für eine Funktion, die einen
Record akzeptiert. Umgekehrt gibt es auch Funktionen, die Records
produzieren. Angenommen, unser Computerhändler bietet neben der
Einzelkonfiguration von Prozessor, Hauptspeicher und Festplatte einige
Standardmodelle an~-- sagen wir, ein Billigmodell, ein Modell für
Profis (was immer eine "<Profi"> sein mag) und ein Modell für
Computerspieler. Je nachdem, welches der Modelle der Kunde auswählt,
muss die entsprechende Konfiguration zusammengesetzt werden. Für die
Standardkonfiguration gibt es drei feste Möglichkeiten, es handelt
sich hier also um eine Aufzählung.
Für die Aufzählung machen wir erst einmal~-- nach
Konstruktionsanleitung~\ref{ka:aufzaehlung} auf
Seite~\pageref{ka:aufzaehlung}~-- eine Daten- und eine
Signatur-Definition:
%
\indexvariable{model}
\begin{lstlisting}
; Ein Modell ist eins der folgenden:
; - Billigmodell
; - Profi-Modell
; - Gamer-Modell
(define model
(signature
(enum "cheap" "professional" "gamer")))
\end{lstlisting}
%
Eine Funktion, die zu einer Standardkonfiguration den passenden
Computer fertigt, könnte damit folgende Kurzbeschreibung und Signatur haben:
%
\begin{lstlisting}
; Standard-Computer zusammenstellen
(: standard-computer (model -> computer))
\end{lstlisting}
%
Die Testfälle sollten alle drei Standardkonfigurationen abdecken:
%
\begin{lstlisting}
(check-expect (standard-computer "cheap")
(make-computer "Sempron" 2 500))
(check-expect (standard-computer "professional")
(make-computer "Xeon" 4 1000))
(check-expect (standard-computer "gamer")
(make-computer "Quad" 4 750))
\end{lstlisting}
%
Hier ist das Gerüst:
%
\begin{lstlisting}
(define standard-computer
(lambda (k)
...))
\end{lstlisting}
%
Bei der Schablone gehen wir wieder nach Konstruktionsanleitung~\ref{ka:aufzaehlung} auf
Seite~\pageref{ka:aufzaehlung} vor.
Da es sich beim Argument von \lstinline{standard-computer} um eine Fallunterscheidung~-- eine Aufzählung
mit \emph{drei} Alternativen~-- handelt, können wir die
dazu passende Schablone~-- eine Verzweigung mit \emph{drei} Zweigen~--
zum Einsatz bringen:
%
\begin{lstlisting}
(define standard-computer
(lambda (k)
(cond
(... ...)
(... ...)
(... ...))))
\end{lstlisting}
%
Bei den Tests der Zweige müssen wir \lstinline{k} mit den Elementen der
Aufzählung vergleichen. Da es sich um Zeichenketten handelt, nehmen
wir dazu \lstinline{string=?}:
%
\begin{lstlisting}
(define standard-computer
(lambda (k)
(cond
((string=? k "cheap") ...)
((string=? k "professional") ...)
((string=? k "gamer") ...))))
\end{lstlisting}
%
In jedem Zweig müssen wir nun dafür sorgen, dass der entsprechende
Computer hergestellt wird. Für das Herstellen von Computer-Records
ist der Konstruktor \lstinline{make-computer} zuständig. Dem"-entsprechend
müssen wir in jedem Zweig einen Aufruf von \lstinline{make-computer}
platzieren, jeweils mit drei Argumenten:
%
\begin{lstlisting}
(define standard-computer
(lambda (k)
(cond
((string=? k "cheap")
(make-computer ... ... ...))
((string=? k "professional")
(make-computer ... ... ...))
((string=? k "gamer")
(make-computer ... ... ...)))))
\end{lstlisting}
%
Jetzt müssen wir die Argumente für die Aufrufe von
\lstinline{make-computer} zur Verfügung stellen. Für jeden Aufruf sind
das der Prozessor, die Größe des Hauptspeichers und die
Größe der Festplatte. Die entsprechenden Angaben können wir zum
Beispiel den Testfällen entnehmen. Folgendes kommt dabei heraus:
%
\indexvariable{standard-computer}
\begin{lstlisting}
(define standard-computer
(lambda (k)
(cond
((string=? k "cheap")
(make-computer "Sempron" 2 500))
((string=? k "professional")
(make-computer "Xeon" 4 1000))
((string=? k "gamer")
(make-computer "Quad" 4 750)))))
\end{lstlisting}
%
Fertig!
\begin{aufgabeinline}
Schreibe eine Funktion, die einen Computer klassifiziert als
"<fett">, "<Durchschnitt"> oder "<müde">. Definiere die Kriterien
dafür selbst.
\end{aufgabeinline}
\section{Zusammengesetzte Daten selbst konstruieren}
\mentioncode{zusammengesetzte-daten/wallclock-time.rkt}
%
Für ein weiteres Beispiel greifen wir auf folgenden Satz aus der
Einleitung zurück, den wir schon als Datendefinition auslegen können:
%
\begin{lstlisting}
; Eine Uhrzeit besteht aus Stunde und Minute.
\end{lstlisting}
%
Für die Entwicklung der dazu passenden Record-Definition müssen wir
uns einen Namen für den Record-Typ ausdenken. Dann können wir bereits
ein karges Gerüst hinschreiben:\indexvariable{wallclock-time}
%
\begin{lstlisting}
(define-record wallclock-time
make-wallclock-time
...)
\end{lstlisting}
%
Als Nächstes müssen wir festlegen, \emph{wie viele} Bestandteile die
Records haben sollen. In diesem Fall ("<Stunde und Minute">) sind es
zwei. Wir können das Gerüst der Record-Definition
entsprechend um zwei Felder erweitern:
%
\begin{lstlisting}
(define-record wallclock-time
make-wallclock-time
(... ...)
(... ...))
\end{lstlisting}
%
Auf die Anzahl der Bestandteile zu achten, hilft uns dabei,
Mantra~\ref{mantra:schreib} auf Seite~\pageref{mantra:schreib}
umzusetzen:
\mantraschreib*
\noindent Als Nächstes kommen die Namen der Selektoren. Dabei befolgen wir eine
Konvention, die Selektoren alle mit \lstinline{wallclock-time-} anfangen
zu lassen. Bei der Benennung des Konstruktor haben wir ebenfalls
eine Konvention angewendet, dessen Name sich aus \lstinline{make-} und
dem Namen des Record-Typs ergibt. Also:
%
\begin{lstlisting}
(define-record wallclock-time
make-wallclock-time
(wallclock-time-hour ...)
(wallclock-time-minute ...))
\end{lstlisting}
%
Dass wir die Selektoren untereinander schreiben, dient lediglich der
Übersichtlichkeit, ist also ebenfalls eine Konvention.
Es fehlen noch
die Signaturen bei \lstinline{wallclock-time-hour} und
\lstinline{wallclock-time-minute}. Nicht nur die: Wir brauchen
überhaupt erstmal Datendefinitionen für die beiden Felder.
%
\begin{lstlisting}
; Eine Stunde ist eine ganze Zahl zwischen 0 und 23.
; Eine Minute ist eine ganze Zahl zwischen 0 und 59.
\end{lstlisting}
%
Da es um ganze Zahlen ab 0 geht, könnten wir \lstinline{natural}
verwenden, präziser ist es aber, wenn wir \lstinline{integer-from-to}
aus Abschnitt~\ref{function:integer-from-to} auf
Seite~\pageref{function:integer-from-to} benutzen und eigene
Signatur-Definitionen einführen:
%
\indexvariable{hour}
\indexvariable{minute}
\begin{lstlisting}
; Eine Stunde ist eine ganze Zahl zwischen 0 und 23.
(define hour (signature (integer-from-to 0 23)))
; Eine Minute ist eine ganze Zahl zwischen 0 und 59.
(define minute (signature (integer-from-to 0 59)))
\end{lstlisting}
%
Diese Signaturen können wir jetzt in der Record-Definition für
\lstinline{wallclock-time} verwenden:
%
\indexvariable{wallclock-time}
\begin{lstlisting}
(define-record wallclock-time
make-wallclock-time
(wallclock-time-hour hour)
(wallclock-time-minute minute))
\end{lstlisting}
%
Wir schreiben die Signaturen der definierten Funktionen aus. Diese
ergeben sich direkt aus der Record-Definition:
%
\begin{lstlisting}
(: make-wallclock-time (hour minute -> wallclock-time))
(: wallclock-time-hour (wallclock-time -> hour))
(: wallclock-time-minute (wallclock-time -> minute))
\end{lstlisting}
%
Der Konstruktor akzeptiert für jedes Feld ein Argument~-- entsprechend
stehen die Signaturen der Felder vor dem Pfeil. Heraus kommt beim
Konstruktor immer ein Record, da steht also der Name des Record-Typs.
\begin{feature}{\lstinline{define-record} (einfach)}{scheme:define-record-simple}
Eine Record-Definition\index{Record-Definition}\indexvariable{define-record}
hat folgende allgemeine Gestalt:\label{def:define-record}
%
\begin{lstlisting}
(define-record |\(t\)|
|\(c\)|
(|\(\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, den
\lstinline{define-record} anlegt. Der Konstruktor hat
folgende Signatur:
%
\begin{lstlisting}
(: $c$ ($\mathit{sig}\sb{1}$ $\ldots$ $\mathit{sig}\sb{n}$ -> $t$))
\end{lstlisting}
\item $\mathit{Sel}_1, \ldots, \mathit{sel}_n$ sind die Namen der Selektoren für die Felder
des Record-Typs. Der Selektor $s_i$ hat folgende Signatur:
%
\begin{lstlisting}
(: $\mathit{sel}\sb{i}$ ($t$ -> $\mathit{sig}\sb{i}$))
\end{lstlisting}
\end{itemize}
%
\end{feature}
Bei den Selektoren ist es umgekehrt: Da steht immer die Record-Signatur
vorn (sie akzeptieren ja jeweils einen Record) und nach dem Pfeil
steht die Signatur des jeweiligen Feldes.
Abbildung~\ref{scheme:define-record-simple} fasst die Form
von Record-Definitionen zusammen.
Hier sind drei Beispiele für Uhrzeiten als Daten, mit Kommentaren,
welche die repräsentierte Information beschreiben:
%
\begin{lstlisting}
(define wt1 (make-wallclock-time 11 55)) ; fünf vor zwölf
(define wt2 (make-wallclock-time 0 0)) ; Mitternacht
(define wt3 (make-wallclock-time 1 1)) ; 1 Uhr 1
\end{lstlisting}
%
Zuerst berechnen wir für eine Uhrzeit die Anzahl der Minuten
seit Mitternacht. Hier sind Kurzbeschreibung, Signatur, Testfälle und Gerüst:\indexvariable{minutes-since-midnight}
%
\begin{lstlisting}
; Minuten seit Mitternacht berechnen
(: minutes-since-midnight (wallclock-time -> natural))
(check-expect (minutes-since-midnight wt1) (+ (* 11 60) 55))
(check-expect (minutes-since-midnight wt2) 0)
(check-expect (minutes-since-midnight wt3) 61)
(define minutes-since-midnight
(lambda (wt)
...))
\end{lstlisting}
%
\lstinline{Minutes-since-midnight} soll eine Funktion sein, die
Uhrzeiten als Eingabe akzeptiert, also zusammengesetzte Daten. Eine
Funktion, die aus zusammengesetzten Daten etwas berechnet, muss meist
deren Bestandteile verwenden, auf die sie mit den Selektoren zugreifen
kann. Wir fügen als nächsten Schritt Aufrufe beider Selektoren ein:
%
\begin{lstlisting}
(define minutes-since-midnight
(lambda (wt)
... (wallclock-time-hour wt) ...
... (wallclock-time-minute wt) ...))
\end{lstlisting}
%
Jetzt setzen wir noch etwas Wissen über Uhrzeiten ein und
vervollständigen damit den Rumpf:
%
\indexvariable{minutes-since-midnight}
\begin{lstlisting}
(define minutes-since-midnight
(lambda (wt)
(+ (* 60 (wallclock-time-hour wt))
(wallclock-time-minute wt))))
\end{lstlisting}
%
\begin{aufgabeinline}
Schreibe eine Funktion, die für eine Uhrzeit zurückliefert, ob sich
diese auf den Vormittag oder den Nachmittag (also vor oder nach 12 Uhr
mittags) bezieht.
\end{aufgabeinline}
%
Die Funktion \lstinline{minutes-since-midnight} können wir auch umdrehen:
%
\begin{lstlisting}
; Aus Minuten seit Mitternacht die Uhrzeit berechnen
(: minutes-since-midnight->wallclock-time (natural -> wallclock-time))
\end{lstlisting}
%
Der Pfeil in \lstinline{minutes-since-midnight->wallclock-time} gehört zum Namen und steht für die Umwandlung
einer Größe in eine andere.
Die Testfälle sind gegenüber
\lstinline{minutes-since-midnight} umgedreht:
%
\begin{lstlisting}
(check-expect (minutes-since-midnight->wallclock-time
(+ (* 11 60) 55))
wt1)
(check-expect (minutes-since-midnight->wallclock-time 0)
wt2)
(check-expect (minutes-since-midnight->wallclock-time 61)
wt3)
\end{lstlisting}
%
Hier ist das Gerüst:
%
\indexvariable{minutes-since-midnight->wallclock-time}
\begin{lstlisting}
(define minutes-since-midnight->wallclock-time
(lambda (msm)
...))
\end{lstlisting}
%
Diese Funktion, produziert eine Uhrzeit~-- sie muss also
den Konstruktor für \lstinline{wallclock-time} aufrufen. Daraus ergibt
sich folgende Schablone:
%
\begin{lstlisting}
(define minutes-since-midnight->wallclock-time
(lambda (msm)
(make-wallclock-time ... ...)))
\end{lstlisting}
%
Um die Schablone zum Rumpf zu vervollständigen, müssen wir aus den
Minuten seit Mitternacht \lstinline{msm} zunächst die Stunde berechnen.
Dazu brauchen wir eine Funktion, die ganzzahlig teilt. Die eingebaute
Funktion \lstinline{/} macht das leider nicht:
%
\begin{lstlisting}
(/ 61 60)
|\evalsto| 1.01$\overline{\mathtt{6}}$
\end{lstlisting}
%
Aber die Funktion \lstinline{quotient} hilft uns weiter:\label{func:quotient}
%
\begin{lstlisting}
(quotient 61 60)
|\evalsto| 1
\end{lstlisting}
%
Das können wir in der Schablone benutzen:
%
\begin{lstlisting}
(define minutes-since-midnight->wallclock-time
(lambda (msm)
(make-wallclock-time (quotient msm 60) ...)))
\end{lstlisting}
%
Es fehlt noch die Minute~-- dafür brauchen wir den
Divisions\emph{rest}. Den berechnet die eingebaute Funktion
\lstinline{remainder}:
%
\begin{lstlisting}
(remainder 67 60)
|\evalsto| 7
(remainder 125 60)
|\evalsto| 5
\end{lstlisting}
%
Damit können wir den Rumpf vervollständigen:
%
\begin{lstlisting}
(define minutes-since-midnight->wallclock-time
(lambda (msm)
(make-wallclock-time (quotient msm 60) (remainder msm 60))))
\end{lstlisting}
\begin{aufgabeinline}
Schreibe eine Funktion \lstinline{make-wallclock-time-12h}, die eine
Uhrzeit aus einer Zeitangabe im 12-Stunden-Format konstruiert. Also
zum Beispiel:
%
\begin{lstlisting}
(make-wallclock-time-12h 6 30 "AM")
|\evalsto| #<record:wallclock-time 6 30>
(make-wallclock-time-12h 6 30 "PM")
|\evalsto| #<record:wallclock-time 18 30>
(make-wallclock-time-12h 12 0 "PM")
|\evalsto| #<record:wallclock-time 12 0>
(make-wallclock-time-12h 12 00 "AM")
|\evalsto| #<record:wallclock-time 0 0>
(make-wallclock-time-12h 12 30 "PM")
|\evalsto| #<record:wallclock-time 12 30>
\end{lstlisting}
%
(Für die beiden Fälle 12:00AM und 12:00PM gibt es keine eindeutige
Zuordnung, wir haben das willkürlich festgelegt.)
\end{aufgabeinline}
\begin{aufgabeinline}
Funktioniert \lstinline{minutes-since-midnight->wallclock-time} für
alle Zahlen als Eingabe?
\end{aufgabeinline}
\begin{aufgabeinline}
Schreibe eine Funktion \lstinline{wallclock-time-add-minutes}, die
auf eine Uhrzeit eine bestimmte Anzahl Minuten addiert.
Benutze
die vorhandenen Funktionen \lstinline{minutes-since-midnight} und
\lstinline{minutes-since-midnight->wallclock-time}! Du darfst
annehmen, dass auch das Ergebnis noch in die 24 Stunden eines Tages
passt.
\end{aufgabeinline}
\section{Konstruktionsanleitungen für zusammengesetzte Daten}
Dieser Abschnitt fasst die Erkenntnisse aus den Beispielen
zu zusammengesetzten Daten in Form von Konstruktionsanleitungen
zusammen. Wir fangen an mit der Datenanalyse und der zugehörigen
Record-Definition.
\begin{konstruktionsanleitung}{Zusammengesetzte Daten: Datenanalyse}
\label{ka:zusammengesetzt-datenanalyse}
Zu"-sam"-men"-ge"-setzte Daten kannst Du an Formulierungen wie "<ein $X$
besteht aus~\ldots">, "<ein $X$ ist charakterisiert durch~\ldots">
oder "<ein $X$ hat~\ldots"> erkennen. Manchmal lautet die
Formulierung etwas anders. Die daraus resultierende Datendefinition
ist ein Kommentar im Programm in folgender Form:
%
\begin{lstlisting}
; Ein X hat / besteht aus / ist charakterisiert durch:
; - Bestandteil / Eigenschaft 1
; - Bestandteil / Eigenschaft 2
; ...
; - Bestandteil / Eigenschaft n
\end{lstlisting}
%
Darauf folgt eine entsprechende Record-Definition.
Dafür überlege Dir Namen für den Record-Typ $T$ und für die
Felder, $f_1 \ldots f_n$. Zu jedem Feld gehört
eine Signatur $\mathit{sig}_{i}$:
%
\begin{lstlisting}
(define-record $T$
make-$T$
($T$-$f\sb{1}$ $\mathit{sig}\sb{1}$)
$\ldots$
($T$-$f\sb{n}$ $\mathit{sig}\sb{n}$))
\end{lstlisting}
%
Der Name des Record-Typs \(T\) ist die Record-Signatur,
\lstinline{make-}\(T\) ist der Konstruktor und \(T\)-\(f\sb{i}\)
sind die Selektoren.
Dass der Konstruktorname mit \lstinline{make-} anfängt und dass die
Selektornamen sich aus dem Namen des Typs und der Felder
zusammensetzt, ist reine Konvention. Von ihr solltest Du nur aus
guten Gründen abweichen.
Darunter gehören die Signaturen für den Konstruktor
und die Selektoren:
%
\begin{lstlisting}
(: make-$T$ ($\mathit{sig}\sb{1}$ $\ldots$ $\mathit{sig}\sb{n}$ -> $T$))
(: $T$-$f\sb{1}$ ($T$ -> $\mathit{sig}\sb{1}$))
$\ldots$
(: $T$-$f\sb{n}$ ($T$ -> $\mathit{sig}\sb{n}$))
\end{lstlisting}
%
\end{konstruktionsanleitung}
\pagebreak[1]
Wenn Du genügend Übung mit der Verwendung von Konstruktoren und
Selektoren hast, kannst Du die Signaturen (die ja redundant sind)
auch weglassen: Die relevanten Signaturen für die Felder stehen ja
schon in der Record-Definition.
Wenn Du die Datenanalyse und die Record-Definition für
zusammengesetzte Daten abgeschlossen hast, solltest Du anhand der
Signatur der Funktion feststellen, ob die zusammengesetzten Daten als
Ein- oder als Ausgabe verwendet werden. Abhängig davon kannst Du die
entsprechende Schablone aus den folgenden beiden
Konstruktionsanleitung auswählen.
\begin{konstruktionsanleitung}{Zusammengesetzte Daten als Eingabe:
Schablone}
\label{ka:zusammengesetzt-eingabe-schablone}
Wenn Deine Funktion zusammengesetzte Daten als Eingabe akzeptiert
(das ergibt sich aus der Signatur), gehe nach Schreiben des Gerüstes
folgendermaßen vor:
%
\begin{enumerate}
\item Für jede Komponente, schreibe \texttt{($\mathit{sel}$ $r$)} in die
Schablone, wobei $\mathit{sel}$ der Selektor der Komponente und $r$ der Name
des Record-Parameters ist, also zum Beispiel:
\begin{lstlisting}
(wallclock-time-hour wt)
\end{lstlisting}
\item Vervollständige die Schablone, indem Du einen Ausdruck
konstruierst, in dem die Selektor"=Anwendungen vorkommen.
\item Es ist möglich, dass nicht alle Selektor-Anwendungen im Rumpf
verwendet werden: In diesem Fall lösche die Selektor-Anwendung
wieder.
\end{enumerate}
%
\end{konstruktionsanleitung}
%
Mit etwas Übung kannst Du nicht benötigte Selektor-Anwendungen auch von
vornherein weglassen. Gelegentlich deutet es aber auf einen Fehler
hin, wenn eine fehlt: Darum ist es oft sinnvoll, sie zunächst
hinzuschreiben.
\noindent Funktionen, die zusammengesetzte Daten als Ausgabe haben, müssen einen
entsprechenden Record konstruieren und deshalb den Konstruktor
aufrufen. Hier ist Schablone dafür:
%
\begin{konstruktionsanleitung}{Zusammengesetzte Daten als Ausgabe:\\
Schablone}
\label{ka:zusammengesetzt-ausgabe-schablone}
Wenn Deine Funktion zusammengesetzte Daten als Ausgabe hat, schreibe
einen Aufruf des passenden Record-Konstruktors in den Rumpf,
zunächst mit einer Ellipse für jedes Feld des Records, also zum
Beispiel:
%
\begin{lstlisting}
(make-wallclock-time $\ldots$ $\ldots$)
\end{lstlisting}
\end{konstruktionsanleitung}
\section{Ein- und Ausgabe zusammengesetzter Daten}
\label{sec:armadillo}
\mentioncode{zusammengesetzte-daten/dillo.rkt}
%
In diesem Abschnitt kombinieren wir Ein- und
Ausgabe zusammengesetzter Daten in einer einzigen Funktion.
Im Beispiel dafür geht es um Gürteltiere in Texas:
Die überqueren insbesondere die Highways
und werden dabei leider oft überfahren~-- am Straßenrand
sind entsprechend viele Gürteltiere zu sehen. Außerdem füttern
freundliche Autofahrer gelegentlich die Gürteltiere. Mit diesen
beiden Aspekten wollen wir uns beschäftigen: Was passiert, wenn ein
Gürteltier überfahren wird? Was passiert, wenn ein Gürteltier
gefüttert wird? Entsprechend interessiert uns, ob ein Gürteltier am
Leben ist und welches Gewicht es hat. Das können wir nach
Konstruktionsanleitung~\ref{ka:zusammengesetzt-datenanalyse} auf
Seite~\pageref{ka:zusammengesetzt-datenanalyse} direkt in eine
Datendefinition übersetzen:
%
\begin{lstlisting}
; Ein Gürteltier hat folgende Eigenschaften:
; - Gewicht (in g)
; - lebendig oder tot
\end{lstlisting}
%
Wiederum handelt es sich um zusammengesetzte Daten, wie
aus der Formulierung "<hat"> ersichtlich ist. Wir beschränken uns
hier auf die beiden Eigenschaften, die für die Aufgabenstellung
relevant sind.
Aus der Datendefinition können wir direkt eine passende
Record-Definition machen:
%
\indexvariable{dillo}
\begin{lstlisting}
(define-record dillo
make-dillo
(dillo-weight natural)
(dillo-alive? boolean))
\end{lstlisting}
%
("<Dillo"> steht kurz für "<Armadillo">, das aus dem Spanischen
übernommene englische Wort für Gürteltier.)
Für das Feld \lstinline{alive?} könnten wir unterschiedliche Repräsentationen
wählen: Eine Aufzählung wäre möglich; wir haben uns für einen
booleschen Wert entschieden, der die Frage "<Lebt das Gürteltier?">
beantwortet. Hier sind die Signaturen für die Record-Funktionen:
%
\begin{lstlisting}
(: make-dillo (natural boolean -> dillo))
(: dillo-weight (dillo -> natural))
(: dillo-alive? (dillo -> boolean))
\end{lstlisting}
%
Hier sind einige Exemplare als Daten plus Information:
%
\begin{lstlisting}
(define dillo1 (make-dillo 55000 #t)) ; 55 kg, lebendig
(define dillo2 (make-dillo 58000 #f)) ; 58 kg, tot
(define dillo3 (make-dillo 60000 #t)) ; 60 kg, lebendig
(define dillo4 (make-dillo 63000 #f)) ; 63 kg, tot
\end{lstlisting}
%
Fangen wir damit an, Gürteltiere zu füttern. Die
Standard-Futter-Portion ist dabei 500\,g, und das Gürteltier nimmt durch
die Fütterung um das entsprechende Gewicht zu. Hier sind Kurzbeschreibung
und Signatur:
%
\begin{lstlisting}
; Gürteltier mit 500 g Futter füttern
(: feed-dillo (dillo -> dillo))
\end{lstlisting}
%
Hier der erste, naheliegende Testfall:
%
\begin{lstlisting}
(check-expect (feed-dillo dillo1) (make-dillo 55500 #t))
\end{lstlisting}
%
Bei \lstinline{feed-dillo} ist relevant, was es mit toten
Gürteltieren macht: Tote Gürteltiere fressen nicht, entsprechend
nehmen sie auch nicht zu, wenn man ihnen Futter anbietet:
%
\begin{lstlisting}
(check-expect (feed-dillo dillo2) dillo2)
\end{lstlisting}
%
Hier das Gerüst der Funktion:
\begin{lstlisting}
(define feed-dillo
(lambda (dillo)
...))
\end{lstlisting}
%
Für den Namen des Parameters verwenden wir auch \lstinline{dillo}, nicht
zu verwechseln mit der Signatur, die ebenfalls \lstinline{dillo} heißt. Das
\lstinline{lambda} sorgt dafür, dass \lstinline{dillo} sich innerhalb seines
Rumpfes auf den Parameter bezieht, nicht auf die weiter außen
stehende Signatur.
\begin{aufgabeinline}
Um Dir klarzumachen, welches \lstinline{dillo} zu welchem
\lstinline{lambda} beziehungsweise zu welcher Definition gehört, kannst
Du in \drscheme{} den Knopf \lstinline{Syntaxprüfung} drücken und
danach den Maus-Zeiger über die verschiedenen Vorkommen von
\lstinline{dillo} bewegen.
\end{aufgabeinline}
%
Mehr zu diesem Thema kommt in
Abschnitt~\ref{sec:lexikalische-bindung} auf
Seite~\pageref{sec:lexikalische-bindung}.
\lstinline{Feed-dillo} hat zusammengesetzte Daten sowohl als Eingabe
als auch als Ausgabe. Entsprechend kommen die Schablonen für beide
Situationen zum Einsatz.
Zunächst ist die Schablone für zusammengesetzte
Daten als Eingabe aus
Konstruktionsanleitung~\ref{ka:zusammengesetzt-eingabe-schablone} auf
Seite \pageref{ka:zusammengesetzt-eingabe-schablone} an der Reihe.
Wir schreiben die Aufrufe der Selektoren auf:
%
\begin{lstlisting}
(define feed-dillo
(lambda (dillo)
... (dillo-weight dillo) ...
... (dillo-alive? dillo) ...))
\end{lstlisting}
%
Dazu kommt die Schablone für zusammengesetzte Daten als Ausgabe aus
Konstruktionsanleitung~\ref{ka:zusammengesetzt-ausgabe-schablone} auf
Seite~\pageref{ka:zusammengesetzt-ausgabe-schablone}, also
der Aufruf des Konstruktors:
%
\begin{lstlisting}
(define feed-dillo
(lambda (dillo)
(make-dillo ... ...)
... (dillo-weight dillo) ...
... (dillo-alive? dillo) ...))
\end{lstlisting}
%