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