Funktionale Programmierung

Bei diesem Thema schauen wir uns an, wie Python noch einen anderen Programmierstil unterstützen kann: Funktionale Programmierung (FP). Wie bei der Rekursion handelt es sich um ein echtes Thema für Fortgeschrittene, das du auch im Moment ignorieren kannst,wenn du möchtest. Funktionale Techniken finden ab und zu ihren Nutzen bei der Programmierung und die Befürworter der FP glauben, dass es ein grundsätzlich besserer Weg zur Software-Entwicklung ist.

Was ist Funktionale Programmierung?

Funktional Programmierung sollte nicht mit imperativer (oder prozeduraler) Programmierung verwechselt werden. Auch ist es nicht wie objektorientierte Programmierung. Es ist etwas anderes. Aber nicht radikal, da die Konzepte, die wir untersuchen, vertraute Programmierkonzepte sind, die jedoch auf eine andere Art ausgedrückt werden. Die Philosophie hinter der Art, wie diese Konzepte angewendet werden, um Probleme zu lösen, ist auch etwas anders.

Bei Funktionaler Programmierung dreht sich alles um Ausdrücke. Tatsächlich ist es eine andere Möglichkeit, die FP mit dem Begriff der ausdrucksorientierten Programmierung zu umschreiben, da in FP alles auf einen Ausdruck reduziert wird. Du solltest dich daran erinnern, dass ein Ausdruck eine Ansammlung von Operationen und Variablen ist, die in einem einzigen Wert resultieren. So ist x == 5 ein Boolscher Ausdruck. 5 + (7-Y) ist ein arithmetischer Ausdruck. Und string.search("Hallo Welt", "Hölle") ist ein String-Ausdruck. Das Letzte ist auch ein Funktionsaufruf innerhalb des string - Moduls und, wie wir sehen werden, sind in der FP Funktionen sehr wichtig (Du hast das schon aufgrund des Namens vermutet!).

Funktionen werden in FP als Objekte verwendet. Das bedeutet, dass sie oftmals innerhalb eines Programmes hin und her übergeben werden, in ähnlicher Weise wie andere Variablen. Wir haben davon Beispiele in unseren GUI-Programmen gesehen, wo wir den Namen eine Funktion dem command - Attribut einer Button-Steuerung übergeben haben. Wir verwendeten die Ereignisbehandlungsfunktion als ein Objekt und haben eine Referenz der Funktion dem Button zugewiesen. Diese Idee der Funktionenübergabe innerhalb unseres Programmes ist der Schlüssel zur FP.

Funktionale Programme neigen auch dazu, stark listenorientiert zu sein.

Schließlich versucht FP sich eher auf das Was, anstatt auf das Wie einer Problemlösung zu konzentrieren. Das heißt, ein funktionales Programm sollte eher das zu lösende Problem beschreiben, anstatt das Augenmerk auf den Lösungsmechanismus zu werfen. Es gibt verschiedene Programmiersprachen, die auf diese Richtung abzielen, und die am meisten gebräuchlichste ist Haskell und die Haskell-Website ( www.haskell.org) hat viele Artikel, die sowohl die Philosophie der FP als auch die Sprache Haskell beschreiben. (Meiner persönlichen Meinung nach sind diese Ziele zwar lobenswert, aber von den FP-Befürwortern überbewertet.)

Ein rein funktionales Programm ist durch die Festlegung eines Ausdruckes aufgebaut, die die Absicht des Programmes beschreibt. Jeder Term des Ausdruckes ist ebenfalls eine Aussage über die Charakteristik des Problems (eingekapselt mag ein andere Ausdruck dafür sein) und die Auswertung dieser Terme führen eventuell zu einer Lösung.

Gut, das ist die Theorie. Geht das? Ja, manchmal geht es gut. Für einige Problemfälle ist es eine natürliche und leistungsstarke Technik. Unglücklicherweise erfordert es für viele andere Probleme eine ziemlich abstrakte , stark von mathematischen Prinzipien beeinflusste Denkweise. Der resultierende Code ist oft fern jeder Lesbarkeit für den Laienprogrammierer. Der sich ergebende Code ist auch sehr oft viel kürzer als der vergleichbare imperative Code und viel genauer. Es sind diese letzten Eigenschaften, die viele konventionelle imperative oder objektorientierte Programmierer dazu bewegt haben, sich mit FP zu befassen. Obwohl man diese Art der Programmierung nicht voll in sein Herz schließen kann, gibt es doch einige starke Werkzeuge, die von allen verwendet werden können.

Wie macht Python das?

Python ist mit verschieden Funktionen ausgestattet, die eine funktionale Art der Programmierung ermöglichen. Diese Funktionen sind allesamt praktischer Natur, dahingehend, dass sie in Python äußerst einfach zu schreiben sind. Wichtig ist vor allem zu wissen, dass in Python die Möglichkeit angelegt ist, auf FP-Art programmieren zu können, wenn man dies möchte.

Wie schauen auf einige dieser vorgegebenen Funktionen und sehen, wie sie mit einigen Beispieldatenstrukturen verfahren, die wir definieren als:

schwing = ['Schwein','Schinken','Gewürze']
zahlen = [1,2,3,4,5]

def eier(item): 
    print item
    return item

map(aFunction, aSequence)

Diese Funktion wendet eine Python-Funktion aFunction auf jeden Bestandteil von aSequence an.Der Ausdruck:

L = map(eier, schwing)
print L

ergibt für jeden Bestandteil von schwing eine Ausgabe durch die Funktion eier und eine neue Liste (in diesem Fall identisch mit schwing) wird in L zurückgegeben.

Wir können dasselbe tun, indem wir schreiben:

for i in schwing:
   print i
   L.append(i)
print L

Beachte dennoch, dass die map - Funktion es erlaubt, ohne eingenisteten Codeblock auszukommen. Von diesem Gesichtspunkt aus reduziert dies die Komplexität des Programmes um eine Ebene. Wir werden sehen, dass es bei der FP ein wiederkehrendes Thema ist, die relative Komplexität von Code durch das Eliminieren von Blöcken zu reduzieren.

filter(aFunction, aSequence)

Wie der Name filter ausdrückt, extrahiert diese Funktion jedes Element aus der Sequenz, für die die Funktion wahr ergibt. Stell dir unsere Zahlenliste vor. Wir möchten eine neue Liste mit nur ungeraden Zahlen erzeugen, dann können wir sie so produzieren:

def istUngerade(n): return (n%2 != 0) # verwendet den mod Operator
L = filter(istUngerade, zahlen)
print L

Alternativ können wir schreiben:

def istUngerade(n): return (n%2 != 0)
for i in zahlen:
   if istUngerade(i):
      l.append(i)
print L

Beachte wiederum, dass der konventionelle Code zwei Einzugsebenen erforderlich macht, um das Ergebnis zu erhalten. Wieder ist die ansteigende Menge an Einzügen ein Maß für das Ansteigen der Code-Komplexität.

reduce(aFunction, aSequence)

Die reduce - Funktion ist etwas wenniger vertändlich in ihrer Absicht. Diese Funktion reduziert eine Liste auf einen einzigen Wert durch die Kombination der Elemente über eine unterstützende Funktion. Wir könnten zum Beispiel die Werte einer Liste aufsummieren und den Gesamtwert so zurückgeben:

def add(i,j): return i+j
print reduce(add, zahlen)

Wie vorher können wir das mehr konventionell so erledigen:

L = [] # leere Liste
res = 0
for i in range(len(zahlen)): # verwende Indexierung
    res = res + zahlen[i]
    return res     
print res

Während das in diesem Fall das gleiche Ergebnis erzielt, ist es nicht immer so einfach. Was reduce tatsächlich tut, ist, dass es die unterstützende Funktion aufruft, indem es die beiden ersten Bestandteile der Sequenz übergibt und das zweite Teil durch das Ergebnis ersetzt. Mit anderen Worten, eine genauere Wiedergabe von reduce sähe so aus:

L = zahlen[:] # mache eine Kopie des Originals
while len(L) >= 2:
   i,j = L[0],L[1] # verwend Tuple-Zuweisung
   L = [i+j] + L[2:]
print L[0]

Wieder einmal sehen wir das die FP-Technik die Komplexität des Codes reduziert, dadurch dass es die Verwendung von eingezogenen Codeblöcken vermeidet.

lambda

Eine Eigenschaft die du in den Beispielen bisher bemerkt hast, ist, dass die an FP-Funktionen übergebenen Funktionen zur äußersten Kürze neigen, meist nur eine einzelne Codezeile. Um diese Menge an sehr kleinen Funktionen weiter zu unterstützen, ist Python mit einer weiteren Hilfe zur FP ausgestattet - lambda.

Lambda ist ein Begriff der gewöhnlich auf eine anonyme Funktion angewendet wird; das ist ein Codeblock, der so ausgeführt werden kann, als sei es eine Funktion, aber ohne Namen. Lambdas können in einem Programm beliebig definiert werden, wo ein erlaubter Ausdruck erscheint, das heißt, wir können sie innerhalb unserer FP- Funktionen verwenden.

Ein Lambda sieht so aus:

lambda <eineParameterListe> : <ein codeblock, der die Parameter verwendet>

Somit kann die obige add Function neugeschrieben werden als:

add = lambda i,j: i+j

Und wir können die Definitionszeile , durch das Erzeugen von lambda innerhalb des Aufrufes von reduce vollständig vermeiden, nämlich so:

print reduce(lambda i,j:i+j, zahlen)

Ähnlich können wir dann auch unsere map und filter Beispiele so schreiben:

L = map(lambda i: print i; i, schwing)
print L
L = filter(lambda i: (i%2 != 0), zahlen)
print L

Ander Konstrukte

Obwohl diese Funktionen berechtigterweise sehr nützlich sind, sind sie nicht ausreichend, einen vollständigen FP-Stil in Python zu erzeugen. Die Kontrollstrukturen der Sprache müssten auch verändert werden, oder sogar ersetzt, um sich der FP anzunähern. Ein Weg, dies zu erreichen, ist die Anwendung eines Seiteneffektes, der sich dabei ergibt, wie Python Boolsche Ausdrücke auswertet.

Kurzschlussauswertung (Short Circuit evaluation)

Weil Python die Kurzschlussauswertung von Boolschen Ausdrücken verwendet, können gewisse Eigenschaften dieser Ausdrücke ausgenutzt werden. Wir rekapitulieren über die Kurzschlussauswertung: wenn ein Boolscher Ausdruck ausgewertet wird, startet die Auswertung auf der linken Seite und arbeitet sich nach der rechten Seite vor, hält an, wenn es nicht weiter erforderlich ist, weiter auszuwerten, um das Durchlaufen bis zum ende aufzuhalten.

Nehmen wir einige spezifische Beispiele, um zu sehen, wie die Kurzschlussauswertung arbeitet:

>>> def WAHR:
...   print 'WAHR'
...   return 1 # boolesches TRUE
...   
>>>def FALSCH:
...   print 'FALSCH'
...   return 0 # boolesches FALSCH
...

Zuerst definieren wir zwei Funktionen, welche uns sagen, wenn sie ausgeführt werden und als Wert ihren Namen zurückgeben. Wir verwenden dies jetzt, um zu untersuchen, wie Boolesche Ausdrücke ausgewertet werden:

>>>print WAHR() and FALSE()
WAHR
FALSCH
0
>>>print WAHR() and WAHR()
WAHR
WAHR
1
>>>print FALSCH() and WAHR()
FALSCH
0
>>>print WAHR() or FALSCH()
WAHR
1
>>>print FALSCH() or WAHR()
FALSCH
WAHR
1
>>>print FALSCH() or FALSCH()
FALSCH
FALSCH
0

Beachte, dass nur WENN der erste Teil eines UND - Ausdrucks WAHR ist, dann, und nur dann, wird der zweite Teil ausgewertet. Wenn der erste Teil FALSCH ist, dann wird der zweite Teil nicht ausgewertet, da der Ausdruck als Ganzes nicht wahr sein kann.

Ähnlich in einem ODER - basierten Ausdruck, wenn der erste Teil wahr ist, dann muss der zweite nicht ausgewertet werden, da das Ganze wahr sein muss.

Wir können diese Eigenschaften verwenden, um ein verzweigungsartiges Verhalten zu reproduzieren. Als Beispiel nehmen wir ein Code-Stück wie das Folgende:

if WAHR(): print "Es ist WAHR"
else: print "Es ist FALSCH"

Wir können dies durch ein Konstrukt im FP-Stil ersetzen:

V =  (WAHR() and (print "Es ist WAHR")) or \
     (print "Es ist FALSCH")

Versuche dich durch dieses Beispiel durchzuarbeiten und dann ersetze den WAHR() - Aufruf durch einen FALSCH()- Aufruf. So haben wir durch die Verwendung der Kurzschlussauswertung von Booleschen Ausdrücken einen Weg gefunden, um konventionelle if/else - Anweisungen aus unserem Programm zu eliminieren. Vielleicht erinnerst du dich, dass wir im Kapitel über die Rekusion beobachtet haben, wie eine Rekursion zum Ersetzen eines Schleifen-Konstrukts eingesetzt werden kann. Durch Kombination dieser beiden Effekte können wir alle konventionellen Kontrollstrukturen aus unserem Programm entfernen, sie durch reine Ausdrücke ersetzen. Dies ist ein großer Schritt zur Möglichkeit von Lösungen im reinen FP-Stil.

Schlussfolgerungen

An dieser stelle wirst du dich wohl fragen, was das alles soll? Da wirst du nicht allein sein. Obwohl FP auf Computerwissenschaftler (und oft auch Mathematiker) sehr anziehend wirkt, scheinen die meisten praktischen Programmierer FP-Techniken sehr sparsam zu verwenden und in einer Art Zwitterzustand mit eher traditionelen imperativen Stilen, wo sie sich heimischer fühlen fühlen, vermengen.

Wenn du Operationen für Elemente einer Liste anwenden musst, so dass map, reduce oder filter als der natürlichste Weg zu sein scheinen, um die Lösung auszudrücken, dann verwende sie trotz allem. Manchmal wirst du auch finden, dass eine Rekusion angebrachter ist, als eine konventionelle Schleife. Aber noch seltener wirst du eine Anwendung für die Kurzschlussauswertung finden, anstelle des konventionellen Verfahrens - ausgenommen es ist innerhalb eines Ausdruckes erforderlich. So wie jedes Programmierwerkzeug, gib es nicht her, denn man weiß nie, wozu man es irgendwann einmal benötigt. Und schließlich weißt du, dass es Alternativen gibt!

Es bleibt noch etwas über lambda zu sagen. Es gibt noch einen Bereich außerhalb des Geltungsbereiches von FP, wo lambda einen realen Nutzen hat und das ist für die Ereignisbehandlung in der GUI - Programmierung. Ereignisbehandler sind oft sehr kurze Funktionen, oder sie rufen möglicherweise einfach größere Funktionen mit fest verknüpften Argumentwerten auf. In diesen Fällen kann eine lambda - Funktion als Ereignisbehandler verwendet werden, der die Notwendigkeit der Definition von vielen kleinen individuellen Funktionen und das Auffüllen des Namensraumes mit einer Unmenge nur einmal benötigter Namen vermeidet.Erinnere dich, dass eine Lamda-Anweisung ein Funktionsobjekt zurückgibt. Dieses Funktionsobjekt ist dasjenige, das an das Widget übergeben wird und wird aufgerufen, wenn das Eereignis eintritt. Wenn du dir vergegnwärtigst, wie ein Button-Widget in Tkinter definiert ist, dann wird ein lambda so erscheinen:

b = Button(parent, text="Drück mich", 
           command = lambda : print "Ich wurde gedrückt!")
b.pack()

oder verwende die bind-Technik, die ein Ereignisobjekt als ein Argument sendet:

b = Button(parent, text="Drück mich")
b.bind(, lambda ev : print "Gedrückt!")

Gut, das war dann wirklich alles zur Funktionalen Programmierung. Es gibt noch viele andere Quellen, wenn du tiefer einsteigen möchtest, einige sind unten aufgelistet.

Andere Quellen

Fall jemand eine gute Referenz kennt, sollte er mir eine E-Mail zukommen lassen.


Zurück Weiter Inhalt
 

Im Falle von Fragen oder hinweisen sende eine Mail in englisch an alan.gauld@yahoo.co.uk oder auf deutsch an bup.schaefer@freenet.de