(algebraische Datentypen;
Spezialfälle: enum, struct, class)
können einer Teilmenge ganzer Zahlen zugeordnet werden
typedef enum { Mon, Tue, Wed, Thu, Fri, Sat, Sun } day;
Designfragen:
int
umgewandelt?
int
umgewandelt?
das ist nett gemeint, aber vergeblich:
#define Mon 0 #define Tue 1 ... #define Sun 6 typedef int day; int main () { day x = Sat; day y = x * x; }
im wesentlichen genauso nutzlos:
typedef enum { Mon, Tue, Wed, Thu, Fri, Sat, Sun } day; int main () { day x = Sat; day y = x * x; }
Übung: was ist in C++ besser?
enum Day { Mon, Tue, Wed, Thu, Fri, Sat, Sun; public static void main (String [] argv) { for (Day d : Day.values ()) { System.out.println (d); } } }
verhält sich wie Klasse
(genauer: Schnittstelle mit 7 Implementierungen)
siehe Übung (jetzt oder bei Objekten)
with Ada.Text_Io; procedure Day is type Day is ( Mon, Tue, Thu, Fri, Sat, Sun ); subtype Weekday is Day range Mon .. Fri; X, Y : Day; begin X := Fri; Ada.Text_Io.Put (Day'Image(X)); Y := Day'Succ(X); Ada.Text_Io.Put (Day'Image(Y)); end Day;
mit Bereichsprüfung bei jeder Zuweisung.
einige Tests können aber vom Compiler statisch ausgeführt werden!
procedure Fruit is subtype Natural is Integer range 0 .. Integer'Last; type Apples is new Natural; type Oranges is new Natural; A : Apples; O : Oranges; I : Integer; begin -- nicht alles korrekt: A := 4; O := A + 1; I := A * A; end Fruit;Natural, Äpfel und Orangen sind isomorph, aber nicht zuweisungskompatibel.
Sonderfall: Zahlenkonstanten gehören zu jedem abgeleiteten Typ.
Typ =
R = A×B×C
Kreuzprodukt mit benannten Komponenten:
erstmalig in COBOL (≤1960
Übung: Record-Konstruktion (in C, C++)?
R = A∪B∪C
disjunkte (diskriminierte) Vereinigung (Pascal)
nicht diskriminiert (C):
I
physikalische Größe =
viele teure Softwarefehler durch Ignorieren der Einheiten.
in F# (Syme, 200?), aufbauend auf ML (Milner, 197?)
http://msdn.microsoft.com/en-us/library/dd233243.aspx
Haskell (http://haskell.org/)
Java
das ist ein algebraischer Datentyp,
die Konstruktoren (Leaf, Nil) bilden die Signatur der Algebra,
die Elemente der Algebra sind Terme (Bäume)
BA : = {f : A→B}
ist sinnvolle Notation, denn
| B|| A| = BA
spezielle Realisierungen:
Design-Entscheidungen:
dynamische Dimensionierung und Allokation,
Bereichsprüfungen. Nicht notwendig rechteckig.
Unterschiede zwischen
Das geht:
(es ist nicht
Designfrage: kann ein Feld (auch: String)
seine Größe ändern?
(C: wird sowieso nicht geprüft, Java: nein, Perl: ja)
in Java: wenn man das will, dann will man
statt Array eine LinkedList, statt String einen StringBuffer.
wenn man mit Strings arbeitet,
dann ist es meist ein Fehler:
benutze Strings zwischen Programmen,
ein einem Programm: benutze immer anwendungsspezifische
Datentypen.
...deren externe Syntax spiel überhaupt keine Rolle
es wird oft als Argument für C (und gegen Java)
angeführt, daß die erzwungene Bereichsüberprüfung
bei jedem Array-Zugriff so teuer sei.
sowas sollte man erst glauben, wenn man es selbst
gemessen hat.
modernen Java-Compiler sind sehr clever
und können theorem-prove away
(most) subscript range checks
das kann man auch in der Assembler-Ausgabe
des JIT-Compilers sehen.
implizite Verweise:
Verweis-Semantik (wie in Java)
Wert-Semantik
Rekursion unter Verwendung von Verweistypen
Pascal:
C: ähnlich, benutze typedef
typedef struct {
A foo;
B bar;
C baz;
} R;
R x; ... B x.bar; ...
type tag = ( eins, zwei, drei );
type R = record case t : tag of
eins : ( a_value : A );
zwei : ( b_value : B );
drei : ( c_value : C );
end record;
typedef union {
A a_value; B b_value; C c_value;
}
interface I { }
class A implements I { int foo; }
class B implements I { String bar; }
Notation dafür in Scala (http://scala-lang.org/)
abstract class I
case class A (foo : Int) extends I
case class B (bar : String) extends I
Verarbeitung durch Pattern matching
def g (x : I): Int = x match {
case A(f) => f + 1
case B(b) => b.length() }
[<Measure>] type kg ;;
let x = 1<kg> ;;
x * x ;;
[<Measure>] type s ;;
let y = 2<s> ;;
x * y ;;
x + y ;;
data Tree a = Leaf a
| Branch ( Tree a ) ( Tree a )
data List a = Nil | Cons a ( List a )
interface Tree<A> { }
class Leaf<A> implements Tree<A> { A key }
class Branch<A> implements Tree<A>
{ Tree<A> left, Tree<A> right }
die unterschiedliche Notation dafür (Beispiele?)
ist bedauerlich.
int main () {
int a [10][10];
a[3][2] = 8;
printf ("%d\n", a[2][12]);
}
statische Dimensionierung,
dynamische Allokation,
keine Bereichsprüfungen.
Form: rechteckig, Adress-Rechnung:
int [M][N];
a[x][y] ==> *(&a + (N*x + y))
int [][] feld =
{ {1,2,3}, {3,4}, {5}, {} };
for (int [] line : feld) {
for (int item : line) {
System.out.print (item + " ");
}
System.out.println ();
}
in
int [][] a
int [,] a
int a [] = {1,2,3};
int b [] = {4,5};
int c [] = {6};
e = {a,b,c};
printf ("%d\n", e[1][1]);
aber welches ist dann der Typ von e
?
int e [][]
.)
aber niemals innerhalb eines Programms.
explizite Verweise in C, Pascal
Testfall:
class ...
ist:
struct ...
ist:
class s {public int foo; public string bar;}
s x = new s(); x.foo = 3; x.bar = "bar";
s y = x; y.bar = "foo";
Console.WriteLine (x.bar);
und dann class
durch struct
ersetzen
type Tree = ^ Node ;
type Tag = ( Leaf, Branch );
type Node = record case t : Tag of
Leaf : ( key : T ) ;
Branch : ( left : Tree ; right : Tree );
end record;