Monaden sind ein etwas sperriges Konzept, das in rein funktionalen Sprachen wie Haskell u.a. für eine vernünftige Ein-/Ausgabeprogrammierung notwendig ist. Für Scala sind Monaden vielleicht nicht ganz so wichtig, aber auch mit Scala kann man sinnvoll monadisch programmieren – und das Schöne daran: Über die ganze komplizierte mathematische Theorie dahinter muss man sich keine Gedanken machen. Im Gegenteil, wer schonmal ein bisschen Scala programmiert hat, wird dabei vermutlich (unbewusst) auch Monaden genutzt haben.
Was sind diese Monaden? Ein abstrakter Datentyp für verkettbare Berechnungen mit folgenden Eigenschaften:
- Ein Typkonstruktor wie
List[A]
(mit z.B.List[Int]
als Ausprägung). - Eine Funktion, um einen Wert im Typ "einzuwickeln". Das kann der Konstruktor sein, z.B.
new List(1)
bzw.List(1)
, aber häufig wird diese Funktionreturn
,unit
,lift
oderpure
genannt. - Eine Funktion, um die enthaltenen Werte zu verrechnen, aber nach der Berechnung im Typ verpackt zu lassen. Übliche Namen sind
bind
oder (in Scala)flatMap
.
case class MiniMonade[A](wert:A) {Die Mini-Monade soll also genau einen Wert des generischen Typs "A" speichern und verarbeiten können. Listen sind auch Monaden und können entsprechend beliebig viele Werte verarbeiten. In unserer Monade ist zusätzlich noch die Funktion
def flatMap[B](f: A => MiniMonade[B]) = f(wert)
def map[B](f: A => B): MiniMonade[B]
= MiniMonade(f(wert))
}
map
implementiert, damit wir Objekte dieser Klasse in den for-Comprehensions von Scala einsetzen können. Und genau das ist auch der häufigste Anwendungsfall für Monaden in Scala:val m1 = MiniMonade(2)In dieser for-Comprehension werden alle Werte aus den beiden Monaden-Objekten miteinander mit x*y verrechnet und landen anschließend (durch die Magie der Monaden-Funktionen) wieder in einem Monaden-Objekt. Da unsere Objekte jeweils nur einen Wert speichern, wird nur 2*3 berechnet:
val m2 = MiniMonade(3)
val erg = for { x <- m1; y <- m2 } yield x*y;
println( erg )
HeartOfGold:~ much$ scala MiniMonade.scalaDas scheint unnötig viel Aufwand für eine simple Multiplikation zu sein... Aber folgendes Beispiel dürfte – so oder in ähnlicher Form – jeder schonmal programmiert haben, und das ergibt sicher mehr Sinn:
MiniMonade(6)
val zeilen = List(10,20)Andere gebräuchliche monadische Typen sind (neben vielen weiteren) Map und Option, und alle diese Klassen können in einer for-Comprehension kombiniert werden.
val spalten = List(1,2)
val summen = for {z <- zeilen; s <- spalten} yield z+s;
println( summen )
// List(11, 12, 21, 22)
Die Verwendbarkeit in for-Comprehensions ist in Scala sicherlich der wichtigste Grund, warum man geeignete eigene Klassen monadisch machen sollte. Würden wir noch withFilter und foreach implementieren, könnten wir unsere Monade außerdem in weiteren for-Varianten einsetzen.
Beim diesjährigen Herbstcampus werde ich einen Einsteiger-Vortrag (OO-Vorkenntnisse reichen) zu diesem Thema halten.