Programmieren in Scala 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 232 / 300 Motivation • Aus Sicht unseres Sotwaretechnik-Lehrstuhls: Scala ist eine der derzeit modernsten Programmiersprachen • KIV wird derzeit nach Scala portiert (fast fertig) • KIV ist dann mit Scala und Java (GUI) programmiert. • Scala unterstützt Konzepte die vieles mit den Konzepten der KIV-Spezifikationen gemeinsam haben. 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 233 / 300 Was ist Scala? • Scala ist entwickelt an der Uni Lausanne von Prof. Odersky http://www.scala-lang.org • Viele Dokus (Tutotial, etc.) kann man dort finden • Eclipse-IDE: //http://www.scala-ide.org • Scala ist eine objektorientierte Sprache • Alle Konzepte von Java (Methoden, Klassen, Vererbung etc.) gibt es (z.T. in verbesserter Form) auch in Scala • Scala wird in ByteCode der JVM compiliert • Scala unterstützt auch die Konzepte aus funktionalen Sprachen (Higher-Order Funktionen Pattern Matching etc.) • Die Syntax von Scala ist etwas anders, und deutlich verbessert 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 234 / 300 Scala: Typen • Scala kennt wie Java Klassen und generische Klassen • Oberster Typ ist Any statt Object • Scala kennt keine primitiven Typen: • statt int und Integer gibt es nur Int • analog: bool, Boolean ⇒ Boolean, array, Array ⇒ Array • generische Typen werden mit eckigen Klammern geschrieben: Array[Int] statt Array<Int> • Array-Zugriff mit runden Klammern a(i) (statt a[i]) • Fest vordefiniert sind Tupel mit (für 3-Tupel) Typ (A,B,C). Sie werden auch als (a,b,c) konstruiert. Die Felder werden z.B. mit (a,b,c). 2 selektiert (liefert b) 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 235 / 300 Scala: Methodendefinition • Scala kennt wie Java statische und dynamische Methoden • Java: type method(argty1 arg1, argty2 arg2, ...){ body } • Scala: def method(arg1:argty1,arg2:argty, ...):type = { body } • Der Typ void heisst Unit in Scala • Methoden ohne Resultat können vereinfacht als def method(arg1:argty1, ... ) { body } geschrieben werden (kein Typ und kein Gleichheitszeichen) 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 236 / 300 Scala: Methodenaufruf • Aufruf wie in Java für statische und dynamische Methoden type . smethod (arg1, arg2, . . . ) object . dmethod (arg1, arg2, . . . ) • Bei Methoden ohne Argumente dürfen Leerklammern weggelassen werden (auch schon bei der Definition) • Konvention: Leerklammern weglassen gdw. keine Seiteneffekte: z.B. Selektoren (“getter”) und Tests: list.length, list.isEmpty • Vorteil: Feldzugriff kann lokal auf get-Methode (gleichen Namens) geändert werden (keine Änderung in anderen Klassen!) • Dynamischen Methoden mit einem Argument darf man mit object method arg aufrufen (Infix: weder Punkt noch Klammern!) • Vorteil: +, * etc. haben keine Sonderrolle mehr (sie können auch überladen werden). 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 237 / 300 Ausprobieren von Scala • Scala kann man wie Java compilieren und ein Programm main(arglist:Array[String]):Unit in einem object ausführen. • Scala kann aber auch mit einem Kommandozeileninterpreter (entweder von innerhalb Eclipse oder standalone) bedienen • Aufruf von scala gibt scala>-prompt • Eintippen von Ausdrücken wertet diese aus scala> "Hello scala> 7 scala> 2 scala> 6 "Hello" + " World!" World!" 3 + 4 new Array(4,5).length new Array(5,6)(1) 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 238 / 300 Scala: Felder und lokale Variablen • Scala unterscheidet bei Feldern und Variablen überschreibbare (var) und nicht überschreibbare (val; Java: final) • nicht überschreibare Felder/Variablen val x:Int = 5 • überschreibare Felder/Variablen var x:Int = 5 • Scala implementiert eine Typinferenz: Für die meisten Variablen und initialisierten Felder kann die Typangabe wegfallen. • Für das obige Beispiel: val x = 5 ist ok 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 239 / 300 Scala: Methodenrümpfe • Scala kennt keinen Unterschied zwischen Statement und Expression. • Statements sind einfach Expressions vom Typ Unit • Deshalb ist mischen erlaubt, z.B.: val x = if (y > 5) { val y = 3; y + 2} else 5 • Der ?-Operator von Java ist in Scala überflüssig • In Scala werden Strichpunkte nur benötigt, wenn zwei Statements auf derselben Zeile stehen (sehr selten) • Zeilenumbruch fügt (wo sinnvoll) implizit einen Strichpunkt ein • Wenn return expr das letzte Statement einer Methode ist, darf nur expr geschrieben werden. 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 240 / 300 Scala: Beispele für Methoden • Fakultätsfunktion: def fac(x:Int):Int = {if (x == 0) 0 else fac(x - 1)} • Länge (wie in Listenklasse vordefiniert): def length:Int = {if (isEmpty) 0 else tail.length + 1} • Letztere Funktion würde in Java so aussehen: int length() {if (isEmpty) return 0; else return tail.length() + 1;} 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 241 / 300 Scala: Klassendefinitionen (1) • Java Klassen enthalten sowohl statische als auch dynamische Methoden • Scala teilt die Methoden auf in dynamische in der Klasse und statische, die im sog. companion object gleichen Namens stehen • Beide müssen in dieselbe Datei, eines von beiden darf fehlen • Wenn nur object ⇒ Singleton. object A { def staticmethod(arg:Int):Int = { 2 * arg } } class A { val field = 5 def dynamicmethod(arg:Int):Int = { this.field + arg} } 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 242 / 300 Scala: Klassendefinitionen (2) • Java Klassen haben immer einen (häufig nutzlosen) nullstelligen Konstruktor • meist muss einer definiert werden, der einfach nur Felder initialisiert. • In Scala stattdessen: Felder, die im Konstruktor initialisiert werden sollen, als Argumente der Klasse. Kein nullstelliger Konstruktor. class A(val field1:Int, var field2:Int) {...} ergibt als möglichen Konstruktoraufruf new A(3,5). Ein nullstelliger Konstruktoraufruf ist nicht möglich. 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 243 / 300 Scala: Abstrakte Datentypen (1) Scala unterstützt freie Datentypen analog zu KIV-Specs. Beispiel: arithmetische Ausdrücke. sealed abstract classed AExp case class Binop(val e1:AExp, val str:String, val e2:AExp) extends AExp case class Unop(val str:String, val e:AExp) extends AExp case class Val(val v:Int) extends AExp case class Var(val id:String) extends AExp erlaubt zu schreiben: Binop(Var("x"),"+",Unop("-",Val(4)))) 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 244 / 300 Scala: Abstrakte Datentypen (2) • Ein abstrakter Datentyp besteht aus einer abstrakten (Summen-)Klasse (AExp) und einer Anzahl Unterklassen (Summanden; hier Binop, Unop, Val, Id) • Alle Klassen werden zusammen in die Datei AExp der Summenklasse geschrieben • sealed ⇒ keine (weiteren) Unterklassen in anderen Dateien • case class: erlaubt Konstruktoraufruf ohne Schlüsselwort new (und Pattern matching; siehe später) • Die Felder sind meist nicht schreibbar (val), sie werden nur vom Konstruktor initialisiert (“immutable” datatype; entspricht algebraischen Datentypen). • Gleichheit (i.e. ==) vergleicht case classes strukturell (nicht wie in Java auf Referenz-Gleichheit). 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 245 / 300 Scala: Listen (1) Listen sind ein vordefinierter freier Datentyp sealed abstract class List[+A] case class ::[A](val head:A, tail:List[A]) extends List[A] case object Nil extends List[Nothing] erlaubt Konstruktion mit 1::2::Nil Bem: eigentlich ::(1,::(2::Nil)), aber “::” ist auch eine Infixfunktion auf Listen, die mit Doppelpunkt endet. Solche Infixfunktionen drehen die Argumentreihenfolge um! Alternativ: List(1,2) (durch Aufruf der statischen Methode List mit variabler Argumentzahl im companion object der Klasse List) 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 246 / 300 Scala: Listen (2) • Listen erlauben kein Überschreiben des head oder tail. • Listen sind kovariant (das bedeuet das + vor dem Typparameter): jedes x:List[Int] ist auch ein x:List[Any] jedes x:List[Binop] ist auch ein x:List[AExp] • allgemein x:List[type1] ist Subtyp von x:List[type2] falls type1 ein Subtyp (Unterklasse) von type2 ist. • Arrays sind dagegen nicht kovariant (weil modifizierbar) • Nothing ist der leere Typ ohne jedes Element (Subtyp von jedem Typ). Nil ist deshalb vom Typ List[type] für jeden Typ type • Listen haben viele vordefinierte Funktionen, u.a. ++ (append), x(i) (get für das i-te Element, 0-basiert); siehe scaladoc 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 247 / 300 Scala: Pattern Matching • Scala erlaubt Pattern Matching für ADTs • match verallgemeinert Java’s switch. • Methode allids (in Klasse AExp) sammelt alle vorkommenden Variablen des Ausdrucks • Funktion ++ hängt Listen aneinander. • Ein Underscore steht für wildcard (beliebig) def allids:List[String] = { this match { case Var(id) => List(id) case Binop(e1, ,e2) => e1.allids ++ e2.allids case Unop( ,e0) => allids(e0) case Val( ) => Nil } } Binop(Var(”x”),”+”,Val(4)).allids ergibt List(”x”) 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 248 / 300 Scala: Higher-Order Funktionen • Scala erlaubt Higher-Order Functions, i.e. Funktionen, die Funktionen als Argumente bekommen. • Funktionstypen werden A => B geschrieben. • Beispiel: Funktion map (in Klasse List[A] definiert) wendet eine Funktion f auf alle Listenelemente an def map[B](f:A => B):List[B] = { this match { Nil => Nil x :: xs => f(x) :: xs.map(f) }} oder alternativ: def map[B](f:A => B):List[B] = { if (isEmpty) Nil else f(head) :: tail.map(f) } 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 249 / 300 Scala: lambda-Abstraktionen • Das Funktionsargument von map kann eine Funktion sein, die mit def definiert wurde • Da die Funktion meist nur einmal verwendet wird, gibt es auch die Möglichkeit, sie als lambda-Abstraktion anzugeben • x:Int => x + 1 ist die Funktion, die eins addiert • (x:Int,y:Int) => x + y ist die Additionsfunktion • Verwendung z.B mit map: List(1,2,3).map((x:Int) => 2 * x) ergibt List(2,4,6) • Kurzschreibweise: e(_) statt (x:Int => e(x)) (manchmal ist Typangabe notwendig: e(_:Int) • Beispiel: List((1,2),(4,5)).map(_._1) == List(1,4) (mit Selektor . 1 für Paare) 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 250 / 300 Scala: Datentyp Map • Eine Map bildet endlich viele Schlüssel (keys) auf Werte (values) ab. • Analog zu Liste von Paaren, aber keine doppelten Schlüssel • Paare kann man sowohl mit (a,b) als auch mit a -> b konstruieren. • Konstruktion aus Paaren mit Map("x" -> 3, "y" -> 4) • Das letzte zählt: Map("x" -> 3, "x" -> 4) == Map("x" -> 4) • Zugriff über Map("x" -> 3, "y" -> 4)("x") ergibt 3 • Addieren: Map("x" -> 3) + ("y" -> 4) ergibt Map("x" -> 3, "y" -> 4)("x") • Löschen mit Map("x" -> 3, "y" -> 4) - "x" gibt Map("y" -> 4) 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 251 / 300 Scala: viele weitere Features Scala hat noch viele andere Features, auf die wir hier nicht eingehen: • lazy Funktionen und Felder • interfaces erweitert scala zu traits. Diese dürfen auch Code definieren. • implizite Argumente • selbstdefiniertes Pattern Matching • erweiterte generische Syntax für Schleifen • ... 17. Juni 2013 G. Schellhorn, D. Haneberg: Formale Methoden im Software Engineering 252 / 300