Wie löscht man in Funktionaler Programmierung Passworte und Schlüssel?
Wo ich doch (anlässlich der Kritik an Scala) sowieso gerade dabei bin, auf Funktionaler Programmierung herumzuhacken, kann ich gleich noch nachlegen. Mal so aus IT-Sicherheits-Sicht.
Grundsätzlich habe ich ja gar nichts gegen Funktionale Programmierung, finde ich ja in vielerlei Hinsicht gut und mache es selbst ab und zu. Was mich stört sind die Spinner aus dem akademischen Bereich, die Funktionales Programmieren als Königsweg und einzig saubere Programmierung ansehen. (Für die, die das jetzt nicht verstanden haben, ein einfaches Analogon: Ich mag Pizza sehr und habe überhaupt nichts gegen Pizza einzuwenden. Trotzdem esse ich sie eher selten und nur, wenn ich wirklich Appetit darauf habe. Weil es auch andere gute Gerichte gibt. Und würde jeden für einen Spinner halten, der predigt, nur noch Pizza zu essen.)
Entgegen anderslautender Interpretationen meiner Blog- und anderen Kommentare zum Thema bin ich einzelnen Konzepten der Funktionalen Programmierung nicht abgeneigt, wo sie passen. Im Gegenteil, einen Aspekt der Funktionalen Programmmierung (genauer gesagt einen Aspekt, den ich persönlich in der Funktionalen Programmierung für äußerst wichtig und – wenn er eingesetzt wird – für nützlich halte, den ich seltsamerweise aber gerade bei den Verfechtern der funktionalen Programmierung fast nie sehe und finde) halte ich sogar für besonders wichtig, wenn es um IT-Sicherheit geht: Exceptions.
Es gibt einen sehr verbreiteten (und historisch gewachsenen) Programmierstil, Fehlerzustände und Ergebnisse außerhalb der Spezifikation dadurch als Rechenergebnis zurückzuliefern, daß man ihn wie normale Ergebnisse, nur in Form eines besonderen, ausgezeichneten Wertes zurückgibt. Das kann ein Null-Pointer sein. Als Fließpunktwert NaN. Eine negative Zahl, wenn man sonst nur positive Zahlen zurückgeben kann, oder sonst irgendein Wert außerhalb erwarteter Bereiche.
Das führt dazu, daß man nach jedem Rechenschritt oder Funktionsaufruf erst einmal das Ergebnis auf Gültigkeit oder die verschiedenen Fehlerzustände prüfen muß. Das ist nicht nur aufwändig und fehlerträchtig, sondern es setzt auch voraus, daß der, der die aufgerufene Funktion geschrieben hat, seine Fehlerzustände vollständig definiert, und daß der, der sie aufruft, sie vollständig überprüft. Klappt in der Realität nie sauber und ordentlich. Und es hat vor allem den Nachteil, daß man damit sofort wieder in der imperativen Programmierung steckt, also mit der Technik, Fehler als normale Werte zurückzugeben, genau betrachtet überhaupt nicht funktional Programmieren kann. Man könnte nun die Auffassung der theoretischen Informatik vertreten, daß ein Algorithmus nur innerhalb der spezifizierten Werte richtig arbeiten muß und alles außerhalb der Spezifikation völlig egal ist, der Algorithmus also Mist zurückgeben kann. Was aber erstens Unfug, zweitens unsicher und drittens unrichtig ist, weil es das Problem nicht löst, sondern nur eine Aufrufebene nach oben verlagert.
Wer sauber funktional programmieren will, der muß also Exceptions werfen (und natürlich eine Sprache verwenden, die das unterstützt). Dann nämlich fällt ein nicht abgefangener Fehlerwert nicht dazu, daß er übersehen wird, sondern führt zum Abbruch auch der nächsthöheren Funktionen/Prozeduren, bis da eben etwas ist, was ihn fängt – oder das Programm terminiert wird, was eindeutig besser (und sicherer) ist, als es mit fehlerhaften Werten unspezifiziert weiterlaufen zu lassen.
Und es macht Programme kürzer, effizienter, leichter lesbar und wartbar, fehlerärmer, weil ich einen Fehler nur dort abfangen muß, wo es sinnvoll ist, und nicht auf allen Funktionsebenen dazwischen unzählige Abfragen einbauen muß. Zudem widerspricht das einer strukturierten Programmierung, denn ein Abbruch einer Funktion/Prozedur vor deren Ende durch ein return oder ähnliches ist eigentlich nicht strukturiert.
Mit einer Exception brauche ich den Fehler nur da abzufangen, wo ich vernünftig darauf reagieren kann, und an allen anderen Stellen kann ich einfach so tun und unbekümmert ohne Sonderfälle so losprogrammieren, als wären alle Werte korrekt. Damit kann man dann erst schön, gut und lesbar funktional programmieren. (Ich wüßte aber von keinem Fall einer Universitätsvorlesung von Funktional-Evangeliums-Predigern, in denen dieser Aspekt berücksichtigt würde, weil es denen reicht, wenn das Programm die Musterlösung zum Übungsblatt richtig berechnet, und alles andere nicht interessiert.)
Der Vorteil der Exceptions ist, daß eine Nicht-Behandlung eines Fehlerzustandes in den darüberliegenden Prozeduren (weil sie vergessen wurde oder nicht gewollt ist) nicht zu einem Versagen des Programmes führt, sondern der Fehlerzustand erhalten bleibt, bis er gefangen wird (spätestens von der Laufzeitumgebung).
Und damit kann man dann auch sehr sicher programmieren. Weil irgendwelche sicherheitsrelevanten Fehler wie beispielsweise in einer Rechte- oder Zertifikatsprüfung oder kryptographischen Routinen in den darüberliegenden Prozeduren nicht mehr abgefangen und einzeln behandelt werden müssen, und damit nicht übersehen oder vergessen werden können.
Auf den ersten Blick hat dies eine scheinbar negative Nebenwirkung, die aber bei Licht betrachtet positiv ist. Wenn nämlich eine Prozedur die Exceptions, die die unter ihr aufgerufenen Prozeduren werfen können, nicht abfängt, dann kann auch die Prozedur selbst während jedes Aufrufes beendet werden und damit sicherheitsrelevante Arbeitsschritte nicht mehr durchführen. Solche sicherheitsrelevanten Schritte könnten sein, irgendwelche Sonderrechte wieder abzugeben (Privilegien wieder abgeben, suid root wieder abschalten), Resourcen zu schließen (Filedescriptor oder socket zu) oder einfach vertrauliche Daten, Passworte, kryptographische Schlüssel zu löschen oder meinetwegen im Gegenteil auch irgendwie zu sichern, weil man sie noch braucht. Sicheres Programmieren bedeutet eben auch, daß man manche Schritte unbedingt erledigen muß.
Dazu gibt es – je nach Sprache – zwei Lösungsansätze.
- Der objektorientierte Ansatz (z. B. in C++) sieht vor, daß Objekte einen Destruktor haben können, also eine Prozedur, die beim Abbau der Variable oder der Rückgabe des Speicherbereiches aufgerufen wird. Bei lokalen Variablen (allerdings nicht dynamisch erzeugten, also nur die auf dem Stack und nicht auf dem Heap) wird dieser Destruktor beim Beenden einer Prozedur, und zwar auch dann, wenn sie durch eine Exception vorzeitig beendet wird, auf jeden Fall aufgerufen, und damit auch zu einem definierten Zeitpunkt. Die gammeln dann nicht noch irgendwie rum. Wenn die Prozedur verlassen wird, ist der Destruktor aufgerufen worden, darauf kann man sich verlassen.
Das kann man wunderbar einsetzen (und entspricht meinem persönlichen Programmierstil) um sicherheitsrelevante Schritte festzulegen. Wenn ich also einen kryptographischen Schlüssel oder ein Passwort speichere, dann packe ich das nicht in einen gewöhnlichen String, sondern in eine dedizierte Objektklasse, deren Destruktor dafür sorgt, daß der Speicherplatz mit Nullen überschrieben wird.
Und wenn ich setuid-root-Rechte oder sonst eine andere uid brauche, dann lege ich dafür eine Variable eines eigenen Datentyps an, deren Initialisierungsroutine den gewünschten Zustand herstellt, und deren Destruktor ihn wieder abbaut. Da kann man das Abbauen dann nicht mehr vergessen, es gibt keine Race-Conditions usw. Einfach, robust und zuverlässig.
Seltsamerweise drängen aber gerade die, die das funktionale Programmieren so sehr verfechten und als den goldenen Weg hinstellen, häufig auf Programmiersprachen, in denen man keine explizite Vernichtung von Variablen und manchmal auch keinen Destruktor hat, sondern nicht mehr benötigte Variablen einfach vergessen werden und im Hauptspeicher rumliegen, bis irgendwann mal früher oder später vielleicht der Garbage Collector vorbeikommt und sie zusammenkehrt.
-
Die andere Variante ist die einer Ensure-Direktive (heißt in den verschiedenen Sprachen leicht unterschiedlich) mit der man Programmschritte angeben kann, die auf jeden Fall ausgeführt werden müssen, auch wenn eine Exception vorbeigeschossen kommt. Hat den Vorteil, daß man direkt und leserlich hinschreiben (und beim Codeprüfen nachlesen) kann, was zu tun ist, hat aber den Nachteil, daß es nur getan wird, wenn man nicht vergißt, es hinzuschreiben.
Man schreibt also etwas in der Art
begin Datei öffnen irgendwas mit der Datei machen .... ensure Datei schließen end
hin und die Datei wird auch dann geschlossen, wenn irgendwas schief geht, was wir hier nicht abfangen. Ähnlich kann man das auch mit kryptographischen Schlüsseln, Passworten und dergleichen treiben, bei denen man in die ensure-Direktive schreibt, daß das Zeug zu löschen ist (oder was auch immer zu tun ist, Rechte ablegen usw.).
Seltsamerweise wird auch das von den Funktional-Propheten nicht berücksichtigt, weil es ja ein prinzipiell imperativer Stil ist oder so aussieht (obwohl man es für funktionales Programmieren in meinen Augen unbedingt notwendig ist).
Funktionale Programmierung kann man deshalb auch als „Wegwerfgesellschaft” ansehen. Was sie algorithmisch nicht mehr benötigen, das lassen sie einfach fallen und hinter sich liegen, und erwarten von anderen, daß die das einsammeln. Wie Raucher auf der Straße.
Aber die funktionale Programmierung hat noch ein größeres Sicherheitsproblem, nämlich das sogenannte „seiteneffektfreie Programmieren”, was als so grandios angesehen und als einziges Ideal vorgegeben wird. Zugegeben, es hat seine Daseinsberechtigung und schöne Eigenschaften.
Worum geht es dabei? Es geht darum, daß eine Funktion oder ein Operator, den man auf Daten anwendet, diese nicht verändert, sondern eine neue Version mit dem Ergebnis zurückliefert. Hängt man beispielsweise an “abc” ein “d” an, so wird nicht der Wert “abc” verändert, sondern ein neuer erzeugt, der dann “abcd” enthält, ohne die beiden alten Werte verändert zu haben. Hört sich schön an, funktioniert aber nur im Kleinen.
Würde man ein Bild bearbeiten und da einzelne Pixel verändern, dürfte es der Operator also nicht im Bild tun, sondern müßte für jede einzelne Pixel-Operation eine neue Kopie des 20-Megapixel-Bildes anlegen. Und ich dürfte diesen Blog-Artikel hier nicht in mein Blog schreiben, sondern müßte eine komplett neue Kopie meines gesamten Blogs anlegen, die sich gegenüber dem unveränderten Blog durch den neuen Artikel unterscheidet. Schon daher absurd und nicht realitätsfähig.
Ein besonderer Denkfehler dieses sogenannten Seiteneffektfreien Programmierens (als Teil, Stil und besondere Rechtfertigung des Funktionalen Programmierens), der sich vor allem aus der mathematischen Lehrsituation an Universitäten ergibt, ist die Annahme, daß Programmierung nur konstruktiv mit Daten umgeht. Es geht immer nur um die Frage, wie man neue Rechenergebnisse erzeugt. Das sichere Entsorgen alter Daten kommt in der akademisch-mathematischen Sichtweise nicht vor, weil man unterstellt, daß Daten „weg” sind wenn man vergisst, wie man darauf zugreift. Wenn man aber die Variable nicht mehr hat, weil man den Bereich verlassen hat, in dem sie gültig war, kann man zwar aus Programmsicht nicht mehr drauf zugreifen. Das heißt aber nicht, daß man auch aus Maschinensicht nicht mehr drauf zugreifen kann. Oder mit anderen Worten: Seiteneffektfreies Programmieren macht den Fehler, nur die sprachlich-theoretische Sicht, nicht aber die tatsächlich ausführende Maschine zu betrachten. Und dort liegen das Passwort und der Schlüssel dann halt im Hauptspeicher rum.
Dazu kommt, daß die Softwaretechniker IT-Sicherheit nicht kennen. Die überlegen sich zwar, wie sie korrekt programmieren und das richtige Ergebnis errechnen. Aber wie man so programmiert, daß etwas nicht geht und daß sich ein Programm auch außerhalb der Spezifikationen zumindest an die Sicherheitsspezifikationen hält, kommt in deren Weltbild überhaupt nicht vor. Softwaretechniker kennen in der Regel keine IT-Sicherheit (was ein Widerspruch in sich ist und meines Erachtens deren Existenzberechtigung negiert).
Kurioserweise könnte man ein Passwort oder einen kryptographischen Schlüssel niemals auf funktionale Weise löschen, denn das wäre ja ein Seiteneffekt. Eine funktional-seiteneffektfreie Löschfunktion müßte nämlich als Ergebnis eine Kopie des Schlüssels produzieren, in der der Schlüssel dann nicht mehr steht (!), ohne den Schlüssel selbst zu löschen. Oder es würde eben genügen, den Schlüssel nicht mehr zu benutzen und zu vergessen, wo er steht, ohne ihn zu verändern.
Da sieht man sehr deutlich, daß das, was da gepredigt wird, massiver Humbug ist. Programmierung kommt nicht ohne Seiteneffekte und imperative Elemente aus (wobei ich ja ausdrücklich sage, daß auch funktionale Elemente – wenn man sie denn richtig nutzt und implementiert – großen, aber nicht alleinigen Nutzen haben).
Der wesentliche Grundfehler dieses extrem-funktionalen Stils ist, daß das nur im Kleinen und für bestimmte, mathematisch orientierte (also rein ergebnisrückgabeorientierte) Problemstellungen funktioniert und man damit glücklich werden, solange man sich nur kleine Probleme aussucht und man sie sich eben willkürlich (und nicht nach Vorgabe der Aufgabenstellung von außen) aussucht. Solange man nur Probleme bearbeitet, die zur funktionalen Programmierung passen, sieht es eben so aus, als ob die alles lösen kann. Und wer nur den Hammer kennt, für den sieht sowieso alles wie ein Nagel aus. Eine typische Situation an den Universitäten, wo man mit funktionalem Programmieren wunderbar die Aufgabe 3b auf dem achten Übungsblatt der Einführungsvorlesung im zweiten Semester stellen und lösen kann, die man bis nächsten Dienstag abgeben muß, für die man zwei Punkte bekommt, wenn der Tutor aus dem sechsten Semester nickt, und die man dann für immer vergessen kann.
8 Kommentare (RSS-Feed)
Oh, was ein Käse.
Wie soll denn eine Kopierstrategie wie lazy copy oder eine andere dazu führen, daß ein Schlüssel mit Nullen überschrieben wird? Keine der Kopiestrategien löscht oder überschreibt das ursprüngliche Datum.
Auch für das Beispiel Bildverarbeitung (das ich mit Bedacht so gewählt habe) taugt das nicht. Für ein Pixel braucht man bei 8 Bit tiefe und RGB oder RGBA 24 oder 32 Bit. Will ich da aber Lazy Copy treiben, brauche ich für das Pixel mindestens einen Pointer zzgl. der Verwaltungsfunktionen wie Refzähler, also viel mehr Informationen. Da ist der Verwaltungsaufwand also noch viel größer als plattes Kopieren. (Genau der Bockmist, den ich anprangere.) Man müßte sich dann überlegen, ob man das segment- oder zeilenweise macht, aber das kann man dann schon wieder nicht allgemein für alle Datentypen machen, sondern ist wieder Bild-spezifisch, und man hat einen Mords Verwaltungsaufwand und zusätzlichen Speicherbedarf. Das sind dann alles so die vermeintlich tollen Vorschläge, wenn man nicht weiß, was dahinter vor sich geht. (Von wegen Halbwissen…)
Und versuch mal, einer Graphikkarte ein Bild zu liefern, das nicht als Pixelraster, sondern als Verwaltungsstruktur mit Lazy Copies vorliegt. Viel Spaß dabei auch.
Und wie man mit Monaden Schlüssel löscht oder Bilder seiteneffektfrei bearbeiten kann, ist mir auch nicht ersichtlich.
Monaden sind letztlich auch nichts anderes als eine abstrakte Organisation und Verkettung von Funktionsaufrufen und können daher nicht mächtiger sein als Funktionsaufrufe. Die können nicht mehr, nur weil sie einen tollen eindrucksvollen Namen haben. Die lösen weder das Problem der Bildverarbeitung, noch das des Löschens von Schlüsseln. Nur weil man das in Formelschreibweise auf Papier schreiben kann, heißt das noch lange nicht, daß es dann auch weniger Speicher braucht als mehrfaches vollständiges Kopieren. Das ist halt leider so, daß man durch die Abstraktion von der Maschine häufig glaubt, sie darum nicht mehr kümmern zu müssen.
Und das mit der maybe-Monade ist auch nur heiße Luft. Denn das läuft auch darauf hinaus, daß man einen riesigen Funktionswirrwarr nur noch mehr aufbläst, weil man damit jede Aktion noch durch weitere ständige Abfragen ersetzt – also quasi einen Makro baut und doch wieder imperativ programmiert, sich nur selbst betrügt, weil man es hinter der Schreibweise verbirgt. Dann wird trotzdem der ganze Käse weiterberechnet, obwohl der Ungültig ist, nur alles jetzt dreimal oder öfter. Das ist doch grober Mist.
Davon abgesehen klappt das auch nur mit den Fällen, die man vorgesehen hat, aber nicht beim zusammensetzen komplexer Programme aus verschiedenen Bibliotheken und Fehlerzuständen, die sich nicht formelhaft erfassen lassen. Wenn ich ganz tief unten einen neuen Fehlertyp brauche, muß das ganze Monadengeraffels neu programmiert werden.
Daher leidet Deine Argumentation daran, daß Du Dir – durch funktional vergurkten Stil – die Sache zu einfach machst und nicht mehr merkst, daß Du mehr Probleme auflädst als Du löst.
Wie Du ganz zutreffend über formales Programmieren sagst: Formale Spezifikation und Verifikation wird darin relativ einfach. Stimmt. Das Vorlesung halten wird auch gleich viel einfacher. Aber es kommt keine ordentliche Software dabei heraus, weil man wesentliche Arbeitsschritte wegvereinfacht hat. Deshalb wird es ja so einfach, weil man die Arbeit nicht mehr richtig macht und wesentliche Probleme einfach ignoriert.
> Und versuch mal, einer Graphikkarte ein Bild zu liefern, das nicht
> als Pixelraster, sondern als Verwaltungsstruktur mit Lazy Copies
> vorliegt. Viel Spaß dabei auch.
Erstens ist das ein Problem der Graka und nicht ein Problem des Programmierparadigmas. Und ich würde Grafikkarten nicht unbedingt als Argument nehmen – Grafikkartentreiber sind soweit ich das gelesen habe (und auch meiner persönlichen Erfahrung nach, aber was heißt das schon) der Hauptgrund für Systemabstürze.
Ansonsten schau dir mal an, was der syscall fork(2) üblicherweise mit dem Speicher macht. COW-Pages sind eine Möglichkeit das zu realisieren (es ist aber leider zu diesem Zeitpunkt noch nicht möglich, Speicherbereiche explizit COW zu mappen). Freilich will deine Graka sich vermutlich nicht an Memory Mappings halten, sondern eine effektive Adresse wollen – ob das auch in Zukunft so bleibt, wird sich zeigen, ich hörte dass es Planungen gibt das im Zuge diverser Virtualisierungskacke zu ändern – dazu weiß ich aber davon nicht wirklich genug.
> also quasi einen Makro baut und doch wieder imperativ programmiert,
> sich nur selbst betrügt, weil man es hinter der Schreibweise
> verbirgt.
Das ist es was Monaden versuchen. Skalieren tun sie dabei hinsichtlich des Coding-Aufwandes imho nicht, aber zumindest theoretisch ist es ohne große Magie möglich dass der compilierte Code genauso effizient ist wie äquivalenter imperativer Code – es hat nur keiner einen Compiler geschrieben der das kann (soweit ich weiß). Kommt vielleicht noch, Monaden sind für ein Konstrukt das nicht aus der Fashion-orientierten Pragmatenwelt kommt zwar vielleicht schon alt, aber für Leute die nicht nur Rumhacken wollen ziemlich neu, und dafür dass sie ziemlich neu sind leisten sie viel, denn man kann relativ gut mit ihnen Coden, und relativ gut über sie formale Aussagen machen.
> Dann wird trotzdem der ganze Käse weiterberechnet, obwohl der Ungültig
> ist, nur alles jetzt dreimal oder öfter. Das ist doch grober Mist.
An dieser Stelle greift Lazy Evaluation – was ein Grund ist warum die extrem starke “Monadisierung” auch hauptsächlich in Haskell stattfand. Die Leute haben sich nämlich was dabei gedacht.
> Das Vorlesung halten wird auch gleich viel einfacher. Aber es kommt
> keine ordentliche Software dabei heraus
Das ist eine Aussage. Wie willst du sie begründen? Dass es noch keine größeren darauf aufbauenden Softwareprojekte gibt stimmt prinzipiell, mal abgesehen von ein paar Betriebssystemkernen (aber Betriebssystemkerne sind ja auch praktisch gesehen unsinnig, schaffen sie doch eine riesige Lücke zwischen Hardware und Software), window-managern und web-frameworks, aber es ist auch noch ein aktiv erforschtes Gebiet, und das wird es wohl auch noch ne ganze Zeit lang bleiben.
> weil man wesentliche Arbeitsschritte wegvereinfacht hat.
Meinst du den wesentlichen Arbeitsschritt des des so lange auf dem Code herumhauens bis er endlich alle Testfälle unterstützt oder den wesentlichen Arbeitsschritt des Testfälle wegen des neusten gemeldeten Bugs erfindens?
Hast du schon mal mit formaler Verifikation gearbeitet? Die traumhafte Welt in der man beweisen muss dass ein Pointer kein Null-Pointer ist.
Naja, du solltest jedenfalls nicht einfach mal so aus dem nichts auf funktionaler Programmierung rumhacken, nur weil ein paar Skript Kinder nicht programmieren können, und in imperativer Programmierung die Erklärung dafür suchen. Die Leute die daran Arbeiten sind meistens sehr fähig. Und die meiste schlechte Software (dbus, kde, wordpress) ist letztendlich nicht in funktionalen Sprachen geschrieben.
Oh ja, schon wieder die alte Leier der Funktional-Heinis, daß man Probleme gar nicht lösen muß, weil man sie einfach als Probleme anderer Leute definiert und dann denen überläßt. Juchhei!
Graphikkarten? Ja, ein Problem des Kartentreibers. In der Denkweise, wir brauchen ja keine imperativen/seiteneffektbasierten Sprachen, weil wir alles, was funktional nicht kann, einfach einkaufen und ignorieren. Wozu Kraftwerke, bei uns kommt der Strom doch aus der Steckdose. Hahaha. Und dann hoffen, daß der, der die Graphikkartensoftware programmiert, hoffentlich nicht an der Uni studiert hat, damit der das nicht funktional programmieren will. Ganz toll.
Sag ich doch, Funktional-Prediger können nicht ordentlich programmieren und erklären sich für toll und allmächtig, indem sie einfach alles ausblenden, was sie nicht können und wollen. Und die Arbeit anderen überlassen.
Was syscall fork mit dem Speicher macht, ist belanglos, weil es zum Thema Security schon zu spät ist, wenn es zu einem fork kommt. Dann muß der Schlüssel schon gelöscht sein. Oder das Problem stellt sich neu.
Ja, das ist es, was Monaden versuchen. Und deshalb ist es keine brauchbare Lösung des Problems. Monaden sind da einfach nicht das richtige Mittel, auch wenn es in manchen Einzelfällen hinkenderweise funktionieren mag. Wieder so die Sichtweise, daß man ein Problem als gelöst ansieht, wenn man so eine Verbal-Fachbegriff-Theorie-Wackellösung hat. Tut’s aber doch nicht.
Und Lazy Evaluation hilft weder beim Graphikkarten-Problem noch beim Schlüssel-Problem. Es hilft einfach, mit tollen Fremdwörtern zu werfen.
Wie ich es begründen will? Habe ich doch gerade.
Auf dem Code herumhauen? Ich habe immer den Eindruck, daß die Funktional-Ritter sich mit bewußt schlechten imperativen Programmbeispielen oder Leuten, die nicht programmieren können, vergleichen müssen, um halbwegs brauchbar dazustehen. Daß funktionales Programmieren pauschal so viel einfacher und fehlerfreier wäre, würde ich mal bezweifeln.
Ja, ich habe schon mit formaler Verifikation gearbeitet. Ob Du das jetzt glaubst oder nicht, ich hab sowas studiert und sogar Tutorien und Übungen darin gehalten. Und ich kenne die weite Strecke, die zwischen einem formal verifizierten Programm und einem Stück Software, das tut, was es soll, noch klafft. Und weiß, daß man die allermeiste Software nicht oder nicht mit vertretbarem Aufwand und brauchbarem Ergebnis verifizieren kann, während es eine Menge nutzt, wenn man ordentlich imperativ schreiben kann.
Und es gibt auch schöne verifizierte Programme, die überhaupt nicht funktionieren, weil die Verifikation nicht deren CPU- oder Speicherverbrauch berücksichtigt hat und die Kiste einfach stehen bleibt oder nicht zum Ende kommt.
Was dbus, kde und wordpress mit funktional zu tun habe, verstehe ich nun gar nicht. Versuchst Du abzulenken?
> … wo man mit funktionalem Programmieren wunderbar die Aufgabe 3b
> auf dem achten Übungsblatt der Einführungsvorlesung im zweiten
> Semester stellen und lösen kann, die man bis nächsten Dienstag
> abgeben muß, für die man zwei Punkte bekommt, wenn der Tutor aus dem
> sechsten Semester nickt, und *die man dann für immer vergessen kann*.
Was ja auch eine schöne Beschreibung seiteneffekfreien funktionalen Programmierens ist. 😉 SCNR.
> Würde man ein Bild bearbeiten und da einzelne Pixel verändern,
> dürfte es der Operator also nicht im Bild tun, sondern müßte für
> jede einzelne Pixel-Operation eine neue Kopie des 20-Megapixel-
> Bildes anlegen.
Wenn du schon mit einem Bildbeispiel kommst, könnte man der Fairness halber noch erwähnen, wo funktionalie Programmierung richtig gut funktioniert: im Audiobereich, genauer bei digitaler Echtzeit-Signalverarbeitung. Hier wird mit kleinen Samples und Ringpuffern gearbeitet und nicht mit 20-Megapixel-Bildern. Man werfe etwa einen Blick auf http://faust.grame.fr/index.php . Hier finde ich das funktionale Paradigma nicht nur passend, sondern richtig instruktiv für Einsteiger.
Zu deinen kryptographischen Bedenken: +1
@florian: Ich sag ja gar nicht, daß funktionale Programmierung generell schlecht wäre. Und oft setze ich die auch selbst ein. Nur diese Gläubigkeit, daß die FP das ultimativ bessere sei und uns von allem erlöst, die ist Unfug.
Nach etwas grübeln über dieses Feuergefecht hier, muss ich etwas dazu schreiben.
Ich bin schon immer ein praxisorientierter Mensch gewesen und konnte mit Theoriegebrabbel welches mit der Realität nichts zu tun hat noch nie etwas anfangen.
Die Erfahrung hat mir gezeigt, dass viele Softwareentwickler bei ihrer Arbeit keinerlei Bezug mehr zur eigentlichen darunterliegenden Hardware haben. Alles wird nur noch abstrakt betrachtet. Der Arbeitsspeicher beispielsweise ist nicht mehr physikalisch Anwesend sondern wird als logisches Etwas bedient. Geräte werden irgendwie benutzt in der Hoffnung die Anderen werden schon daran gedacht haben, dass ich versuche es so (ineffektiv) zu benutzen. Das OS wird es schon schön optimieren. Das Passwort wird schon keiner Finden, denn ich sehe es ja auch nicht mehr. Letztendlich ist dieses Denken genau der Grund warum hier das Löschen des Passwortes im RAM gar nicht erst als Problem erkannt wird. Du kannst schreiben und diskutieren was du willst.
“Was ich nicht mehr sehe, gibt es auch nicht mehr.” Das erinnert mich irgendwie an ein Kleinkind welches sich die Hand vor Augen hält und denkt die Anderen können sie auch nicht mehr sehen, weil das Kind selbst auch nichts mehr sieht.
Ich habe durch meine Arbeit oft mit größeren Datenmengen zu tun und durfte des Öfteren Software anderer Entwickler “reparieren”. Häufiges Problem war, das die zu verarbeitende Datenmenge und dessen Auswirkungen einfach nicht beachtet wurde. Wenn ich nur die Metadaten eines Bildes brauche, gibt es keinen Grund dieses komplett in den Ram zu laden (und es dort am besten noch zu kopieren). Bei einem Bild mag das noch relativ egal sein. Nicht aber wenn ich mehrere Tausend möglichst schnell verarbeiten muss.
Was ich damit sagen will ist, dass mein Problem letztendlich vorgibt welcher Lösungsweg sinnvoll ist. Mit aller Gewalt zu versuchen für heilig erachtete Lösungswege oder Programmierstile zurecht zu klopfen damit diese auf umständlichem Wege das Selbe tun ist unsinnig und ineffektiv. Der Vergleich mit der Pizza war dafür sehr gut.
Um zum eigentlichen Thema zurückzukommen: Wenn eine Art zu programmieren mein Problem besser lösen kann als eine Andere, dann sollte man auch so arbeiten.
@Christian: Sehr gut beschrieben.
Es ist mir schon häufiger aufgefallen, daß Funktional-Programmierer unheimlich gerne so schöne einfache funktionale Ausdrücke hinschreiben und sich Arbeit (und Können) sparen, und sich über den schön einfachen Ausdruck freuen, aber dann nicht mal grob oder asymptotisch sagen können, wieviel Rechenzeit oder Speicher das braucht, ob das linear, quadratisch, exponentiell oder sowas steigt.
Der Unterschied zwischen Abstraktion und Ignoranz ist häufig nur graduell.
Ok, sorry, etwas weniger Halbwissen und dafür etwas mehr Substanz hätte ich bei der sonstigen Qualität der Blogeinträge hier schon erwartet.
Alleine an Aussagen wie “Eine funktional-seiteneffektfreie Löschfunktion müßte nämlich als Ergebnis eine Kopie des Schlüssels produzieren, in der der Schlüssel dann nicht mehr steht (!), ohne den Schlüssel selbst zu löschen.” zeigen dass du nicht wirklich viel Ahnung davon hast, und offenbar auch nicht über lazy copies [was man wohl am Ehesten mit deiner “Bildbearbeitung” machen würde] – aber wer kann es Dir verübeln, es gibt leider viel zu viele Enthusiastenkinder die im Wesentlichen funktional programmieren weil sie für C zu blöd sind.
Funktionale Programmierung ist ein sehr facettenreiches Forschungsgebiet das eben inzwischen so langsam in den Praxisalltag eintaucht. Wir wollen mal nicht vergessen, dass auch die gesamten Systemschnittstellen, das allseits beliebte C, und die von dir genannte Kryptographie dereinst graue Theorie waren, über die Pragmaten sich lustig gemacht haben.
Obiges würde man funktional vermutlich am Ehesten mit einer Monade, oder im guten alten Lisp-Stil (wobei Common Lisp klar _keine_ funktionale Sprache ist, nur um das gesagt zu haben) zum Beispiel mit einem (with-key (a) …)-macro lösen. Aber zugegeben, an diesem Teil ist etwas dran, dafür wurden aber klarerweise funktionale Sprachen auch nicht gemacht, und das Einbetten nichtfunktionaler Strukturen in funktionale Sprachen ist einfach ein Gebiet an dem es noch viel zu forschen gibt.
Aber sorry, grad der Teil mit dem Exception-Handling entbehrt wirklich jeglicher Grundlage. Schau dir mal
– finalizer
– die maybe-monade
– die exception-monade
an. Zeug das meiner Meinung nach zu kompliziert ist als dass ich es verwenden will – aber es ist da, und obendrein sind es Standardbeispiele die üblicherweise in jedem einführenden Skript zu dem Thema auf den ersten Seiten zu finden sind. Achja, und weil du in einem vorherigen Blogpost was Negatives über Lisp sagtest: http://clhs.lisp.se/Body/m_hand_1.htm – vielseitiger als jedes try-catch in C++ (aber zugegeben, imperativ).
Das Ganze Gwafi über “seiteneffektfreies Programmieren” hängt mit gewissen Fixpunktprinzipien zusammen, die zum Beispiel Haskeller gerne haben wollen, und damit, dass die formale Spezifikation eines Programmes das Seiteneffekte hat eben gleich um ein Vielfaches komplizierter ist. Das ist keine Ideologie, es haben sich durchaus Leute etwas dabei gedacht.
Das ist btw meiner Meinung nach genau der Punkt des funktionalen Programmierens: Formale Spezifikation und Verifikation wird darin relativ einfach. Du möchtest keine Schleifeninvarianten finden, um deine while-Schleife in Java zu verifizieren, denn bis du sie gefunden hast hast du vier andere Funktionen in meinetwegen Agda geschrieben, für die du die Verifikation “geschenkt” bekommst.
Und formale Verifikation ist meiner Meinung nach das wichtigste Forschungsgebiet im Bereich der Informatik – und der einzig sinnvolle Lösungsansatz (und ich spreche bewusst nur von einem Ansatz) für die vielen Bugs die unsere lieben Pragmaten wöchentlich produzieren (und ich rede dabei nicht von neuartigen Bugs sondern von so Dingen wie Pufferüberläufen oder Datenbank-Injektionen).