Funktionale Programmierung mit Haskell



  • ProgChild schrieb:

    Du sagst, der Mensch kann nicht parallel Denken.

    Das sage ich nicht. Ich sage, prozedurales oder objektorientiertes Denken ist dem Menschen natürlich mitgegeben, funktionales, deklaratives oder massiv paralleles Denken sind Kulturtechniken.

    Das Absehen vom zeitsequentiellen Aspekt erfordert Abstraktion, die erlernt und geübt werden kann.

    Die aber für das Überleben des Urmenschen keine große Rolle spielte, und daher nicht im Gehirn "festverdrahtet" zu sein braucht.

    [quote="ProgChild"]
    Dazu fällt mir folgendes Zitat ein.

    Languages less powerful than the one you know look impoverished [armselig].
    Languages more powerful than the one you know look weird [komisch].
    (Paul Graham)

    Ein schöner Aphorismus.



  • u_ser-l schrieb:

    Also ist selbst die einfache logische Operation der Implikation (, die Grundbestandteil jeden strategischen Denkens und gedanklichen Vorwegnehmens von Vorgängen der realen Welt ist,)
    gedanklich untrennbar mit zeitlicher Abfolge verbunden.

    Sprachliche Ausdrücke entwickeln sich immer vom Konkreten zum Abstrakten. Letztlich kann man es runterbrechen auf: Erst körperbezogene Bedeutung, dann Ortsangabe, dann Zeitangabe, dann weitere Abstraktion wie z. B. logische Bedingungen. Im Deutschen lässt sich die komplette Reihe in der Regel nicht mehr direkt finden. Dafür haben andere Sprachen in historischer Zeit für Ausdrücke die ganze Reihe durchlaufen. Aber auch im Deutschen lassen sich jede Menge Indizien finden. So haben diverse Zeitausdrücke ihre Entsprechungen in Ortsangaben (am Montag/am Rathaus, um 5 Uhr herum/um das Haus herum, zwischen 3 und 5 Uhr/zwischen Tisch und Stuhl, ...). Aber auch das körperbezogene Koordinatensystem lässt sich stellenweise noch im Deutschen und (besser) im Englischen erknnen. Zum Beispiel in "zurück" oder "back" steckt noch ziemlich gut erkennbar der Rücken.

    Die Entwicklung von konkret zu abstrakt ist universell und sagt noch nichts über menschliche Denkweisen im Allgemeinen aus. Sondern erst einmal nur über die Fähigkeit Analogien zu bilden. Im Grunde ist jedes Funktionswort eine tote Metapher.

    Im Fall von "dann" ist das nicht anders. Im Althochdeutschen hat "dann" ausschließlich örtliche oder zeitliche Bedeutung. Die kausale entwickelte sich erst in den letzten Jahrhunderten. Eine funktionale Unterscheidung zwischen "dann" und "denn" erfolgt sogar erst seit dem 18. Jahrhundert. Davor hatte selbst "denn" keine kausale Bedeutung.

    ProgChild schrieb:

    Wie ich schon erwähnt habe, sehe ich sehr viele Menschen, die Probleme haben prozedural zu denken. Erkläre mir das mal, wenn das so natürlich ist.

    Ich habe an der Universität sowohl Kurse für prozedurale, als auch für objektorientierte und für logische Programmierung betreut. Logische Programmierung ist eben so deklarativ wie funktionale Programmierung. Dein Summenbeispiel sähe in Prolog z. B. so aus:

    summe([], 0).
    summe([H|T], S) :- summe(T, ST), S is ST + H.

    Das ist ein unglaublich einfaches Programm. Der Wert von keiner Zahl zusammengezählt ist 0, ansonsten nimmt man eine Zahl von der Liste, rechnet die Summe für den Rest aus und addiert darauf diese Zahl, fertig.

    ?- summe([3,5,7], S).
    S = 15.

    Meine Erfahrung aus diesen Kursen ist, dass Programmieranfänger, die noch nie mit Programmierung in Berührung kamen, völlig unabhängig vom Paradigma der Programmiersprache enorme Probleme haben. Du sagst, die rekursive Lösung des Summenbeispiels sei natürlich und einfach. Doch meine Erfahrung ist, dass ausgerechnet die Rekursion die Anfänger vor schier unlösbare Probleme stellt. Es braucht in der Regel lange und viel Frust, bis sie einfache Probleme rekursiv lösen können. Und dafür ist es egal, was für ein Paradigma die Programmiersprache verfolgt. Nur bei deklarativen ist es natürlich offensichtlicher.

    Ich würde aufgrund meiner Erfahrung also nicht sagen, dass es die prozedurale Programmierung ist, die schwer erlernbar ist. Denn echte Anfänger haben mit der deklarativen Programmierung ebenfalls ihre liebe Not. Für mich sieht die Situation wie folgt aus: Das Paradigma ist nicht das Problem. Viel eher ist das streng formale Durchdenken einer Aufgabe das Problem. Daran hackt es bei echten Anfängern.

    Wenn man aber prozedurale gegen deklarativ antreten lässt, dann würde ich persönlich eher zu u_ser-l neigen. Wenn man den Anfängern erklärt, dass y = x*x keine Bedeutung definiert, sondern einen konkreten Ablauf ausführt, dann kapieren sie das schnell. Aber Rekursion - und ohne die geht es in deklarativen Sprachen nicht wirklich - das ist eine ganz andere Nuss. Die kann man ihnen fünfmal erklären und sie produzieren dann immer noch *hust* Mist *hust* 😉



  • u_ser-l schrieb:

    Das sage ich nicht. Ich sage, prozedurales oder objektorientiertes Denken ist dem Menschen natürlich mitgegeben, funktionales, deklaratives oder massiv paralleles Denken sind Kulturtechniken.

    Das Absehen vom zeitsequentiellen Aspekt erfordert Abstraktion, die erlernt und geübt werden kann.

    Die aber für das Überleben des Urmenschen keine große Rolle spielte, und daher nicht im Gehirn "festverdrahtet" zu sein braucht.

    Wenn der Urmensch auf die Jagt ging, musste er alle seine Sinne benutzen. Zum Beispiel musst er gleichzeitig lauschen, ob sich irgendwo was bewegt und langsam vorwärts schreiten.

    Er hat sich bestimmt nicht gedacht: "Ich gehe einen Schritt. Dann bleibe ich stehen und lausche. Dann gehe ich wieder einen Schritt. Dann bleibe ich wieder stehen..."
    Er wird wohl gedacht haben: "Ich gehe langsam vorwärts und währenddessen lausche ich."

    Aber egal. Belassen wir es dabei!



  • minhen schrieb:

    Aber Rekursion - und ohne die geht es in deklarativen Sprachen nicht wirklich - das ist eine ganz andere Nuss. Die kann man ihnen fünfmal erklären und sie produzieren dann immer noch *hust* Mist *hust* 😉

    Das Problem an der Sache ist, dass man sagt: "Rekursion ist wenn eine Funktion sich selber aufruft." Das ist so abstrakt, dass der Zuhörer reflexartig sagt: "Verstehe ich nicht." Wenn man ihnen aber ein paar Beispiele präsentiert und wie man sich Schritt für Schritt immer komplizierteren Problemen nähert, so wird das alles plötzlich total natürlich.

    Denke man mal an die ganzen Graphen und Baum-Algorithmen. Ohne Rekursion wäre die meisten nicht so einfach nachvollziehbar.



  • ProgChild schrieb:

    minhen schrieb:

    Aber Rekursion - und ohne die geht es in deklarativen Sprachen nicht wirklich - das ist eine ganz andere Nuss. Die kann man ihnen fünfmal erklären und sie produzieren dann immer noch *hust* Mist *hust* 😉

    Das Problem an der Sache ist, dass man sagt: "Rekursion ist wenn eine Funktion sich selber aufruft." Das ist so abstrakt, dass der Zuhörer reflexartig sagt: "Verstehe ich nicht." Wenn man ihnen aber ein paar Beispiele präsentiert und wie man sich Schritt für Schritt immer komplizierteren Problemen nähert, so wird das alles plötzlich total natürlich.

    Nein, ist es nicht. Oder glaubst du ernsthaft, ich rede von solchen Erklärungen:

    Erklärung 1: Rekursion ist, wenn sich eine Funktion selbst aufruft.
    Versteht ihr nicht? Ok:
    Erklärung 2: Eine sich selbst aufrufende Funktion nennt man rekursiv.
    Versteht ihr nicht? Ok:
    Erklärung 3: Rekursiv bedeutet, dass sich die Funktion immer wieder selbst aufruft.
    Versteht ihr immer noch nicht? Mensch, seid ihr doof!

    Wenn du das unter "Rekursion erklären" verstehst, nur weil man eine andere Meinung als du hast ... dann brauchen wir das Thema gar nicht weiter diskutieren, weil es dann schlicht sinnlos wäre.



  • sollte es nach der neuen Rechtschreibung nicht konsequenterweise 'funkzionale Programmierung' heißen?

    - vgl 'Differenzial' vs 'Differential'





  • u_ser-l schrieb:

    sollte es nach der neuen Rechtschreibung nicht konsequenterweise 'funkzionale Programmierung' heißen?

    - vgl 'Differenzial' vs 'Differential'

    Seit ich letztens in einer Zeitung "neudeutsche Getrenntschreibung bzw. Getrennt Schreibung" gelesen hab, fange ich an, mich für die deutsche Sprache zu schämen ...



  • denkt der Mensch von Natur aus überhaupt in Funktionen und nicht vielmehr in Aktionen?

    Kuchen := backen(verstreichen(rühren(Mehl, Wasser, Butter, Zucker), Backpinsel, Öl, Blech), 180 C)
    

    wahrscheinlich werden nicht umsonst im Mathematikunterrich arithmetische Operation (also Aktionen) bereits in der Grundschule definiert, wogegen Funktionen (und Komposition) erst Jahre später in der Mittelstufe gelehrt werden.



  • u_ser-l schrieb:

    sollte es nach der neuen Rechtschreibung nicht konsequenterweise 'funkzionale Programmierung' heißen?

    😃 👍
    Aber das hätte ja kein Niwo mehr.



  • und eben weil der Mensch von Natur aus eher in Aktionen als in Funktionen denkt, waren Seiteneffekte (auf globale Variablen usw.) stets eine ergiebige Fehlerquelle - beim menschlichen Denken steht von Natur aus eben die Aktion und nicht das E/A-Verhalten im Vordergrund, das muß erlernt oder anerzogen werden, beispielsweise, indem sich der Programmierer durch Vermeidung globaler Variablen vor solchen Fehlern schützt, oder eben durch funktionale Programmierung.



  • @ProgChild: Ich kann dir versichern, dass Microsoft Research eine ganze Reihe von produktiven "Dingen" geschaffen hat. Aber du kannst dich auch selber davon überzeugen indem du dir einfach mal ein paar ihrer Veröffentlichungen anschaust (z.B. "Runtime Support for Multicore Haskell"). Viel wird über die ICFP (international conference on functional programming) oder das JFP (journal on functional programming) veröffentlicht.

    Natürlich ist Haskell standardisiert worden, allerdings ist das 11 Jahre her. Seither wurde an der Sprache aber viel weitergefeilt. Diese Neuerungen sind als Extensions verwendbar, von denen aber viele nur im ghc existieren. Und das liegt auch an Microsoft.

    Und falls du Haskell zu mehr verwenden willst, als dir nur mal anschauen, wie die Sprache so funktioniert, dann kommst du eh nicht am ghc vorbei. Es gibt keinen Haskell-Compiler, der dir deinen Code dermaßen gut optimieren kann (-02). Und ohne Optimierungen kannst du nicht wirklich performante Software in Haskell entwickeln. Auch gibt es sonst keinen Compiler der dir Debugmöglichkeiten bietet (was tatsächlich ein echtes Problem ist).

    @minhen: Ich kann auch nur bestätigen, dass es vielen auf Anhieb nicht leicht fällt, Rekursion tatsächlich zu verstehen. Ob es ein Paradigma gibt, in dem man das am besten/natürlichsten erklären kann, bezweifel ich stark. Das kommt einfach auf den Menschen an.

    Übrigens glaube ich auch nicht, dass funktionale Programmierung den Anspruch hat, eine besonders natürliche Form der Abstraktion zu sein. Möglicherweise ist dies die sequentielle Beschreibung von Algorithmen. Auch Mathematik ist für viele Menschen ein Buch mit sieben Siegeln und doch währe eine Welt ohne Mathe nicht denkbar 🤡.

    Zu guter letzt wollte ich noch sagen, dass ich diese ganze Urzeit-/Jagd-/Evolutions-/Denk-Diskusion für ziemlich sinnfrei halte. Fakt ist doch, dass wir alle nur mutmaßen können, wie das Jagen der Urmenschen stattgefunden hat. Das man aus solchen Mutmaßungen tatsächlich schließen kann, welche Form des Denkens natürlich ist, halte ich für unmöglich.



  • frosch03 schrieb:

    Natürlich ist Haskell standardisiert worden, allerdings ist das 11 Jahre her.

    Nur der Vollständigkeit halber: Der Haskell 98 Report ist zwar von Ende 1998, aber die aktuelle (revidierte) Fassung ist von Dezember 2002, also nur etwas über 6 Jahre alt: http://www.haskell.org/onlinereport/



  • frosch03 schrieb:

    Auch Mathematik ist für viele Menschen ein Buch mit sieben Siegeln und doch währe eine Welt ohne Mathe nicht denkbar 🤡.

    Rechtschreibung auch 🤡

    frosch03 schrieb:

    Zu guter letzt wollte ich noch sagen, dass ich diese ganze Urzeit-/Jagd-/Evolutions-/Denk-Diskusion für ziemlich sinnfrei halte.

    Ganz im Gegenteil. Um menschengerechte Mnemonik entwickeln zu können, muß man menschliches Denken verstehen, und dazu muß man verstehen, wie menschliches Denken zustande kommt und sich entwickelt hat.

    Mnemonik zählt.

    Die Auffassung, der Mensch sei eine Art Roboter mit eingebautem Computer, führt nur zu immer weiteren Syntax-Monstern und unbedienbaren Mensch/Maschine-Interfaces.



  • Das hat doch wenig bis gar nichts mit Mnemonik zu tun. Es ist doch nicht das Problem, dass man sich die Syntax einer Programmiersprache nicht merken könnte. Ich vermute, was Rekursion so schwer macht, ist einfach der Umstand, dass es ein eigenständiges, nicht triviales Konzept ist. Während man bei der prozeduralen Programmierung im Grunde klar der Reihe nach stehen hat, was wann wo wie passiert, ist das bei Rekursion nicht der Fall. Im Gegensatz zu vielen anderen Dingen kann man das, was bei der Rekursion nicht explizit dort steht, auch nicht einfach ignorieren. Bei einem Funktionsaufruf brauch ich mir keinen Kopf um den Stack oder sonstige Interna machen. Und selbst bei der Unifikation zweier Variabeln kann mir das Backtracking erst einmal egal sein. Aber bei der Rekursion ist das, was nicht dort steht, der eigentliche Kern, der springende Punkt, das Thema der ganzen Angelegenheit. Es scheint einfach einfacher zu sein, exakt das hinzutippen, was man sich denkt, wenn man das Problem durchgeht: Erst mach ich das, dann das, dann das. Die zusätzliche Abstraktionsstufe, dass sich der Abflauf hier und dort wiederholt und so und so zusammenfassen lässt und damit diesen eleganten, rekursiven Algorithmus ergibt, die scheint das Problem zu sein. Es ist ja nicht so, dass Anfänger einen gegebenen rekursiven Algorithmus nicht verstehen oder nachvollziehen könnten. Das Problem, das sie meiner Erfahrung nach haben, ist schlicht und ergreifend zu einer Aufgabenstellung selbstständig eine rekursive Lösung zu finden. Das Nachvollziehen einer gegebenen Lösung ist dagegen meistens eher kein Problem.

    Das mit Abstand größte Problem für Anfänger ist aber nachwievor ein Problem einfach nur streng und exakt durchzudenken. Das ist das größte Problem und betrifft sämtliche Programmiersprachen. Bei Rekursion, welche für deklarative Sprachen grundlegend ist, kommt ein zusätzlicher Abstraktionsschritt dazu. Der stellt für sich alleine schon eine Herausforderung dar. Und wenn man als Anfänger noch mit dem Problem Nummer eins hadert, wird's durch Rekursion garantiert nicht besser. Wenn ich für jedes Programm eines Anfängers einen Euro bekommen hätte, bei dem sich die Rekursion in die Unendlichkeit verabschiedet bis in den meisten Fällen der Stack überläuft. Das wär was gewesen 😉

    Aber mit Natürlichkeit und Unnatürlichkeit und wie der Mensch selbst im Hirnkastal denkt, hat das wenig zu tun. Menschen könnten ohne Weiteres "rekursiv denken" und der implizite Abstraktionsschritt im Formalismus würde den Anfängern trotzdem schwer fallen. Das wäre wie mit Sprache und Grammatik. Wir alle haben die deutsche Grammatik intus und können grammatikalische Sätze produzieren, erkennen und verstehen und wissen, wenn ein Satz irgendwie "kein richtiges Deutsch" ist. Und das irgendwie ist der zentrale Punkt. Wehe man muss die Regeln, die man zweifelsohne beherrscht, ausbuchstabieren, aufschreiben oder auch nur bewusst anwenden ...



  • Ohne mich jetzt allzusehr in diesen Thread hineinwerfen zu wollen: Vergesst nicht, dass der persönliche Hintergrund den Zugang zu verschiedensten Konzepten und Sprachen stark beeinflusst.

    Ich habe mal ein paar viertsemestrigen Mathematikern, die vorher noch nie programmiert hatten, die Grundlagen von Haskell gezeigt. Die hatten natürlich keinerlei Schwierigkeiten mit Rekursion (Klar, kommt ja in mathematischen Definitionen auch ständig vor.) und diversen Eigenheiten der funktionalen Programmierung.



  • minhen schrieb:

    Das hat doch wenig bis gar nichts mit Mnemonik zu tun.

    ich finde schon - ein funktionaler Ausdruck wie

    t := a(b(c, x1), x2(d, y(3)), e)
    

    erfordert vom Benutzer, mehr Symbole gleichzeitig im Kurzzeitgedächtnis zu halten als
    der sequentiell notierte

    b_ := b(c,x1)
    y_ := y(3)
    x_ := x2(d,y_)
    res := a(b_,x_,e)
    

    vielleicht nicht das beste Beispiel, aber das Prinzip wird klar.

    Die Kurzzeitgedächtnis-Leistung des Menschen ist eine kostbare Resource, da sie sehr begrenzt ist (nur wenige Bytes), und ein guter Formalismus sollte sparsam damit umgehen - das meine ich auch mit
    "Mnemonik".



  • u_ser-l schrieb:

    der sequentiell notierte

    b_ := b(c,x1)
    y_ := y(3)
    x_ := x2(d,y_)
    res := a(b_,x_,e)
    

    vielleicht nicht das beste Beispiel, aber das Prinzip wird klar.

    -- Haskell
    let b_ = b c x1
        y_ = y 3
        x_ = x2 d y_
    in a b_ x_ e
    


  • ich meine mit "sequentiell" schon, daß die Funktionen y, x, a von einer Art sein, daß sie nur hintereinander ausgeführt werden können sollen, aber zugegeben, das Beispiel ist nicht so gut 🙂



  • Wenn du das meintest, warum schreibst du dann was von der Zahl der Symbole, mit denen das Kurzzeitgedächtnis gleichzeitig hantieren muss? Das sind vollkommen separate Probleme. Das eine löst man durch Einführung von Zwischenvariablen, was in funktionalen Sprachen üblicherweise durch das let-Konstrukt (in Haskell auch nachgestelltes where) geschieht, das andere in Sprachen mit eager evaluation (wie ML oder Scheme) genauso wie in imperativen Sprachen und in Haskell durch Monaden.


Anmelden zum Antworten