Benutzer-Werkzeuge

Webseiten-Werkzeuge


themen:software:paradigmenwechsel

Paradigmenwechsel

Aufbruch

Seit den 50er Jahren des zwanzigsten Jahrhunderts existieren universell programmierbare Computer, die mehr als reine Rechenmaschinen sind und die seither auch für Aufgaben eingesetzt werden, die unter Oberbegriffen wie zuerst Datenverarbeitung und heute Informationstechnologie zusammengefaßt werden. Die durch Halbleitertechnik und Mikroelektronik beispiellos beschleunigte Weiterentwicklung der Hardware weckte aber bereits in den 60er Jahren überzogene Erwartungen an Computer, die diese bis heute nicht erfüllen konnten.

In SF- und Agentenfilmen bis hin zu alltäglichen Krimiserien unterhalten sich die Protagonisten mit Computern in natürlicher Sprache - ja gerade in Zeiten, wo noch nicht jeder direkten Kontakt zu Computern hatte, waren Computer Archetypen für Perfektion bis hin zu einer Gott gleichen Unfehlbarkeit. Diese Einstellung zu Computern änderte sich erst durch deren Popularisierung als Homecomputer oder Personal Computer, weil nun die meisten Menschen Erfahrungen mit fehlerhafter, unflexibler oder einfach nur unergonomischer Software machten.

Die immer noch wenigen Spezialisten, welche Computersoftware entwickeln, mußten schon früh feststellen, daß die Lösung vermeintlich trivialer Aufgaben sehr schnell zu Code mit einer kaum noch beherschbaren Komplexität und in der Folge vielen Fehlern führt - vor allem wenn nicht alle Ausnahmesituationen und Randbedingungen bei der Problemanalyse bis ins vermeintlich unwichtigste Detail berücksichtigt wird. Softwareentwicklung war und ist bis heute eine sehr zeit- und kosten- intensive Profession, auch wenn das nicht im Entwicklungsprozess direkt Beteiligten bis heute kaum vermittelbar ist.

Wunderwaffen

Das Unverständnis bezüglich der im Vergleich zur Evolution der Hardware sehr zögerlichen Fortschritte im Software-Umfeld wird noch dadurch vermehrt, das alle paar Jahre ein neuer Prophet einen neuen Königsweg predigt und auch gleich einige vermeintliche Wunderwaffen in die Schlacht wirft. Ob strukturierte, objektorientierte, funktionale, aspektorientierte oder deklarative Programmierung, relationale oder Objektdatenbanken, vierte Generation-Sprachen, Expertensysteme, UML, XML, SOA, Virtualisierung, Web 2.0, Cloud, Scrum, Extreme Programming - der alljährliche Hype verspricht sehr viel und führt schnell zur Ernüchterung, um Platz für den nächsten Hype zu machen. Jeder kleine Fortschritt wird vom Erfinder oder seinen Vermarktern gerne verbal zum Paradigmenwechsel hochstilisiert und von Teilen der Entwicklergemeinde euphorisch aufgegriffen, während andere sich - vielleicht aufgrund genügend schlechter Erfahrungen - gegen jede „neue Mode“ sperren.

Nun möchte ich nicht abstreiten, daß von fast jedem Hype ein paar nützliche Werkzeuge in der Werkzeugkiste jedes sich konsequent weiter bildenden Softwareentwicklers landen, aber wirkliche Paradigmenwechsel erfordern vor allem einen Prozess des Umdenkens und Umlernens und das braucht viel Zeit und Erfahrung - Mißerfolge inklusive. Allzu leicht wird ein neues Werkzeug einfach wie bereits Altbekanntes verwendet und die Wunderwaffe wird dabei stumpf. Begünstigt wird dieses Fehlverhalten meist dadurch, daß das neue Werkzeug „kompatibel“ zu einem vorhandenen gemacht wird, wobei gleich Kompromisse mit eingebaut werden, welche die entscheidenden Merkmale der neuen Technologie von vorn herein schwächen oder aushebeln.

Ein Fortran- oder BASIC-Programmierer kann heute immer noch die meisten Programmiersprachen nutzen, indem er nur ein bißchen neue Syntax lernt und so weiter wurstelt wie immer schon. Er benutzt überwiegend globale Variablen, Prozeduren ohne Parameterübergabe und Rückgabewerte, verwendet vielleicht ein paar Bibliotheksobjekte ohne die Entwurfsmuster dahinter zu verstehen, hat aber keine Notwendigkeit, sich selbst mit abstrakten Klassen, Entwurfsmustern, Funktionen höherer Ordnung, Schnittstellen oder Design by Contract zu beschäftigen. Wenn Sprachen wie C++, Java, Perl, Javascript und sogar Ruby ihm das einfach so durchgehen lassen, wie soll er da überhaupt merken, was ihm an Wissen fehlt, häufig bemerkt er seine uneffektive Arbeitsweise schon deshalb gar nicht, weil er durch diese so überlastet ist, daß er keine Zeit mehr dafür erübrigen kann, etwas neues zu lernen. Komplett falsche Vorstellungen der Projektverantwortlichen von Methoden wie „agiler Softwarentwicklung“ tun ein Übriges, um die gewohnt schlechte Software hervor zu bringen, an die sich viele resignierte Nutzer inzwischen gewöhnt haben.

Sicher ist, das Wunderwaffen zum Arsenal der Marketing-Profis, Fanatiker oder Phantasten gehören, sicher ist aber auch, daß jedes Werkzeug nur so gut sein kann, wie man in der Lage ist, das rechte Werkzeug am rechten Ort richtig einzusetzen.

Alter Wein in neuen Schläuchen

Gerade jüngere Softwareentwickler kennen vielleicht nicht die Vorgeschichte, die zu den Werkzeugen führte, die sie heute - ohne viel darüber nachzudenken - einsetzen. Die älteste höhere Programmiersprache ist Fortran und ihre wichtigsten Merkmale prägen bis heute die meisten populären Programmiersprachen, denn Fortran ist eine imperative Programmiersprache und bis heute existieren nur wenige Programmiersprachen, welche die mit imperativer Programmierung einhergehenden Probleme wirksam verhindern. Ein Grund dafür mag die größere Maschinennähe imperativer Sprachen sein, die zu performanterem Code führt - Fortran hat sich deshalb bis heute als Sprache für rechenintensive Aufgaben gehalten.

Nur wenige wissen, das schon 1958 recht zeitnah zu Fortran eine Programmiersprache namens LISP entstand, die zwar zu Zeiten der ersten KI-Hype auch bereits selbst als Wunderwaffe gehandelt wurde, aber ansonsten nie sehr weit aus den Elfenbeintürmen theoretischer Informatiker beim MIT heraus drang. Interessant ist aber, daß ein Großteil der nachfolgenden Hypes und Wunderwaffen bis heute ihren Ursprung in LISP haben bzw. sich mit der wohl minimalistischsten Syntax von allen Programmiersprachen in LISP zumindest nachbilden lassen. Ob höhere Funktionen, Closures, Objektorientierung, Aspektorientierung, Expertensysteme, Domain Specific Languages (DSL), all das wurde mit LISP meist Jahrzehnte vor dem Ausprägung einer entsprechenden „Mode“ schon umgesetzt.

Wer schon einmal LISP-Code gesehen hat und angesichts der tiefen Verschachtelung von Code in einem Wald aus Klammern dessen Lesbarkeit bemängelt, sollte sich einfach nur die XML- Dateien anschauen, die er vielleicht gerade schreibt- sind die etwa viel lesbarer? Wenn man XML komplett durch LISP ersetzen würde, hätte man keinerlei Nachteile, aber einen enormen Gewinn an Flexibilität und erheblich weit reichende Möglichkeiten und vielleicht sogar ein bißchen besser lesbaren Code.

Wer nun meint, ich schreibe Stuß, weil XML keine Programmiersprache, sondern eine mit einer Programmiersprache nicht vergleichbare Markup-Language sei, sollte sich fragen, wieso es dann solche XML-Abartigkeiten wie XSL und WPF gibt, in welche man mit Gewalt Kontrollstrukturen hinein zwingt, die eigentlich nicht in eine reine „Datenbeschreibungssprache“ gehören. Diese Kritiker sollten auch bereits bemerkt haben, daß XML in vielen Bereichen immer mehr durch JSON verdrängt wird, was ja nur eine Untermenge der Programmiersprache Javascript ist - einer trotz C-Syntax zumindest hinsichtlich ihrer Flexibilität zu LISP nicht ganz unähnlichen Sprache.

Bezeichnend ist jedenfalls auch, das in der in einer Entwicklungskrise erstarrten Java-Welt mit der als neue Wunderwaffe gehandelten Sprache Clojure ein waschechter LISP-Dialekt auftaucht. Der neue Hype „funktionale Programmierung“ ist ein absolut uraltes Kernmerkmal von LISP, wobei allerdings LISP zumindest noch die imperative Programmierung mit ihren globalen Variablen und fest vordefinierten Kontrollstrukturen zu läßt- also nicht so akademisch verbrämt daher kommt, wie manch pure funktionale Sprache.

Pure funktionale Sprachen wie Haskell werden es wie auch die puren objektorientierten Sprachen der Smalltalk-Generation sehr schwer haben, an Popularität zu gewinnen, weil damit C-, Basic- oder Perl/PHP- Programmierer nicht mehr ihren imperativen Schweinkram durchziehen können. Das weiß auch Microsoft und hat mit der neuen auf Objective Caml basierenden funktionalen Programmiersprache F# imperativen Programmierern noch ihre ausgetretenen Visual-Basic- Pfade offen gelassen. Microsoft's Entscheidung, in VisualStudio 2010 eine so „exotische“ Sprache wie F# zu integrieren hat auch mich erst überrascht. Nach dem Einbau der Dynamic Language Runtime und der Jahre andauernden Förderung der Projekte IronPython und IronRuby hätte auch ich eher diese Sprachen in Microsoft's aktueller Entwicklungsumgebung erwartet, aber ich kann Microsoft's Entscheidung inzwischen verstehen - auch wenn es sicher recht lange dauern wird, bis Enwickler F# effektiv einsetzen können - es sei denn sie haben bereits viel Erfahrung mit einer funktionalen Programmiersprache oder LISP. F# hat aber gegenüber Clojure bei der Java-Konkurrenz sicher auch Performance- Vorteile, weil es als statisch typisierte Sprache dichter an der Hardware ist und der Compiler da einiges optimieren kann. IronPython und IronRuby unterstützen im Unterschied zu der auf Python aufbauenden, aber noch recht unbekannten Sprache Boo nur Duck-Typing und nicht die sicher performantere statische Bindung, die ein Compiler herstellen kann. Mit F# hat Microsoft aber der ebenfalls funktionale Programmierung mit statischer Typisierung/Bindung unterstützenden Sprache Scala etwas entgegen zu setzen, die in der Java-Welt an Popularität gewinnt und als möglicher Nachfolger der an ihre Grenzen gekommenen Sprache Java gehandelt wird.

Der Fluch des Imperativs

Zuerst möchte ich mich bei allen Lesern bedanken, die es vielleicht trotz Verärgerung über mein Herumnörgeln an als bewährt oder zumindest pragmatisch anerkannte Programmiermethoden bis hierher geschafft haben. Hier möchte ich nun die eigentlich schon lange bekannten Probleme aber auch die unwiderlegbaren Vorteile imperativer Programmierung beleuchten. Außerdem möchte ich ein wenig auf all die Heilmittelchen eingehen, mit dem die Probleme seit Jahrzehnten mit mehr oder weniger Erfolg bekämpft werden. Fangen wir mit den Vorteilen an. Es geht nicht ohne imperative Programmierung

Imperative Programmierung kommt der Architektur heute bekannter Hardware - egal ob von Neumann- oder Harvard Architektur - sehr entgegen. Jede statische Variable hat ihre feste, von Compiler oder Assembler definierte Adresse ebenso wie jede Funktion. Dynamisch allokierter Speicher kann bereits bei einem Stapel mit lokalen Variablen Ärger machen, wenn jener überläuft, von komplizierten, Fragmentierung und mögliche Speicherlecks verursachenden Heap-Mechanismen mal ganz abgesehen. Wenn dann noch eine automatische Speicherverwaltung mit einem Garbage-Collektor dazu kommt, reagiert ein Programm nicht mehr deterministisch bzw. das exakte Laufzeitverhalten ist kaum noch vorhersehbar. Deshalb kommt man bei missionskritischen oder recht hardwarenahen Realisierungen um imperative Programmierung nicht immer herum und man muß eventuell auf einen Heap oder sogar einen Stack verzichten und sich mit festen Speicheradressen und Registern begnügen (sicher nicht vergnügen).

Außerdem gibt es exklusive Ressourcen, auf welche mehrere Threads, Prozesse oder Nutzer zugreifen müssen. Das sind häufig Geräte wie ein Drucker, DVD-Brenner oder eine Soundkarte, können aber auch Dateien, Sockets oder Verbindungen zu einem Server, Dienst oder Datenrecord sein. Hier kann man programmieren, wie man will, all diese Sachen haben so etwas wie eine konkrete, feste Adresse, Id, Port oder URL und stellen somit Singularitäten dar. Man kann sich in der zugehörigen Software bemühen wie man will, statische oder globale Variablen oder Singleton-Objekte zu vermeiden, aber wie man den Fakt der Singularität auch bemäntelt, so ein Ding bleibt, was es ist. Solange der den Zustand von so einem Ding nicht mehr ändert oder es nicht konkurrierend geändert werden muß, ist das aber auch noch nicht schlimm.

Funktionale Programmierung baut auf der mathematischen Funktionentheorie auf. Eine Funktion ist etwas, was bei Ausführung mit den gleichen Eingangsbedingungen auch immer das gleiche Ergebnis zurückliefert. Eine Funktion bzw. ein „Getter“ bei objektorientierter Programmierung, deren Ergebnis sich durch äußere Einflüsse oder andere Codeteile über mehrere Aufrufe ändert, ist mit funktionaler Programmierung nicht definierbar - hier gibt es nur Konstanten und keine Variablen - schon gar keine globalen.

Diese Einschränkung der funktionalen gegenüber der imperativen Programmierung dürfte ein Hauptgrund sein, warum fast alle sich funktional nennenden Programmiersprachen immer noch imperative Programmierung, also tatsächlich veränderliche und dazu noch global erreichbare Variablen vorsehen. Der Preis für deren Nutzung ist aber hoch, vor allem wenn mehrere Singularitäten in sich verändernden Beziehungen bei konkurrierenden Zugriff zueinander stehen. Synchronisationsmechanismen wie Semaphore oder Mutex, Warteschlangen, Caches, Record-Locking-Mechanismen, Deadlocks - das sind alles die Komplexität erhöhende und Zuverlässigkeit gefährdende Hindernisse, um die man sich kümmern muß, sobald man es mit statischen Variablen, Singletons oder extern beeinflußten Ressourcen zu tun bekommt.

Das ist Grund genug, sich derartige Hindernisse nicht unnötig in den Weg zu stellen und Sichtbarkeitsbereiche von Variablen und sich verändernde Zustände auf das absolut notwendige Maß zu beschränken.

Kommando zurück!

Ein typisches, namensgebendes Merkmal imperativer Sprachen sind auch fest eingebaute „Statements“, mit denen sämtliche Kontrollstrukturen wie Fallunterscheidungen, Schleifen, Deklarationen, Definitionen und vor allem Wertzuweisungen realisiert werden. Statements stehen Ausdrücke gegenüber, die im Gegensatz zu Statements einen Wert bzw. ein Ergebnis haben und die sich somit auch als Rückgabewert und Aufrufparameter für Funktionen eignen. Funktionale Sprachen kennen keine (oder nur sehr wenige) „Statements“, sondern nur Ausdrücke bzw. Funktionen- das gilt auch für Kontrollstrukturen, die somit Funktionen wie alle anderen sind, wobei der Entwickler durchaus auch eigene Kontrollstrukturen programmieren kann.

Das Problem mit durch die Programmiersprache vorgegebenen Statements ist ihre Unflexibilität und dass ihre Wirkung nur in einem Nebeneffekt (side effect) besteht. Der Nebeneffekt ist etwas für das Programm selbst nichts fassbares, es ist fällt eben kein Ergebnis an, noch nicht einmal über Erfolg oder Nichterfolg der durchgeführten Aktion. Ein Statement erledigt häufig nicht nur eine atomare Aktion, sondern macht einen ganze Folge von Aktionen zu einem atomarem Gebilde, ohne dass das Programm überhaupt eine Rückmeldung bekommt, was da eigentlich passiert oder nicht passiert ist.

Ein einfaches Beispiel ist schon eine simple Wertzuweisung der Art X:=5.0. Bei einer imperativen Sprache bewirkt dies eine Zustandsänderung der Variablen X, als Statement wird kein Ergebnis zurückgeliefert, die hierbei durchgeführte Zustandsänderung ist ein reiner Nebeneffekt. Alle Programmteile die X benutzen, finden danach in X einen anderen Zustand vor, ohne darüber informiert worden zu sein. Das ist dann ein Problem, wenn eine Änderung von X in anderen Programmteilen weitere Maßnahmen erfordern, Kopien des alten X erstellt wurden oder parallel ablaufende Threads auf X zugreifen während es gerade zugewiesen wird- das kann tatsächlich passieren, wenn X beispielsweise mehr als ein Maschinenwort im Speicher belegt, was bei einer Fließkommazahl wahrscheinlich ist.

Wenn das in der Sprache eingebaute Zuweisungs- Statement nicht bereits Thread-Synchronisation, Hooks oder Setter-Funktionen unterstützt, die bei der Zuweisung ausgelöst werden, hat der Entwickler hier eine Menge Arbeit damit Code zu schreiben, welcher auch noch die Lesbarkeit des Programms signifikant verschlechtert.

Wenn die imperative Programmiersprache solch ein Problem mit hinreichend „intelligenten“ Statements lösen will, entstehen viele spezialisierte Statements, welche trotz einer sehr umfangreichen Syntax kaum einen Abstraktionsgewinn bringen und es wird immer durch Statements unberücksichtigte Spezialfälle geben, welche die Sprache für eine Problemstellung schlecht geeignet oder gänzlich unbrauchbar machen.

Wie spezialisierte Statements mehrere atomare Aktionen verquicken, zeigen vor allem auch die in allen imperativen Sprachen vorhandenen Zählschleifen. Hier wird die Aktion der Ablaufsteuerung, also die Schleifensteuerung mit Zustandsänderungen (zählen) auf eine Variable verquickt. Dadurch wird entweder die Brauchbarkeit der Schleife auf Situationen beschränkt, wo solche eine Zählvariable zur Verfügung steht , oder der Enwickler muß sich mit Zählvariablen befassen, die er eigentlich gar nicht braucht. Bei den Standardbibliotheken für C++ und Java hat man das bereits erkannt und Iterator-Interfaces bzw. Iterator-Funktionen eingeführt, welche den Zugriff auf das Folgeelement von der Schleifensteuerung lösen, um damit auch die Iteration von Kollektionen, Dateien, Datensätzen und anderen Ressourcen elegant zu ermöglichen.

First Class Funktionen

Ein Iterator ist ein erster Schritt zu flexiblen, weil selbstgebauten Kontrollstrukturen und der Nutzung von Funktionen als First Class Objects, allerdings ist die Handhabung besonders bei Java sehr umständlich und mit viel Quellcode für Interfaces und Iterator-Implementierungsklasse verbunden. Funktionale oder objektorientierte Sprachen, welche Funktionen bereits als First Class Objects behandeln, erleichtern das Übergeben und Zurückgeben von Funktionen an und aus Funktionen und Methoden beispielsweise durch eine Syntax für Closures Closures sind Funktionen ohne Namen, sie binden den lexikalischen Kontext (also alle Variablen von Funktionen und Methoden die den Aufruf der Closure umgeben sowie übergebene Aufrufparameter an einen Codeblock, der nach Ausführung auch ein Ergebnis - möglicherweise auch eine weitere Funktion - zurückgibt (bei Smalltalk und Ruby werden Closures als Blöcke bezeichnet). Besonders der letzte Fall ist sehr interessant, weil der gesamte Kontext der Closure so lange erhalten bleibt, wie die zurückgegebene Funktion genutzt wird. Dazu jedoch später, betrachten wir erst mal Closures, die einfache Daten zurückgeben.

Bei den meisten Sprachen und Bibliotheken, die Iteratoren kennen (manchmal auch unter Namen wie Iterables, Enumeratoren oder Enumerables), sind für Klassen die Kollektionen, Dateien oder Datensatz-Sammlungen bereitstellen, Methoden wie each, map, find, collect, detect, reject, inject etc. vordefiniert. Diesen Funktionen wird nun eine Funktion bzw. eine Closure/Block übergeben, welche mit jedem Element der Kollektion oder jeden Datensatz aufgerufen wird. Je nach Iterator-Funktion muß die aufgerufene Closure ein Ergebnis zurückgeben; im Falle von dem einer einfachen for- Schleife entsprechendem each gar keines, bei find, detect, reject etc. ein Boolean, weil diese Funktionen Werte aus der verarbeiteten Kollektion herausfiltern, mit den bei map oder collect durch die Closure zurück gegebenen Werten wird eine neue Kollektion aufgebaut, welche der Iterator-Funktion als Ergebnis dient.

Ich will hier nicht ins Detail gehen, aber aufzeigen, wie mit Iterator-Funktionen und Closures hier leicht Schleifensteuerungen gebaut werden können, die kein fest eingebautes Statement bereitstellen kann. Was man dabei vielleicht noch gar nicht gleich erkennt, ist die Möglichkeit einer Parallelverarbeitung. Nirgendwo ist festgelegt, dass eine Iterator-Funktion nur mit einem Thread oder Prozessorkern realisiert ist. Ein Iterator könnte die Verarbeitung der Elemente einer Kollektion prinzipiell auch auf hunderte von Prozessoren in den Rechnern eines Datacenters verteilen. So etwas passiert auch bei Google's legendärem map-reduce Algorithmus, wenn wir eine Suchanfrage starten und das hat letztlich auch die derzeitige Hype der funktionalen Programmierung und den Anfang vom Ende imperativer Programmierung ausgelöst.

Doch wozu soll es - wie oben angedeutet - gut sein, wenn eine Funktion (oder Closure) eine andere Funktion zurück gibt? Wie oben schon gesagt, hat die zurückgegebene Funktion noch Zugang zu allen im Quelltext umgebenden Funktionen (lexikalische Bindung). Diese Daten bleiben für die zurück gelieferte Funktion auch noch erhalten, wenn der Aufruf aller umgebenden Funktionen schon beendet ist. Die zurück gelieferte Funktion kann also noch Stunden später auch ohne jeden Parameter aufgerufen werden, kann aber auf Parameter zugreifen, die Stunden zuvor beim Aufruf an die umgebende Funktion übergeben wurden.

Das ist sehr nützlich, wenn eine Schnittstelle - beispielsweise eine Funktion zur Ereignis-Behandlung wie einem Mausklick - Zugang zu Daten (etwa den Feldern eines Formulars) benötigt, welche an die Schnittstellenfunktion gar nicht übergeben werden. Diese Methode nennt sich Currying (schönfinkeln) nach ihren Erfindern.

Ein weiterere Möglichkeit bei der Rückgabe von Funktionen aus Funktionen ist eine Realisierung des Entwurfsmusters Decorator. Die Sprache Python stellt hierfür sogar die Annotation @decorator in ihrer Syntax bereit. Dekoratoren erlauben mittels der umgebenden Funktion die Ausführung von Code vor oder nach der zurück gelieferten, enthaltenen Funktion. Dies ist eine Art von Aspektorientierung, denn man kann damit Standard-Funktionalität wie Logging, Beschaffung und Freigabe von Ressourcen, Fehlerbehandlung oder Parameterprüfung zu Funktionen hinzufügen, ohne deren Code mit allerlei Verwaltung zu „verschmutzen“, was einerseits der Lesbarkeit entgegen kommt und andererseits viel repetitives und fehleranfälliges Codieren erspart.

Was bei Closures schon auffällt, ist das zur Realisierung der lexikalischen Bindung ein Stack nicht mehr ausreicht, sobald als Ergebnis eine Funktion zurückgegeben wird, die erst nach Beendigung der umgebenden Funktion ausgeführt wird. Blöcke bzw. Closures müssen ihren Kontext anderweitig speichern. Es kann aber auch z.B. Iterator-Funktionen geben, die zwar zwecks Rückgabe verlassen, aber später nach der Rücksprungposition fortgesetzt werden (continuations). Bei der Sprache Python nennt sich so etwas generator function und Werte werden hierbei mit yield statt return zurückgegeben. Solche Generatoren stellen sich dem Entwickler wie Kollektionen dar, können aber auch berechnete oder herunter geladene Werte liefern- der Phantasie des Entwicklers sind da keine Grenzen gesetzt. In Verbindung mit einer der funktionalen Sprache Haskell entlehnten Syntax, den list comprehensions können in Python recht komplexe Iterations-, Selektions- und Filter-Szenarien (map & reduce) sehr kompakt und deklarativ formuliert werden, insbesondere wenn Generatoren diese Daten liefern.

Leider stößt Python wie die meisten imperativ-prozeduralen (wie C) und imperativ-objektorientierten Sprachen (wie Java) durch die Verwendung eines Stacks für Funktionsaufrufe hinsichtlich Continuations, Coroutinen und weitergehenden Möglichkeiten funktionaler Programmierung an Grenzen. Es gibt allerdings auch von Python bereits Realisierungen wie Stackless Python, die Microthread- und Funktions-Kontexte unabhängig von einem Stack speichern wodurch Einschränkungen hinsichtlich Multithreading, bidirektionaler Kommunikation von Funktionen und Serialisierung und Speicherung von laufenden Funktionen mit der Möglichkeit einer Fortsetzung nach erneutem Laden möglich werden.

themen/software/paradigmenwechsel.txt · Zuletzt geändert: 2018/09/21 16:45 (Externe Bearbeitung)