@pixewakb: Ja, manchmal benutze ich sowas. Ich bemühe mich, es nicht zu übertreiben mit dem
funktionalen Stil. Sonst kann ich gleich in Scheme oder Haskell programmieren. Im Fall von TomTomTom, sofern ich ihn richtig verstanden habe, bietet sich Funktionskomposition allerdings an.
Man könnte es auch so programmieren, vielleicht wird es dann deutlicher, wie es funktioniert:
Code: Alles auswählen
>>> value = 7
>>> for func in add3, mul2, str, mul2, int, add3:
... value = func(value)
...
>>> print value
2023
Zum Vergleich:
Code: Alles auswählen
>>> add3(7)
10
>>> mul2(10)
20
>>> str(20)
'20'
>>> mul2('20')
'2020'
>>> int('2020')
2020
>>> add3(2020)
2023
Andererseits hat
compose() einige nützliche Eigenschaften. Nehmen wir an, wir haben folgende Definitionen:
Code: Alles auswählen
def compose_two(f, g):
def composed_two(x):
y = f(x)
z = g(y)
return z
return composed_two
def identity(x):
return x
Damit haben wir bereits die Menge der einstelligen Python-Funktionen zum
Monoid gemacht. Monoide kennt jeder, der in der Schule Mathematik hatte. Allerdings wurde es nicht Monoid genannt. Es wurde einfach als ein paar anscheinend unzusammenhängende Gesetze gelehrt. Hier das Assoziativgesetz für die Addition und die Multiplikation:
Wenn man statt der Zeichen + und * die
add() und
mul() Funktionen aus dem
operator Modul verwendet, dann sieht es so aus:
Code: Alles auswählen
from operator import add, mul
add(a, add(b, c)) == add(add(a, b), c)
mul(a, mul(b, c)) == mul(mul(a, b), c)
Und hier zum Vergleich für einstellige Funktionen f, g, h:
Code: Alles auswählen
compose_two(f, compose_two(g, h)) == compose_two(compose_two(f, g), h)
Und in der Pythonshell kann man es ausprobieren:
Code: Alles auswählen
>>> def mul2(v):
... return v * 2
...
>>> def add3(v):
... return v + 3
...
>>> def sub5(v):
... return v - 5
...
>>> func1 = compose_two(compose_two(mul2, add3), sub5)
>>> func2 = compose_two(mul2, compose_two(add3, sub5))
>>> func1(12)
22
>>> func2(12)
22
>>> sub5(add3(mul2(12))) # zum Vergleich
22
Aus der Mathematik kennt man auch neutrale Elemente. Für die Addition ist das die 0 und für die Multiplikation die 1. Dabei gelten diese Gesetze:
Oder in funktionaler Schreibweise:
Code: Alles auswählen
add(a, 0) == add(0, a) == a
mul(a, 1) == mul(1, a) == a
Und für unsere einstelligen Funktionen gibt es sowas ebenso, da ist das neutrale Element
identity():
Code: Alles auswählen
compose_two(f, identity) == compose_two(identity, f) == f
In der Pythonshell:
Code: Alles auswählen
>>> f1 = compose_two(add3, identity)
>>> f2 = compose_two(identity, add3)
>>> f1(12)
15
>>> f2(12)
15
>>> add3(12)
15
Blöd ist jetzt nur, dass man mit
compose_two() nur zwei Funktionen komponieren kann. Für drei Funktionen könnte man sich definieren:
Code: Alles auswählen
def compose_three(f, g, h):
def composed_three(x):
y = f(x)
z = g(y)
u = h(z)
return u
return composed_three
Und für vier, fünf, ... Funktionen kann man sich ebenfalls compose-Funktionen schreiben. Nun gibt es aber in Python die Möglichkeit, beliebig viele Argumente an eine Funktion zu übergeben:
Code: Alles auswählen
>>> def foo(*args):
... for a in args:
... print(a)
...
>>> foo(1, 2, 3)
1
2
3
>>> foo('hallo', 'huhu')
hallo
huhu
Solche Funktionen nennt man
variadisch. Damit kann man nun eine allgemeine compose() Funktion schreiben:
Code: Alles auswählen
def compose(*fs):
def composed(x):
for f in fs:
x = f(x)
return x
return composed
Womit wir wieder am Anfang wären.
Ich schrieb, dass diese Funktion einige nützliche Eigenschaften hat. Man kann damit zB.
compose_xxx() und die Identitätsfunktion definieren:
Code: Alles auswählen
>>> def compose_two(f, g):
... return compose(f, g)
...
>>> def compose_three(f, g, h):
... return compose(f, g, h)
...
>>> func = compose_three(mul2, add3, sub5)
>>> func(12)
22
>>> identity = compose()
>>> identity(12)
12
>>> identity('hallo')
'hallo'