Kleine Sachen mit Python

Eine 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 (po­si­ti­ven) Zahl durch die Kom­po­si­tion zweier Qua­drat­wur­zel-Funk­tio­nen be­rechnet 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 zu­sam­men, hier im Beispiel für die zwei­te und die vierte Wurtel der Zahl 2:

21/2 = [-1.414214, 1.414214]

21/4 =
[-1.189207i, -1.189207, 1.189207, 1.189207i]

Die Listen-Monade erlaubt es mir nun, solche mehrwertigen Funktionen zu­sam­men 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 Funk­tio­nen mBind und mUnit de­fi­niert.

mUnit ist die Einheitsfunktion der Mo­na­de, der Wert wird einfach in eine Liste gepackt.

mBind erwartet als Ein­gangs­pa­ra­me­ter 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 ge­nannt) lassen sich eigene Fun­kti­ons­na­men 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 ge­neig­ten Leser.

Entschlüsseln Sie bitte die Funk­ti­ons­wei­se der Funktion in der rech­ten 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ück­ga­be­wer­ten zu einem Zweiertupel verschnürt werden, welches nun der Rück­ga­be­wert ist. Allerdings passen nun Rückgabewerte und Eingangswerte nicht mehr zusammen: Es muss eine Brücke gebaut, die Funktion mit ihrem Ein­gangs­wert 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 Zwi­schen­er­geb­nis. 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ög­lich­kei­ten zu bieten scheint. Ich habe ein wenig im Umfeld der Mit­schrift-Mo­na­de 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 al­ler­ein­fach­ste aller Monaden:

Hier wird im Fehlerfall einfach ein None zurückgegeben, das dann wei­ter­ge­reicht wird.

Es ist ein Leichtes, hier im Feh­ler­fall in einem Tupel noch Mel­dun­gen mitzugeben.

Die Anwendung im trivialen Bei­spiel:


 
 

Weitere Monadenarten


Die Lese-Monade

Die Lese-Monade wird verwendet, wenn Funktionen lesend auf einen gemeinsamen Zustand zugreifen müssen, etwa auf die Um­ge­bungs­va­riab­len 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


mitschreib-monade-py.html

mitschreib-pymonad-py.html

listen-monade-py.html

vielleicht-monade-py.html

mitschreib-monade.py

mitschreib-pymonad.py

listen-monade.py

vielleicht-monade.py




© 2015 Bernd Ragutt
Alle Rechte vorbehalten
 ... hier kann man hinschreiben letzte Änderung: 6. Februar 2016
Kruschtkiste