Übergang von funktionaler zu OOP

Werbung
Ü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
Herunterladen