|
Kleine Sachen mit PythonEine Annäherung an Monaden |
|
In der rein funktionalen Programmiersprache Haskell gibt es die Typklasse der Monaden. Die Annäherung an dieses Konzept fiel mir nicht ganz leicht. Python hat ja eine gewisse Nähe zu funktionalen Sprachen. Da lag es nahe, das monadische Dunkel mit den Hilfsmitteln Pythons etwas aufzuhellen. |
Die Listen-Monade
Die mathematische Funktionskomposition verknüpft zwei (oder mehrere) Funktionen in der Weise, dass der Ergebniswert y=f(x) der einen Funktion als Eingangswert g(y) der anderen Funktion dient. |
|
So kann die vierte Wurzel einer (positiven) Zahl durch die Komposition zweier Quadratwurzel-Funktionen berechnet werden: |
oder |
Nun hat aber die Quadratwurzel im allgemeinen zwei Lösungen, etwa 4=2*2=(-2)*(-2), oder allgemeiner: die n-te Wurzel einer komplexen Zahl hat n Lösungen. Die Wurzelfunktionen sind also mehrwertige Funktionen. |
|
Ich fasse die Lösungen in Listen zusammen, hier im Beispiel für die zweite und die vierte Wurtel der Zahl 2: |
21/2 = [-1.414214, 1.414214]
21/4
= |
Die Listen-Monade erlaubt es mir nun, solche mehrwertigen Funktionen zusammen zu setzen, im Kompositionssinne zu verketten und das in einer Weise, dass die Mehrwertigkeit nach außen nicht sichtbar wird, denn diese wird in einer Listenstruktur eingepackt und so nach außen hin versteckt. |
Die Listen-Monade ist durch zwei Funktionen mBind und mUnit definiert. mUnit ist die Einheitsfunktion der Monade, der Wert wird einfach in eine Liste gepackt. mBind erwartet als Eingangsparameter eine Liste vList von Werten und eine mehrwertige Funktion f. |
|
Mehrwertig heißt hier, dass f als Rückgabewert eine Liste mit (einem oder) mehreren Werten ausspuckt. Die Python-Funktion map wendet nun die Funktion f nacheinander auf jeden Wert der Liste vList an und erzeugt eine Liste von Listen. Die Funktion flatten macht daraus wieder eine einfache Liste, im Beispiel flatten([[1,2],[3,4]])=[1,2,3,4]. Und das ist auch schon alles. |
Für den Test und die Demonstration verwende ich die Funktion croot, die die n Lösungen der n-ten Wurzel einer komplexen Zahl berechnet – in Mathematik und in Pythoncode:
Mit der Funktion partial aus dem Python-Modul functools (hier ft genannt) lassen sich eigene Funktionsnamen für spezielle Wurzeln vergeben. Wie sieht nun die Anwendung der Monade aus? Zwei Beispiele, die sechste Wurzel aus der (-1): |
|
|
|
Das Beispiel in der rechten Spalte fasst die Wurzelfunktionen in der Liste fList zusammen. Die Funktion foldWithBind implementiert mit der Funktion reduce eine Faltung, dabei werden die Elemente einer Liste, hier fList, mit Hilfe einer Funktion, hier mBind, zusammengefasst, eben verbunden. Initialwert ist dabei mUnit(x)=[x], das eingepackte x. |
|
Hier eine kleine Hausaufgabe für den geneigten Leser. Entschlüsseln Sie bitte die Funktionsweise der Funktion in der rechten Spalte. Wie sieht der Rückgabewert aus, auf den der Funktionsname ja schon einen Hinweis gibt? |
|
Die Mitschreib-Monade
Angenommen Sie haben eine Reihe von verketteten Funktionen und Sie wollen Zwischenergebnisse mitschreiben lassen. Seiteneffekte sind nicht zugelassen, denn sie arbeiten mit einem funktionalen Ansatz. Was ist zu tun? Die Mitschriften müssen mit den eigentlichen Rückgabewerten zu einem Zweiertupel verschnürt werden, welches nun der Rückgabewert ist. Allerdings passen nun Rückgabewerte und Eingangswerte nicht mehr zusammen: Es muss eine Brücke gebaut, die Funktion mit ihrem Eingangswert muss mit dem neuen Rückgabewert verbunden werden. |
|
Die Funktion mBind erwartet ein Tupel xs und eine Funktion fw, die mitschreibt, und sie gibt ein Tupel zurück. Ich habe hier einmal die Annotationensmöglichkeiten (:tuple und ->tuple) von Python benutzt. mBind berechnet den eigentlichen Funktionswert y=f(x) und baut die Zeichenkette zusammen. Die Einheitsfunktion mUnit der Monade verpackt das Argument in ein Tupel zusammen mit der leeren Zeichenkette. |
|
Die Funktionen mUnit und mBind erfüllen die drei folgenden Axiome, wie es von diesen Monadenfunktionen erwartet wird. |
|
|
Die Funktionen sollen etwas Mitschreiben können, etwa das Zwischenergebnis. Hier einge Möglichkeiten: |
||
|
|
|
Die Funktion fFormat wandelt den übergebenen Wert in eine kurze Zeichenkette um. Die Funktion wSquare kann verkettet werden, sie schreibt aber nur die leere Zeichenkette. Die Funktionsfabrik dLift, ein Dekorator, kann verwendet werden, um Funktionen mit @dLift zu dekorieren, womit das Verhalten der dekorierten Funktion verändert wird – hier wird sie zum Mitschreiben angeregt. Zwei Anwendungsbeispiele: |
||
|
||
Die Mitschriften sind in Hochkommas gestellt, die Werte stehen nach dem Doppelpunkt. |
||
Die Mitschrift-Monade kann etwa beim Testen und beim Logging eingesetzt werden. |
PyMonad Seit kurzem gibt es ein Drittanieter-Modul PyMonad, das reizvolle Möglichkeiten zu bieten scheint. Ich habe ein wenig im Umfeld der Mitschrift-Monade herumgespielt. Die bind-Funktion ist als Infix-Operator >> implementiert, damit lassen sich monadische Funktionen schon recht lesbar verbinden – siehe dazu unten die dritte Code-Zeile, der Ausdruck dort wird von links nach rechts ausgewertet. wAdd ist wie die anderen Funktionen eine mit @pymonad.curry dekorierte Funktion, sie erhält den einen Eingabewert explizit als Funktionsparameter x2, der andere wird über die Verkettung mit dem bind-Operator >> eingebunden. Das Stichwort zu dem Verhalten von wAdd(x2) ist Currying (nach dem britischen Mathematiker Haskell Brooks Curry): |
||
|
Die Vielleicht- oder Maybe-Monade
Der lustige Name? Vielleicht hat die Funktion wurzel(x) ja eine Lösung oder die Division x/y klappt eventuell, vielleicht aber auch nicht. Berechnungen können aus verschiedenen Gründen scheitern, etwa bei einer Division durch die Null. In einfachsten Fall soll eine Kette von Berechnungen scheitern, wenn eine Berechnung in der Kette scheitert. Eine Ausnahme auszulösen ist die eine Sache und widerspricht dem funktionalen Ansatz, die andere Sache ist die Vielleicht-Monade, die man allerdings noch um Fehlermeldungen anreichern sollte – was ich aber hier unterlasse. |
|
Hier die beiden Funktionen für die allereinfachste aller Monaden: Hier wird im Fehlerfall einfach ein None zurückgegeben, das dann weitergereicht wird. Es ist ein Leichtes, hier im Fehlerfall in einem Tupel noch Meldungen mitzugeben. Die Anwendung im trivialen Beispiel: |
|
|
|
Weitere Monadenarten
Die Lese-Monade Die Lese-Monade wird verwendet, wenn Funktionen lesend auf einen gemeinsamen Zustand zugreifen müssen, etwa auf die Umgebungsvariablen eines Webservers. |
Die Zustandsmonade Die Zustandsmonade ist eine Kombination aus Lese- und Mitschrift-Monade. Funktionen können damit lesend und schreibend auf einen gemeinsamen Zustand zugreifen. |
Quellcode für Python 3
|
||
© 2015 Bernd Ragutt Alle Rechte vorbehalten |
letzte Änderung: 6. Februar 2016 Kruschtkiste |
|
|