Mappen und Falten: Origami für Anfänger


Bisher war in meinem Scala-Blog noch kein Fitzelchen Scala zu sehen, und jetzt kommt auch gleich mein erstes kleines Problem: Der Syntax-Highlighter bei wordpress.com unterstützt kein Scala (das letzte SyntaxHighlighter-Plugin allerdings sehr wohl). Ich habe deshalb ganz lieb bei WordPress um Aktualisierung gebeten, und Heather vom Support hat ganz lieb geantwortet, dass sie es auf die grooooße Liste gesetzt hat. Warten wir ab und leben solange mit java-gehighlightetem Scala. Deshalb hier auch eher leichte Kost mit kurzen Schnipseln. Aber genug der Vorrede…

Scala besitzt wie Java Listen, Sets und Maps, allerdings gleich doppelt: Veränderlich und unveränderlich. Allerdings sind im Gegensatz zu Java die unveränderlichen Collections der „Standard“. Wie man sich leicht denken kann, unterscheidet sich deren Arbeitsweise erheblich von der der Java-Versionen, denn die Devise heißt jetzt „transformieren statt mutieren“.

Ich will hier die fünf mit Abstand wichtigsten Methoden der Scala-Collections behandeln: foreach, map, flatMap, reduce[Left/Right], fold[Left/Right]. Auch wenn das nur ein grober Überblick im Schnelldurchlauf sein kann, denke ich doch, dass ich das generelle „Flair“ des Konzepts vermitteln kann.

foreach
…unterscheidet sich kaum von ähnlichen Konstrukten in Skriptsprachen, und ist auch ziemlich eng mit Javas erweiterter for-Schleife verwandt. foreach führt eine Operation auf jedem Element der Liste aus, ein eventuelles „Ergebnis“ wird ignoriert. Ein typisches Beispiel sind I/O-Operationen:

List(1,2,3,4,5).foreach(x => println(x*x))

Ausgabe:
1
4
9
16
25

map
…ist meiner Meinung nach das „Herzstück“ der Scala Collections: Es transformiert eine Collection eines Types in eine Collection eines beliebigen anderen Typs. Angenommen obiger Code hätte eine String-Liste als Ausgangspunkt, dann könnte man schreiben:

List("1","2","3","4","5").map(_.toInt).foreach(x => println(x*x))

oder, wenn man „sofort“ die Quadrate berechnen will:

List("1","2","3","4","5").map(x => x.toInt * x.toInt).foreach(println)

flatMap
… funktioniert wie map, kommt aber bei „geschachtelten“ Konstruktionen (etwa eine Liste von Sets oder ein Set von Options) zum Einsatz. Zusätzlich zur Transformation wird die Datenstruktur auch noch „geplättet“.

List(Set("1","2","3"),Set("4","5")).flatMap(_.map(_.toInt)).foreach(x => println(x*x))

Das map wandelt unsere String-Sets zu Int-Sets um. flatMap macht dann diese Int-Sets „platt“, das heißt aus der Liste von Sets wird eine Liste von Ints. Danach drucken wir die Quadrate aus wie gehabt.

reduceLeft und reduceRight
… „dampft“ eine Collection „ein“, indem immer wieder dieselbe Operation angewandt wird (wobei man sich aussuchen kann, ob man links oder rechst anfängt)

println(List(1,2,3,4,5).reduceLeft((x,y) => x*y))

Ausgabe: 120
Die Rechnung läuft so ab: println((((1*2)*3)*4)*5)
Natürlich spielt -Left oder -Right bei kommutativen Operationen wie der Multiplikation keine Rolle (außer bezüglich der Performance).
Man kann obiges Beispiel übrigens auch kürzer schreiben als

println(List(1,2,3,4,5).reduceLeft(_*_))

Neben Summen und Differenzen ist auch die Minimum- oder Maximumsuche eine typische Anwendung:

println(List(2,5,3,1,4).reduceLeft(_ max _))

Ausgabe: 5

foldLeft und foldRight
… ist eng verwandt mit den reduce-Methoden. Der Unterschied ist, dass man einen Startwert vorgeben kann. Hat der Startwert den gleichen Typ wie die Collection, funktionieren beide Methoden fast identisch (nur halt foldLeft/foldRight mit einem zusätzlichen Wert). Interessant wird es, wenn sich der Typ unterscheidet, denn dann wird dieser neue Typ „durchgereicht“. Das Verfahren eignet sich für alle möglichen Aufgaben, die irgendwie mit „Daten sammeln“ umschrieben werden können. Hier „sammelt“ z.B. ein StringBuilder unsere Listenwerte auf:

println(List(1,2,3,4,5).foldLeft(new StringBuilder)(_.append(_)))

Ausgabe: 12345
Oder von rechts (jetzt stimmt die Reihenfolge unserer Parameter nicht mehr, so dass sie „abgekürzte“ Schreibweise mit den Unterstrichen nicht möglich ist):

println(List(1,2,3,4,5).foldRight(new StringBuilder)((i,sb) => sb.append(i)))

Ausgabe: 54321
Die zwei Argumentlisten von foldLeft/foldRight wirken erst einmal befremdlich, aber es gibt einen guten Grund dafür und man gewöhnt sich daran. Ich würde empfehlen, das erst einmal einfach so „hinzunehmen“, die Neugierigen seien auf das Stichwort „Currying“ verwiesen (was weniger mit Kochkunst als mit dem Mathematiker und Logiker Haskell Curry zu tun hat, dem auch der Sprache Haskell ihren Namen verdankt).

Diesen Herbst soll übrigens Scala 2.8 herauskommen, in dem u.a. die Collections völlig überarbeitet werden sollen. Allerdings sind obige Schnipsel so elementar, dass sie aller Wahrscheinlichkeit nach auch dort laufen werden.

Ich hoffe, dass ich einen einigermaßen verständlichen ersten Einblick vermitteln konnte. Man kann übrigens ganz einfach und ohne Installation online auf Simply Scala mit solchen Snippets herumspielen. Dadurch bekommt man schnell ein Gefühl für die Arbeitsweise – und Spaß macht es auch 🙂

Advertisements

2 Gedanken zu “Mappen und Falten: Origami für Anfänger

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