Funktionales Programmieren mit objektorientierten Sprachen Dr. Dieter Hofbauer [email protected] Hochschule Darmstadt, WS 2008/09 – p.1/21 Function Objects In funktionalen Sprachen (Haskell, ML, . . . ) sind Funktionen “first-class citizens”. Sie können • Argument anderer Funktionen sein, • Resultat anderer Funktionen sein, • in Datenstrukturen gespeichert werden, • etc. Wie kann man das in OO-Sprachen (Beispiel Java) imitieren? Eine Möglichkeit: Function Objects Hochschule Darmstadt, WS 2008/09 – p.2/21 Function Objects (Forts.) • Eine Funktion als die Methode eines Function Interfaces. • Benutze das Function Object anstelle der Funktion. • Beispiele: • Comparator: Ordnen von Datenstrukturen • Runnable und Callable: Asynchrone Ausführung von Aktionen im Zusammenhang mit Threads • ActionListener: Callback-Interfaces zur Ausführung von Aktionen in der Zukunft Hochschule Darmstadt, WS 2008/09 – p.3/21 Das Entwurfsmuster Command Aus dem Buch Design Patterns von Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Encapsulate a request as an object, thereby letting you • parameterize clients with different requests, • queue of log requests, and • support undoable operations. Hochschule Darmstadt, WS 2008/09 – p.4/21 Function Objects (Forts.) Hüllen-Interfaces für Function Objects: • Stellt eine einstellige Funktion zur Verfügung: public interface Function { Object apply( Object x ); } • Stellt eine zweistellige Funktion zur Verfügung: public interface Function2 { Object apply( Object x, Object y ); } Bemerkung: Für jede Stelligkeit ein neues Interface? Alternative: Ausschließlich einstellige Funktionen, aber mit Liste / Tupel von Argumenten. Hochschule Darmstadt, WS 2008/09 – p.5/21 Function Objects: Beispiele class ToUpperCase implements Function { public Object apply( Object x ) { return ( (String)x ).toUpperCase( ); } } Liefert das StringArgument in Großbuchstaben. class Concat implements Function2 { public Object apply( Object x, Object y ) { return (String)x + (String)y; } } class MyName implements Function { public Object apply( Object x ) { return new Concat( ).apply( "My", (String)x ); } } "Anna".toUpperCase( ); // new ToUpperCase( ).apply( "Anna" ); // new Concat( ).apply( "My", "Eclipse" ); // new MyName( ).apply( "Eclipse" ); // Liefert die Konkatenation der StringArgumente. Ergänzt das StringArgument um das Präfix "My". "ANNA" // üblicher Methodenaufruf "ANNA" // mit Function Object "MyEclipse" "MyEclipse" Hochschule Darmstadt, WS 2008/09 – p.6/21 Function Objects: Beispiele (Forts.) Ein Function Object kann auch Daten speichern. Beispiel: Die Methode apply erwartet einen String und ergänzt ihn um ein Präfix. Das Präfix wird bei der Konstruktion als Parameter übergeben. class Prefix implements Function { private String prefix; Prefix( String prefix ) { this.prefix = prefix; } public Object apply( Object x ) { return prefix + (String)x; } } new Prefix( "His" ).apply( "Eclipse" ); // "HisEclipse" new Prefix( "Her" ).apply( "Eclipse" ); // "HerEclipse" Hochschule Darmstadt, WS 2008/09 – p.7/21 Funktionen höherer Ordnung Funktion Objects können Argumente anderer Funktionen (Methoden) sein. Beispiel: public static List map( Function f, List list ) { List result = new LinkedList( ); for ( Object x : list ) { result.add( f.apply( x ) ); } return result; } Die Methode map wendet die “Funktion” f auf jedes Element der Liste list an, und gibt eine Referenz auf die so entstehende neue Liste zurück. System.out.println( list ); // [eins, zwei, sieben] map( new ToUpperCase( ), list ); // [EINS, ZWEI, SIEBEN] map( new MyName( ), list ); // [MyEINS, MyZWEI, MySIEBEN] Hochschule Darmstadt, WS 2008/09 – p.8/21 Funktionen höherer Ordnung (Forts.) Beispiel 2: Die Methode filter erzeugt eine Liste derjenigen Elemente der Liste list, die das Prädikat p erfüllen, d.h. für die p den Wert true liefert: public static List filter( Predicate p, List list ) { List result = new LinkedList( ); for ( Object x : list ) { if ( p.apply( x ) ) { result.add( x ); } } return result; } Hochschule Darmstadt, WS 2008/09 – p.9/21 Funktionen höherer Ordnung (Forts.) • Stellt ein einstelliges Prädikat (Methode mit Rückgabetyp boolean) bereit: public interface Predicate { boolean apply( Object x ); } • Testet, ob das String-Argument Länge ≤ 4 hat: class ShortWord implements Predicate { public boolean apply( Object x ) { return ( (String)x ).length( ) <= 4; } } System.out.println( list ); // [eins, sieben, acht] filter( new ShortWord( ), list ); // [eins, acht] Hochschule Darmstadt, WS 2008/09 – p.10/21 Funktionen höherer Ordnung (Forts.) Beispiel 3: Implementiert die Komposition “f nach g” der Funktionen f und g: (f ◦ g)(x) = f (g(x)) Die Methode apply der Komposition wendet • zuerst das apply der “Funktion” g, • danach das apply der “Funktion” f an. Die Function Objects f und g werden als Konstruktorparameter übergeben. class Composition implements Function { private Function f, g; Composition( Function f, Function g ) { this.f = f; this.g = g; } public Object apply( Object x ) { return f.apply( g.apply( x ) ) ; } } Hochschule Darmstadt, WS 2008/09 – p.11/21 Funktionen höherer Ordnung (Forts.) Beispiel 3 (Forts.): System.out.println( list ); // [eins, zwei, sieben] Function f = new MyName( ); Function g = new ToUpperCase( ); map( new Composition( f, g ), list ); // [MyEINS, MyZWEI, MySIEBEN] Dies ist nicht äquivalent zur Komposition in umgekehrter Reihenfolge: map( new Composition( g, f ), list ); // [MYEINS, MYZWEI, MYSIEBEN] Hochschule Darmstadt, WS 2008/09 – p.12/21 Funktionen höherer Ordnung (Forts.) Beispiel 4: n-fache Iteration einer Funktion f : Das Function Object f und die Zahl n ≥ 0 sind Konstruktorparameter. Der Konstruktor realisiert die Gleichungen f 0 = id und f n+1 = f ◦ f n . class Iteration implements Function { private Function comp; Iteration( Function f, int n ) { if ( n < 0 ) throw new IllegalArgumentException( "Iteration für negativen Exponent nicht definiert." ); if ( n == 0 ) comp = new Identity( ); else comp = new Composition( f, new Iteration( f, n-1 ) ); } public Object apply( Object x ) { return comp.apply( x ); } } Hochschule Darmstadt, WS 2008/09 – p.13/21 Funktionen höherer Ordnung (Forts.) Beispiel 4 (Forts.) Eine alternative, effizientere Implementierung: class Iteration implements Function { private Function f; private int n; Iteration( Function f, int n ) { if ( n < 0 ) throw new IllegalArgumentException( "Iteration für negativen Exponent nicht definiert." ); this.f = f; this.n = n; } public Object apply( Object x ) { for ( int i = n; i > 0; i-- ) { x = f.apply( x ); } return x; } } Hochschule Darmstadt, WS 2008/09 – p.14/21 Funktionen höherer Ordnung (Forts.) • Liefert das Argument unverändert zurück: class Identity implements Function { public Object apply( Object x ) { return x; } } • Liefert den um Eins inkrementierten Wert des Integer-Arguments: class Succ implements Function { public Object apply( Object x ) { return (Integer)x + 1; } } • Liefert den doppelten Wert des Integer-Arguments: class Double implements Function { public Object apply( Object x ) { return (Integer)x * 2; } } Hochschule Darmstadt, WS 2008/09 – p.15/21 Funktionen höherer Ordnung (Forts.) Succ succ = new Succ( ); Double doub = new Double( ); // Eins addieren: succ.apply( 2 ); // 3 // Eins und noch eins addieren, auf zwei Arten: succ.apply( succ.apply( 2 ) ); new Composition( succ, succ ).apply( 2 ); // 4 // 4 // Addition als iteriertes Inkrementieren: new Iteration( succ, 100 ).apply( 2 ); // 102 // Exponentiation als iteriertes Verdoppeln: Iterator timesEight = new Iteration( doub, 3 ); timesEight.apply( 2 ); // 16 new Iteration( timesEight, 3 ).apply( 2 ); // 1024 Hochschule Darmstadt, WS 2008/09 – p.16/21 Generic Function Objects Statische Typsicherheit garantieren Typvariablen (ab Java 5): public interface Function<S,T> { S apply( T t ); } Type-Casts werden damit überflüssig: class ToUpperCase implements Function<String,String> { public String apply( String x ) { return x.toUpperCase( ); } } Hochschule Darmstadt, WS 2008/09 – p.17/21 Generic Function Objects (Forts.) Analog für Funktionen höherer Ordnung: public static <S,T> List<S> map( Function<S,T> f, List<T> list ) { List<S> result = new LinkedList<S>( ); for ( T x : list ) { result.add( f.apply( x ) ); } return result; } Hochschule Darmstadt, WS 2008/09 – p.18/21 Generic Function Objects: Aufgaben • Wie sehen die anderen Klassen mit Generics aus, insbesondere Composition und Iteration? • Gesucht ist eine Methode, die für ein gegebenes Prädikat und eine gegebene Liste testet, ob mindestens ein Element der Liste das Prädikat erfüllt: public static <T> boolean exists( Predicate<T> p, List<T> list ) • Schreiben Sie analog eine Methode forall, die testet, ob alle Elemente der Liste das Prädikat erfüllen. Hochschule Darmstadt, WS 2008/09 – p.19/21 Alternative: Closures • Closure: Funktion, die auf freie Variablen im lexikalischen Kontext referiert. • Funktion: Code-Block mit Parametern; darf einen Ergebniswert haben. • Freie Variable: Bezeichner, den die Closure benutzt, aber nicht definiert. Vorträge, Tutorials, Prototyp-Implementierung etc. zu Closures: • Gilad Bracha, Neal Gafter, James Gosling, Peter von der Ahé (aka BGGA): Closures for the Java Programming Language http://www.javac.info/ • Vortrag (ca. 2h) von Neal Gafter (1/2007): Advanced Topics In Programming Languages: Closures For Java http://video.google.com/videoplay?docid=4051253555018153503 Hochschule Darmstadt, WS 2008/09 – p.20/21 Closures in Java 7: Beispiel Eine Methode, die for-each-Schleifen via Closures realisiert: public static <T> void forEach( Iterable<T> seq, { T => void } f ) { for ( T x : seq ) { f.invoke(x); } } Beispielaufruf: List<Integer> nums = ...; { Integer => void } print = { Integer n => System.out.println( n ); }; Utils.forEach( nums, print ); Hochschule Darmstadt, WS 2008/09 – p.21/21