Teil 3: Methoden

Um Code wiederverwendbar zu machen werden Methoden benötigt, die in Scala in einfachster Form wie folgt deklariert werden:

def <name>(<param1>, <param2>, <paramN>): <value> = {
    <body>
}

Wie bei Variablen beginnt die Deklaration mit einem Schlüsselwort, hier `def`. Der Rückgabewert folgt am Ende des Methodenkopfes direkt nach der Parameterliste. Der Funktionskörper befindet sich in geschweiften Klammern und wird durch ein Gleichheitszeichen an den Methodenkopf gebunden. Ein einfaches Beispiel:

scala> def doubleValue(i: Int): Int = { return i*2 }
doubleValue: (i: Int)Int

scala> doubleValue(2)
res10: Int = 4

Auch hier kann wieder von der Typinferenz Gebrauch gemacht werden – der Rückgabewert der Methode braucht nicht notiert zu werden, doch wenn wir ihn weglassen folgt eine Fehlermeldung:

scala> def doubleValue(i: Int) = { return i*2 }
<console>:7: error: method doubleValue has return statement; needs result type
       def doubleValue(i: Int) = { return i*2 }
                                   ^

Um den Fehler zu beseitigen genügt es das `return` zu entfernen:

scala> def doubleValue(i: Int) = { i*2 }
doubleValue: (i: Int)Int

Weshalb ist das so? Nun, in Scala gibt es eine Unterscheidung zwischen einzelnen Anweisungen und ganzen Codeblöcken (wobei ein Codeblock immer in geschweiften Klammern steht). Da die Syntax hier sehr allgemein gehalten und nicht nur auf Methoden beschränkt ist musste eine Möglichkeit gefunden werden wie das Verhalten eines Codeblocks immer gleich bleiben kann – egal wo er steht. Was das genau bedeutet erfahrt ihr im nächsten Kapitel. Jetzt genügt es zu wissen, dass der letzte Befehl in einem Codeblock immer seinen Rückgabewert darstellt, weshalb das return-Statement entfallen kann.

Parameterlisten

Es besteht die Möglichkeit Methoden ohne Parameter auch ohne runde Klammern zu schreiben:

scala> def value = { 2*5 }
value: Int

Diese Schreibweise sollte bei Methoden, die keine Seiteneffekte haben bevorzugt werden. Dies ermöglicht es anderen Entwicklern sofort zu erkennen ob eine Methode etwaige Seiteneffekte besitzt oder ob sie immer die gleiche Ausgabe abhängig von der Eingabe produziert. Das Prinzip stammt wieder aus der funktionalen Programmierung. Eine Funktion sollte möglichst wenig Einfluss auf seine Umgebung nehmen, was u.A. besseres Debugging, weil man sich nur auf einen kleinen Teil eines Programms konzentrieren muss (nämlich der aufgerufenen Funktion).

Eine Methode, die nicht seiteneffektfrei ist würde man also wie folgt notieren:

scala> def doSideEffects() = {
     |   println("hello")
     |   2*5
     | }
doSideEffects: ()Int

Diese Methode gibt einen String auf der Konsole aus, sie ändert also ihre Umgebung. Am Typ einer Methode, der von  der REPL nach der Methodendeklaration auch angezeigt wird, kann man dies nochmal erkennen. Mit dem Kommando `:t` oder `:type` können wir uns den Typ eines Ausdrucks innerhalb der REPL anzeigen lassen:

scala> :t doSideEffects
()Int

scala> :t value
Int

Ein Klammerpaar, das einer Methode folgt wird `Parameterliste` genannt. Eine Methode ohne Klammerpaar wäre also eine Methode ohne Parameterliste, wohingegen eine Methode mit leerem Klammerpaar eine Methode mit einer Parameterliste ergibt, die aber keine Parameter erwartet. Der Typ einer Methode ohne Parameterliste ist identisch mit dem Typ einer Variable:

scala> val aRealValue = 2*5
aRealValue: Int = 10

scala> :t aRealValue
Int

Ohne zusätzliche Dokumentation kann man von außen nicht erkennen ob man eine Methode ohne Parameterliste oder eine Variable aufgerufen hat, was uns erlaubt Methoden ohne syntaktischen Overhead bzw. ohne syntaktische Unterschiede innerhalb von Anweisungen zu nutzen:

scala> 10 + aRealValue + value
res0: Int = 30

Das Prinzip wird `Uniform Access Principle` genannt, dieser Blog-Eintrag klärt darüber noch ein wenig auf. Leider entsteht dabei auch der Nachteil, dass wir einer Methode und einer Variable keinen identischen Namen geben können:

scala> def x = { 10 }
x: Int

scala> val x = x
<console>:8: error: recursive value x needs type
       val x = x
               ^

scala> def x() = { 10 }
x: ()Int

scala> val x = x()
<console>:8: error: recursive value x needs type
       val x = x()
               ^

Obwohl sich der Typ einer Methode mit Parameterliste vom Typ einer Variable unterscheidet bekommen wir einen Fehler. Das liegt daran, da sich sowohl Methoden als auch Variablen im gleichen Namensraum befinden.

Neben Methoden ohne Parameterlisten gibt es auch Methoden mit mehreren Parameterlisten:

scala> def product(i: Int)(j: Int) = { i*j }
product: (i: Int)(j: Int)Int

scala> product(3)(4)
res10: Int = 12

scala> product(3, 4)
<console>:9: error: too many arguments for method product: (i: Int)(j: Int)Int
              product(3, 4)
                     ^

Wofür das genau gut ist werde ich in einem der späteren Kapitel noch näher erläutern. Für den Moment reicht es zu wissen, dass mehrere Parameterlisten mit Parameter nicht mehrere Parameter in einer Parameterliste bedeuten, wie man unschwer an der Fehlermeldung erkennen kann.

Optionale Parameter und benannte Argumente

Falls wir bei einer Methodendefinition schon wissen, dass ein Parameter einen Default-Wert haben kann, dann können wir diesen mit einen Gleichheitszeichen an diesen binden. Später können wir, müssen aber nicht, einen anderen Wert festlegen (= optionaler Parameter).

scala> def sayHelloTo(name: String = "Rudi") = { println("Hello "+name) }
sayHelloTo: (name: String)Unit

scala> sayHelloTo("Franz")
Hello Franz

scala> sayHelloTo()
Hello Rudi

Benannte Argumente erlauben uns die Reihenfolge der Parameter beim Aufruf einer Methode zu ändern:

scala> def calc(i: Int, j: Int) = { i*i*j }
calc: (i: Int, j: Int)Int

scala> calc(5, 4)
res8: Int = 100

scala> calc(j = 4, i = 5)
res9: Int = 100

Besonders nützlich ist das in Kombination:

scala> def doSomething(i: Int = 10, str1: String = "hello", str2: String = "world") = {}
doSomething: (i: Int, str1: String, str2: String)Unit

scala> doSomething(str1 = "oho")

Ohne das benannte Argument würde unser Code überhaupt nicht kompilieren, da der Compiler nicht wüsste welchem Parameter er den String zuordnen soll:

scala> doSomething("oho")
<console>:9: error: type mismatch;
 found   : java.lang.String("oho")
 required: Int
Error occurred in an application involving default arguments.
              doSomething("oho")
                          ^
Advertisements

1 comment so far

  1. Manfred Kiener on

    schönes Tutorial für Scala Einsteiger!


Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: