Methoden und Funktionen in Scala
Transcription
Methoden und Funktionen in Scala
Methoden und Funktionen in Scala Kapitel 11 und 12 des Buches L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 1 Jede Methode hat einen Typ Für die folgende Methode def square(v: Int) = v*v erhalten wir von der Scala-Shell die Rückmeldung square: (Int)Int L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 2 Jede Methode hat einen Typ In der Definition einer Methode müssen wir die Typen der Parameter angeben. In den meisten Fällen kann der Ergebnistyp fehlen. Methoden werden aber oft verständlicher, wenn man den Ergebnistyp angibt. L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 3 Methode vs. Konstante Was ist der Unterschied zwischen den folgenden Definitionen? scala> def one=1 one = 1 one: Int scala> def by0 = 1/0 one: Int scala> def one() = 1 one: Int scala> val one = 1 one: Int = 1 scala> val by0 = 1/0 ArithmeticException: / by zero L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 4 Methodenrumpf ist ein Ausdruck (Kap. 11.4) Bei Methodenrümpfen, die aus genau einem Statement bestehen, dürfen wir auf die geschweiften Klammern verzichten: def isOdd(v: Int): Boolean = if(v%2==0) return false else return true Bei Einhaltung des funktionalen Programmierstiles können wir auf return verzichten: def isOdd(v: Int): Boolean = if(v%2==0) false else true L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 5 Methoden ohne Rückgabewert Wir können Unit explizit angeben def printSquare(v: Int): Unit = println(square(v)) oder den Java-Stil pflegen def printSquare(v: Int){ println(square(v)) } L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 6 Lokale Methoden Methoden können in Methoden definiert werden: def min(a: Int, b: Int, c: Int) = { def min(a:Int, b:Int) = if(a<b) a else b min(min(a,b), c) } Jede Anweisung ist ein wertliefernder Ausdruck! Ein Block { … } liefert sein letztes Teilergebnis! L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 7 Variable Parameterlisten Wenn der Parametertyp mit * markiert ist, kann die Argumentliste beliebig lang sein: def min(args: Int*): Int = { if(args.size==1) args.head else if(args.size==2){ if(args(0)<args(1)) args(0) else args(1) }else min( args(0), min(args.takeRight(args.size-1) :_*) ) } args hat in main den Collection-Typ Seq[Int] :_* expandiert die Seq zu einzelnen Argumenten. L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 8 Endrekursion Der folgende Code ist nicht endrekursiv. Der Compiler erzeugt rekursiven Bytecode. def kill(n: Int): Int = { if(n==0) throw new RuntimeException() else 1+kill(n-1) } Das Ergebnis von kill(3) ist Exception in thread "main" java.lang.RuntimeException at function.Tester$.kill(Tester.scala:57) at function.Tester$.kill(Tester.scala:58) at function.Tester$.kill(Tester.scala:58) at function.Tester$.kill(Tester.scala:58) at function.Tester$.main(Tester.scala:86) at function.Tester.main(Tester.scala) L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 9 Endrekursion Der folgende Code ist endrekursiv. Der Scala-Compiler erzeugt iterativen Bytecode. def kill(n: Int): Int = { if(n==0) throw new RuntimeException() else kill(n-1) } Das Ergebnis von kill(1000) ist Exception in thread "main" java.lang.RuntimeException at function.Tester$.kill(Tester.scala:53) at function.Tester$.main(Tester.scala:86) at function.Tester.main(Tester.scala) L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 10 Die @tailrec -Annotation Durch die Annotation (seit 2.8) meldet der Compiler einen Fehler, wenn er die Rekursion nicht eliminieren kann import scala.annotation._ @tailrec def sum(v: Int): Int = if(v<=0) 0 else v+sum(v-1) Der Compiler meldet: could not optimize @tailrec annotated method L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 11 Funktionen (Kap. 12) Methoden sind keine Funktionen. Wir können aber aus jeder Methode eine Funktion machen: scala> def add(a: Int, b: Int): Int = a+b add: (Int,Int)Int scala> val f = add _ f: (Int, Int) => Int = <function> f ist eine Funktion. Sie wird wie alle anderen Daten behandelt. L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 12 Funktionsliterale (Closures) Funktion direkt, ohne den Umweg über eine Methode definieren: scala> (a:Int,b:Int) => a+b res1: (Int, Int) => Int = <function2> Und eine Konstante damit definieren: scala> val f = (a:Int,b:Int) => a+b f: (Int, Int) => Int = <function2> Funktionen werden wie gewohnt aufgerufen: scala> f(47,11) res2: Int = 58 L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 13 Funktionen sind auch Objekte Methoden sind keine Objekte, Funktionen dagegen schon. Es gibt in der Scala-API die Typen Function0[R] Function1[T1, R] ... Function22[T1,..,T22,R] L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 14 Funktionen sind auch Objekte Funktionen können auch so definiert werden: val f: Function2[Int,Int,Int] = (a,b)=>a+b ...oder alternativ val f: Function2[Int,Int,Int] = _ + _ (Int, Int)=>Int ist eine Abkürzung für Function2[Int,Int,Int] Der i-te Unterstrich _ steht für den i-ten Parameter. L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 15 Funktionen haben Methoden Function2[T1,T2,R] enthälẗ die Methode apply(v1:T1, v2:T2): R für die eigentliche Funktionalität von f: f.apply(47,11) liefert auch 58. f(47,11) ist nur eine Abkürzung für den Aufruf von f.apply(47,11) L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 16 Die Methode apply Jede Klasse/object kann eine oder mehrere applyMethoden mit beliebiger Signatur haben. Wie bei den Funktionen kann der Name apply beim Aufruf einfach ausgelassen werden! Dies ermöglicht viele hübsche Konstruktionen. L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 17 Spezialisierungen Die beiden folgenden Methoden scala> def inc(v: Int) = v+1 inc: (Int)Int scala> def dec(v: Int) = v-1 inc: (Int)Int Sind nur Spezialfälle von add. Man könnte etwa schreiben scala> def inc(v: Int) = add(v, 1) inc: (Int)Int L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 18 Die partielle Anwendung Bei Funktionen geht das so: scala> val inc = add(1, _: Int) inc: (Int) => Int = <function1> Das Zeichen ‚_‘ wird wieder als Platzhalter verwendet. Da alle Funktionsteile, für die Parameter mit Werten versehen sind, bereits ausgeführt werden können, spricht man von partieller Anwendung. inc(66) liefert 67 In Scala kann -wie hier- Pattern Matching für die partielle Anwendung eingesetzt werden. L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 19 Das Curry-Prinzip Scala lässt die folgende Definition zu: def add(a:Int) = (b:Int) => a+b Das sieht umständlich aus, dafür können wir die partielle Anwendung ohne Pattern Matching ausführen: val inc = add(1) add kann dann auch so aufgerufen werden: add(47)(11) inc kann dann so aufgerufen werden: inc(66) L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 20 Das Curry-Prinzip Für def add(a:Int) = (b:Int) => a+b Gibt es auch die (üblichere) Kurzform def add(a: Int)( b: Int) = a+b In der funktionalen Programmierung ist die partielle Anwendung sehr wichtig! In Scala kann das mit Pattern-Matching gemacht werden. In anderen Sprachen ist das nicht so. Dort liegen Funktionen dann standardmäßig in Curry-Form vor. L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 21 Das Curry-Prinzip In Scala können Funktionen ganz einfach in ihre Curry-Form transformiert werden, da die FunctionXX-Typen eine Methode curried haben: val inc = add _ curried 1 z.B. ist Function2[T1,T2,R] wie folgt definiert: def curried: T1 => T2 => R = { (x1: T1) => (x2: T2) => apply(x1, x2) } L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 22 Warum Currying in Scala? In Scala gibt es einen Syntax-Trick: Für einstellige Argumentlisten können geschweifte Klammern verwendet werden: def barker = Actor.actor{ val snoopy = new Dog("Snoopy", 23) for(i<-1 to 3) snoopy bark } Mit dem Curry-Prinzip können einelementige Parameterlisten erzeugt werden. Damit eigene Steuerstrukturen definierbar! L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 23 Alles klar? Methoden haben zwar einen eigenen Typ, sie sind aber keine Objekte. Methoden können innerhalb anderer Methoden definiert werden. Eine Methode kann in eine Funktion umgewandelt werden. Funktionen sind Objekte mit eigenen Methoden. Funktionen können anonym definiert werden. Funktionen werden wie andere Daten behandelt. Der Scala-Compiler kann endrekursive Methoden und Funktionen in iterative übersetzen. Funktionen und Methoden können mit Hilfe von PatternMatching oder dem Curry-Prinzip partiell ausgeführt werden. L. Piepmeyer: Funktionale Programmierung - Methoden und Funktionen in Scala 24