Teil 4: Ausdrücke und Blöcke

Wie bereits im vorherigen Kapitel angesprochen unterscheidet Scala zwischen einzelnen Anweisungen und Codeblöcken, die durch geschweifte Klammern gekennzeichnet werden und mehrere Anweisungen enthalten dürfen.

Ein Block einer Methode zuzuweisen ist sehr einfach:

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

Dies geht aber auch mit nur einem einzelnen Ausdruck:

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

Das Gleiche bei Variablen:

scala> val i = 5
i: Int = 5

scala> val i = { 5 }
i: Int = 5

Oder etwas komplexer:

scala> val i = {
     |   println("hello")
     |   5
     | }
hello
i: Int = 5

scala> def j() = {
     |   println("hello")
     |   5
     | }
j: ()Int

Wie unschwer zu erkennen macht es in Scala keinen Unterschied ob ein Block an eine Variable oder an eine Methode gebunden wird. Die Syntax ist bewusst sehr ähnlich gehalten um es dem Programmierer so einfach wie möglich zu machen. Bleibt nur noch die Frage was jetzt der Unterschied zwischen der Variable `i` und der Methode `j` ist. Wir sehen es beim Aufruf der beiden:

scala> i
res14: Int = 5

scala> j
hello
res15: Int = 5

Der Block, der an `i` gebunden wird wird nur einmal ausgeführt, nämlich dann wenn die Variable erstellt wird. Wenn ein Block dagegen an eine Methode gebunden wird, dann wird er jedes mal ausgeführt wenn die Methode aufgerufen wird. Dies ist bei einem einzelnen Ausdruck übrigens genauso.

Man muss sich in Scala vor Augen führen, dass ein Block oder eine Anweisung immer einen Wert zurückgeben – auch wenn man es explizit nicht festlegt:

scala> def hello() = println("hello")
hello: ()Unit

Die Methode `hello` besitzt den Rückgabetyp `Unit`, sehen wir uns einmal den Typ von `println` an:

scala> :t println
Unit

Ebenfalls `Unit`. Für was steht `Unit`? Der Typ signalisiert, dass der Wert, den eine Anweisung oder ein Block zurück gibt, nicht von weiterer Bedeutung ist. Semantisch kann man `Unit` mit `void` aus C oder Java vergleichen, wenn sie auch nicht komplett identisch sind.

Falls eine Methode `Unit` zurück gibt erlaubt Scala das Gleichheitszeichen wegzulassen und statt dessen geschweifte Klammern zu setzen:

scala> def hello() { println("hello") }
hello: ()Unit

Nach diesem inoffiziellen Style Guide sollte dies dazu genutzt werden um Methoden mit unwichtigem Rückgabewert zu kennzeichnen, das ist aber nur eine Empfehlung und kein Muss. Wird das Gleichheitszeichen bei einem anderen Rückgabewert weggelassen wird weiterhin `Unit` zurückgegeben:

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

scala> doubleValue(2)

Alles hat einen Rückgabewert

Wie bereits erwähnt geben Ausdrücke und Blöcke immer einen Wert zurück. Ein Ausdruck muss aber nicht nur ein Methodenaufruf oder eine Variablendeklaration sein – dazu gehören auch die in die Sprache eingebauten Expressions, wie die if-Expression:

scala> def modTen(i: Int) = if (i < 10) i else i%10 modTen: (i: Int)Int scala> modTen(5)
res16: Int = 5

scala> modTen(23)
res17: Int = 3

Im Gegensatz zu der if-Anweisung in Java gibt Scalas if einen Wert zurück. Die Funktionsweise ist die Gleiche wie beim Conditional Operator in Java:

int modTen(int i) {
  return i < 10 ? i : i%10;
}

Jetzt bleibt nur noch die Frage wie man sich das zu Nutze macht. Immer dann wenn nur eine Anweisung vorliegt erlaubt uns der Scala-Compiler die geschweiften Klammern wegzulassen. Beispiele wie das aussehen könnte haben wir bereits bei den Variablenzuweisungen aber auch bei den Methodendeklarationen gesehen. Der Scala-Compiler beherrscht noch ein Features das man als Anweisungs-Verkettung bezeichnen kann. Dies erlaubt uns die geschweiften Klammern auch wegzulassen wenn mehrere Anweisungen zwar existieren aber zusammengefasst werden können, d.h. sie müssen verkettet werden. Wie das genau aussehen könnte, seht ihr hier:

def calc(i: Int): Int =
  if (i < 100)
    if (i%2 == 0) i*3
    else i*2
  else if (i < 1000)
    if (i*3%2 == 0) i*2
    else if (i < 500) i
    else i*7
  else {
    val j = i*2/3
    i*5/j
  }

Der Code ist ein wenig sinnlos, man kann anhand der fehlenden geschweiften Klammern um den Methodenkörper aber erkennen, dass nur eine Anweisung auf äußerster Ebene existiert. Jede weitere Ebene besitzt ebenfalls nur eine Anweisung – die if-else-Expressions. Lediglich ganz zum Schluss benötigen wir einen else-Block, da mit dem val- und dem return-Statement insgesamt zwei Anweisungen existieren. Wenn dieses Codestück so direkt in die REPL kopiert wird würde er nicht kompilieren, da die Auswertungsreihenfolge anders ist als beim direkten Kompilieren mit scalac. Um das Problem zu umgehen genügt es bereits Klammern um den Methodenkörper zu setzten. Oder wir behelfen uns des `:paste`-Kommandos:

 scala> :paste
 // Entering paste mode (ctrl-D to finish)



// Exiting paste mode, now interpreting.

Damit sollte das Codestück auch korrekt übersetzt werden.

Advertisements

1 comment so far

  1. Michael T. on

    Test für calc:

    Wenn man dem Rückgabewert und j als Double definiert.

    println(calc(50)+“ „+calc(49)+“ „+calc(800)+“ „+calc(300)+“ „+calc(299)+“ „+calc(501)+“ „+calc(1200))

    150.0 98.0 1600.0 600.0 299.0 3507.0 7.5


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: