Übergang von funktionaler zu OOP Algorithmen und Datenstrukturen II 1 Imperative vs. funktionale Programmierung Plakativ lassen sich folgende Aussagen treffen: funktional: imperativ: Algorithmen und Datenstrukturen II Berechnung von Werten von Ausdrücken Berechnung des Kontrollflusses 2 Beispiele zur Unterscheidung: Java vs. Haskell 1. Berechnung des Betrages einer ganzen Zahl n: public static int abs(int n) { if (n >= 0) return n; else return -n; } > abs’ :: (Ord a, Num a) => a -> a > abs’ n | n >= 0 = n > | otherwise = -n Algorithmen und Datenstrukturen II 3 2. Berechnung von sum(n) = n X i2 i=1 public static int sum(int n) { int s = 0; for(int i=1; i<=n; i++) s = s + i * i; return s; } > sum’ :: (Enum a, Num a) => a -> a > sum’ n = foldl g 0 [1..n] > where g x y = x + y * y Algorithmen und Datenstrukturen II 4 3. Berechnung von nextSquare(n) = min{q | q > n, q = s2 , s ∈ N} public static int nextSquare(int n) { int i = 1; int q = 1; while (q <= n) { i++; q = i*i; } return q; } > nextSquare’ :: (Num a, Enum a) => a -> a > nextSquare’ n = (head.dropWhile (<=n).map (^2)) [1..] Algorithmen und Datenstrukturen II 5 Entsprechungen/Unterschiede Es gibt folgende Entsprechungen zwischen beiden Welten: funktional • Liste von Zwischenergebnissen (anonym) • Listentyp [t] • Rekursionsschemata (foldl, map) • abstrahiert von Zwischenergebnissen • Speicheraufwändig • Listen spielen eine zentrale Rolle Algorithmen und Datenstrukturen II 6 imperativ • Wertabfolgen in Behältern (benannt) • Behältertyp t • spezielle Anweisungen (while, for) • Behälter werden weiterbenutzt • Speicherökonomisch • Listen sind ein Datentyp wie viele andere Algorithmen und Datenstrukturen II 7 Listen in Java Algorithmen und Datenstrukturen II 8 public class Node { protected int element; protected Node next; public Node(int val, Node node) { element = val; next = node; } public int element() { return element; } public Node next() { return next; } Algorithmen und Datenstrukturen II 9 } Algorithmen und Datenstrukturen II 10 Knoten zu Listen 2 1 [1,2] = ˆ r r Algorithmen und Datenstrukturen II 11 Die Konstruktoren von IntList public class IntList { private Node first = null; public IntList() { } private IntList(Node first) { this.first = first; } } Algorithmen und Datenstrukturen II 12 public IntList () first private IntList (Node first) first 1 2 ··· n r r r r Die Methode empty /** Tests whether a list is empty. */ public boolean empty() { return first == null; } Algorithmen und Datenstrukturen II 13 Die Methode cons /** cons builds up lists. Returns a new reference to the list cons(val,xs), does not change xs. In order to emphasize that cons doesn’t change the list it acts upon, it is declared static. */ public static IntList cons(int val, IntList xs) { Node n = new Node(val, xs.first); return new IntList(n); } Algorithmen und Datenstrukturen II 14 Cons-Beispiel cons(x,xs) wobei in (a) xs leer ist und in (b) xs der Liste [2,3] entspricht. (a) xs.first (b) xs.first 2 3 r r Algorithmen und Datenstrukturen II return value xs.first x r xs.first return value 2 3 x r r r 15 Die Methoden head und tail Die Funktionen head und tail sind in Haskell folgendermaßen definiert: head (x:xs) = x Algorithmen und Datenstrukturen II tail (x:xs) = xs 16 /** Returns the first element of a list. Returns -1 and prints an error message if the list is empty; does not change the list. */ public int head() { if (empty()) { System.out.println("Error at method head(): Empty List"); return -1; // an exception would be better } return first.element(); } Algorithmen und Datenstrukturen II 17 /** Returns a new reference to the list obtained by removing the first element. Returns null and prints an error message if the list is empty; does not change the list. */ public IntList tail() { if (empty()) { System.out.println("Error at method tail(): Empty List"); return null; // an exception would be better } return new IntList(first.next()); } Algorithmen und Datenstrukturen II 18 Die Methode append Die Definition von append lautet in Haskell: append [] ys = ys append (x:xs) ys = x:append xs ys Algorithmen und Datenstrukturen II 19 Append rekursiv /** Returns a new reference to the list obtained by concatenation of xs and ys; does not change the lists. In order to emphasize this, it is declared static. */ public static IntList append(IntList xs,IntList ys) { if (xs.empty()) return ys; else return cons(xs.head(),append(xs.tail(),ys)); } Algorithmen und Datenstrukturen II 20 ... und nicht-rekursiv private static IntList append2(IntList xs,IntList ys) { Node tmp; if (xs.empty()) return ys; else { for(tmp=xs.first; tmp.next!=null; tmp=tmp.next) ; // Find last node tmp.next = ys.first; return xs; } } Algorithmen und Datenstrukturen II 21 Append-Beispiel Algorithmen und Datenstrukturen II 22 xs.first 1 2 r r ys.first 3 4 5 r r r zs = append(xs,ys) bzw. zs = append2(xs,ys) append (rekursiv) (nicht desktruktive Variante) xs.first 1 2 r r ys.first Algorithmen und Datenstrukturen II @ append2 (nicht rekursiv) @ (desktruktive Variante) @ R @ ys.first xs.first 3 4 5 1 2 r r r r 23 Seiteneffekte! Das Verhalten von append2 ist destruktiv, denn beim Verketten von xs und ys wird xs zerstört (oder milder ausgedrückt, verändert). Diesen Seiteneffekt muss man bei jeder Anwendung von append2 berücksichtigen! Aber auch die erste Version birgt eine Gefahr: Wenn nach zs = append(xs,ys) die Liste ys verändert wird, verändert sich damit auch zs. Algorithmen und Datenstrukturen II 24 Sonderfall xs = ys xs.first 1 2 r r zs = append(xs,xs) bzw. zs = append2(xs,xs) append Algorithmen und Datenstrukturen II @ append2 @ R @ 25 xs.first zs.first 1 2 1 2 r r r r Algorithmen und Datenstrukturen II xs.first 1 2 r r 6 zs.first 26 Vergleich zwischen Haskell und Java Algorithmen und Datenstrukturen II 27 Parametrischer Typpolymorphismus Die Haskell-Funktion swap ist definiert durch: swap (x,y) = (y,x) (der Typ ist (a,b) -> (b,a)) Beispielsweise ist (1,"a") das Ergebnis von swap("a",1). Algorithmen und Datenstrukturen II 28 Um diese Funktion in Java zu implementieren, benutzen wir den Typ Object. Algorithmen und Datenstrukturen II 29 public class Pair { protected Object x; protected Object y; public Pair(Object x, Object y) { this.x = x; this.y = y; } public void swap() { Object tmp = x; x = y; y = tmp; } Algorithmen und Datenstrukturen II 30 public static void main(String[] args) { Pair p = new Pair("a",new Integer(1)); p.swap(); System.out.println(((Integer)(p.x)).intValue()+(String)p.y); } } Algorithmen und Datenstrukturen II 31 Da elementare Daten keine Objekte sind, müssen sie in Objekte verwandelt werden. Dies geschieht mit Hilfe der Hüllenklassen – elementare Daten werden durch einen Hüllenklassenkonstruktor “eingehüllt” und damit zu Objekten. Object HH HH ? j Boolean Char Number PP @ PP PP ) R @ q Integer Long Float Double Abbildung 1: Klassenhierarchie der Hüllenklassen Algorithmen und Datenstrukturen II 32 IntPair Algorithmen und Datenstrukturen II 33 public class IntPair { protected int x; protected int y; public IntPair(int x, int y) { this.x = x; this.y = y; } public void swap() { int tmp = x; x = y; y = tmp; } } Algorithmen und Datenstrukturen II 34 Funktionen höherer Ordnung In Haskell gibt es keinen Unterschied zwischen Funktionen und Daten: Funktionen können Argumente anderer Funktionen sein, Funktionen können Funktionen als Wert liefern etc. In Java sind Funktionen (Methoden) Bestandteil von Objekten. Da Funktionen Objekte als Argumente oder Rückgabewert haben können, unterstützt Java insofern Funktionen höherer Ordnung. Algorithmen und Datenstrukturen II 35 Algebraische Datentypen und Pattern Matching Pattern Matching kann nach einer Idee von Odersky & Wadler (1997) auf sehr elegante Art und Weise simuliert werden. Wir demonstrieren dies an Hand der append-Funktion auf Listen. Algorithmen und Datenstrukturen II 36 public class List { protected static final int NIL_TAG = 0; protected static final int CONS_TAG = 1; protected int tag; public List append(List ys) { switch (this.tag) { case NIL_TAG: return ys; case CONS_TAG: char x = ((Cons)this).head; List xs = ((Cons)this).tail; return new Cons(x, xs.append(ys)); default: return new Nil(); //an exception would be better } Algorithmen und Datenstrukturen II 37 } } public class Cons extends List { protected char head; protected List tail; public Cons(char head, List tail) { this.tag = CONS_TAG; this.head = head; this.tail = tail; } } public class Nil extends List { public Nil() { Algorithmen und Datenstrukturen II 38 this.tag = NIL_TAG; } } Algorithmen und Datenstrukturen II 39 lokale Änderung großer Datenstrukturen • ist in Java ohne weiteres möglich; • in Haskell simulierbar durch Monadena . a Monaden stellen in Haskell eine Möglichkeit dar, imperativ zu programmieren, d.h. es wird ein Kontrollfluss simuliert. Algorithmen und Datenstrukturen II 40 Klassen, Objekte und Vererbung • in Java kein Problem (objektorientierte Programmiersprache); • Haskell-Klassen definieren abstrakte Methoden, jedoch keine Objekte. Eine Instanz einer Klasse muss diese abstrakten Methoden implementieren (d.h. eine eigene Definition angeben). Insofern entspricht eine Haskell-Klasse grob gesprochen einer abstrakten Klasse in Java, die nur abstrakte Methoden enthält (genauer einer Schnittstelle). Algorithmen und Datenstrukturen II 41 Keine Entsprechung gibt es z.B. • in Java für die unendlichen Datenstrukturen in Haskell • in Haskell für die von Java unterstützte Nebenläufigkeit Algorithmen und Datenstrukturen II 42