Haskell



  • ertes schrieb:

    CStoll schrieb:

    ertes schrieb:

    Low-Level-Funktionen sind in Haskell genauso anzuwenden wie in C++ auch, wahrscheinlich sogar noch leichter. Allerdings hat man da eben Funktionen und Kombinatoren mit richtigen Namen statt symbolische Operatoren (die im Endeffekt auch nur Funktionen sind). Das heißt, Haskell bringt auch die guten alten peek und poke wieder zurück. Daher entsteht oft der Eindruck, dass Haskell nicht so gut für Low-Level-Programmierung geeignet sei.

    Dann ist die Fehlersicherheit wohl eher eine Frage der Didaktik als der Sprache: Haskell-Anfänger lernen erst die sicheren WEge und dann die Dummheiten, C++-Anfänger (leider) genau umgekehrt (ich gehe hier nicht näher auf JW ein)

    Du hast immer noch das Typensystem, das dir zur Seite steht (am Anfang wohl eher im Weg, bis du lernst, damit umzugehen). Im Low-Level-Bereich wird dir Haskell allerdings nicht verbieten, einen Null-Pointer zu dereferenzieren. Andererseits kommt es wiederum normalerweise nicht vor, dass du Null-Pointer überhaupt bekommst. Nehmen wir als blödes Beispiel an, du willst eine verkettete Liste im klassischen Sinne: Es gibt also eine Variable und einen Zeiger zum nächsten Glied. In C++ hätte das letzte Glied hier einen Null-Pointer. In Haskell würdest du eher Maybe (Ptr a) benutzen und somit kommt ein Null-Pointer gar nicht vor. Der Versuch, vom letzten Glied ins nächste zu springen endet in einem Typfehler zur Kompilierzeit.

    Und jetzt erkläre mir nochmal, wie man zur Kompilierzeit eine Situation verhindenr will, die erst zur Laufzeit (wenn ich tatsächlich eine konkrete Liste in der Hand halte und durch ihre Elemente iteriere) auftreten kann.
    (und komme mir bitte nicht mit "dafür ist Haskell nicht gedacht").

    So ist es. Allerdings sind viele andere Optimierungen möglich. Beispiel: Du suchst die kleinste gerade quadratische natürliche Zahl (x² mit x ∈ ℕ), die größer als 110 ist, und nehmen wir an, es gäbe keine simple Formel dafür: Du erstellst erst eine unendliche Liste von 0 beginnend aufwärts, quadrierst alle Elemente, filterst die Ergebnisliste, sodass nur gerade Zahlen übrigbleiben, dann verwirfst du so lange den Anfang der Liste, bis das erste Element größer als 110 ist. Dieses Element ist das Ergebnis. Der Code dazu:

    head . dropWhile (<= 110) . filter even . map (^2) $ [0..]

    Durch äquivalente Umformungen wird die Liste vollständig wegoptimiert und es bleibt dieselbe Schleife übrig, die du in C++ von vornherein geschrieben hättest. Das heißt, die referentielle Transparenz wird ein C++-Programm nicht schneller machen, aber sie ermöglicht dir, neue, elegante Design-Patterns zu nutzen, ohne Performance dafür aufzugeben.

    Also quasi von hinten durch die Brust ins Auge.

    Wie gesagt, mir fallen gerade keine praktisch relevanten turing-vollständigen Probleme ein, aber das heißt nicht, daß es sie nicht gibt.

    Wie sehr mich das wundert, dass dir keine einfallen. Daran haben sich schon andere Leute die Zähne ausgebissen. Wobei – ein solches Problem fällt mir ein, sogar mit Praxisrelevanz: Schreibe ein Agda-Programm, für das unentscheidbar ist, ob es terminiert. 😉

    Du hast doch selber erklärt, daß Agda nicht mächtig genug ist, um solche unentscheidbaren Programme zu schreiben, also verzichte ich mal darauf.
    (btw, der Grund warum mir kein Problem einfällt ist wohl, daß mein Studium schon eine Weile her ist - und in der Praxis habe ich mit ganz anderen Aufgaben zu tun als mich um berechenbarkeitstheoretische Fragen zu kümmern)

    - wenn es kompiliert, funktioniert es auch: halte ich immr noch für fragwürdig

    Dazu gibt es sogar einen Wiki-Eintrag.

    Selbst der Beitrag sagt nicht aus, daß du mit Haskell alle Fehler zur Compilezeit verhindern kannst.

    - besser für Web-Anwendungen: eine Zange ist auch besser zum Nägel in die Wand schlagen als ein Schraubendreher - trotzdem nimmt man dafür einen Hammer

    Und was ist der Hammer? Etwa PHP? Python? Ruby? Das kannst du doch gar nicht einschätzen, wie du selber indirekt zugegeben hast. Tu doch nicht so, als würdest du alle Web-Frameworks der Welt kennen, denn mindestens eines davon kennst du nicht: Yesod.

    Ich bin kein Web-Entwickler (jedenfalls nicht hauptsächlich, aber ich weiß selber, daß meine Werkzeuge nicht die besten dafür sind), deshalb kann ich dazu nichts sagen.

    Ich nicht. Du hast völlig Recht: Das ist übertriebene Abstrahierung. Ich persönlich hätte aber keine der beiden Varianten gewählt, sondern diese:

    mf :: (a -> Bool) -> (b -> a) -> [b] -> [a]
    mf criteria operator = filter criteria . map operator
    

    Zwei Unterschiede: Ich habe eine Typensignatur drin, und durch die allein ist eigentlich völlig klar, was die Funktion tut. Der zweite Unterschied ist: Ich habe nur den letzten Parameter wegabstrahiert. So sieht man relativ deutlich den Datenfluss. Das ist eigentlich Sinn und Zweck der Pointfree-Schreibweise: den Datenfluss expliziter darstellen.

    Die Signatur gibt zumindest einen Ansatzpunkt, was deine Funktion machen soll, aber da bleiben immer noch zu viele Freiheiten, die damit nicht beantwortet werden. Kommen die Ergebnisse in der selben Reihenfolge vor, die die ursprüngliche Liste vorgegeben hat? Auf welche Weise wird das Filter-Kriterium angewendet (bedeutet true jetzt "übernimm das Argument in die Ergebnisliste" oder "verwirf das Argument")? Für diese Fragen benötigt man doch wieder die genaue Definition.

    Analog können wir als Beispiel diese Signatur verwenden:
    [code}f :: Ordered a => [a] -> [a][/code]
    Kannst du mir ohne die Definition sagen, was beim Aufruf f [5 7 11 3 29] herauskommen wird?



  • Und jetzt erkläre mir nochmal, wie man zur Kompilierzeit eine Situation verhindenr will, die erst zur Laufzeit (wenn ich tatsächlich eine konkrete Liste in der Hand halte und durch ihre Elemente iteriere) auftreten kann.
    (und komme mir bitte nicht mit "dafür ist Haskell nicht gedacht").

    Kurz zum Maybe (Ptr a) (aus meiner Sicht).

    Maybe ist folgendermaßen definiert:
    data Maybe a = Just a | Nothing

    D.h. Maybe (Ptr a) ist entweder Just (Ptr a) oder Nothing , d.h. falls eine C-Funktion einen ungültigen Pointer zurückgibt ( Ptr a ist teil des Foreign Function Interface) wird in Haskell daraus Nothing . Jetzt könntest du eine Funktion f schreiben, die folgendermaßen aussieht:

    f :: Maybe (Ptr a) -> a 
    f Just (Ptr a) = -- Version für einen gütligen Zeiger
    f Nothing = -- Version für einen ungültigen Zeiger
    

    Du kannst also gar nicht mit ungültigen Zeigern hantieren.

    Das könnte man auch in C++ mit Klassen und Überladung irgendwie erreichen, das macht aber niemand, weil das unnötig ineffizient ist. Stattdessen macht man eben manuelles Pattern-Matching. Mir gefällt die Haskell-Version allerdings besser.

    Selbst der Beitrag sagt nicht aus, daß du mit Haskell alle Fehler zur Compilezeit verhindern kannst.

    Du verlangst von Haskell, dass es alle Fehler zur Compilezeit verhindert? Bleib realistisch.
    Es verhindert einige (siehe Maybe (Ptr a) ), aber wie soll es denn alle verhindern?



  • was gibt eigentlich Haskell aus, wenn du den Definitionsbereich einer partiell definierten Funktion verlässt? (d.h. wenn ich für den "Nothing"-Teil nichts sinnvolles machen kann und den Teil offen lasse)

    Irgendwer schrieb:

    Selbst der Beitrag sagt nicht aus, daß du mit Haskell alle Fehler zur Compilezeit verhindern kannst.

    Du verlangst von Haskell, dass es alle Fehler zur Compilezeit verhindert? Bleib realistisch.
    Es verhindert einige (siehe Maybe (Ptr a) ), aber wie soll es denn alle verhindern?

    Ich verlange von Haskell gar nichts. Aber von den Haskell-Vertretern kam die Aussage, daß compilierte Programme fehlerfrei sind. In dem Artikel wurde gezeigt, wie man durch eine Umstellung der Semantik einige Fehler vermeiden kann - aber deswegen kann man trotzdem immer noch genug Mist bauen.



  • was gibt eigentlich Haskell aus, wenn du den Definitionsbereich einer partiell definierten Funktion verlässt? (d.h. wenn ich für den "Nothing"-Teil nichts sinnvolles machen kann und den Teil offen lasse)

    Entweder gibt's einen Pattern-Matching Fehler, d.h. du hast den Nothing Teil komplett weggelassen, oder du wirfst eine Exception oder benutzt error - genauso wie du es in C++ auch machen wirst. Ganz schön kann auch die Maybe-Monade sein, bei der nur weitergemacht wird, solange eine Funktion Just a zurückgibt, ansonsten wird direkt abgebrochen - und das alles ohne ein direktes "if" zu schreiben, da sich das alles im Bind-Operator >>= abspielt.

    Du kannst auch am immer gegen ein beliebiges Pattern matchen:

    data Bar = Foo | Bar | Baz
    f :: Bar -> Int
    f Foo = 1
    f Bar = 2
    f _   = error "Invalid value"
    

    Aber natürlich werden partiell definierte Funktionen vermieden wo es nur geht.

    Aber von den Haskell-Vertretern kam die Aussage, daß compilierte Programme fehlerfrei sind.

    Ja, mit der Aussage hat ertes wohl die Gemüter hier ziemlich in Wallungen gebracht. Ich bin kein erfahrener Haskell-Programmierer, habe aber von erfahrenen Haskellern schon häufig die Aussage gehört und gelesen, dass Programme häufig beim ersten kompilieren laufen.

    Aber vielleicht sind das auch einfach alles schlechte imperative Programmierer und Lüger noch obendrauf. 😉



  • ertes schrieb:

    head . dropWhile (<= 110) . filter even . map (^2) $ [0..]

    Durch äquivalente Umformungen wird die Liste vollständig wegoptimiert

    und in Forth kann man - bei laufender Compilierung eines Wortes (!) - den Compiler ausschalten, den Interpreter anschalten, die fragliche Zahl (min x mit 2|x und x^2>=110) vom Interpreter berechnen lassen, das Ergebnis festverdrahtet in den Maschinencode einfügen, den Compiler wieder anschalten und den Rest des Programms compilieren.

    Da wird aber nicht so ein Buhei drum gemacht wie in der funktionalen Programmierung.



  • Das Konzept "Beispiel" hast du auch nicht verstanden, oder?

    In Haskell hindert dich niemand daran obige Funktion in GHCi auszuführen und das Ergebnis direkt in den Quelltext zu schreiben. Genauso wie dich daran in C++ oder Forth keiner hindert. Den großen Vorteil in der Unterbrechbarkeit des Kompiliervorgangs sehe ich nicht...



  • Irgendwer schrieb:

    Den großen Vorteil in der Unterbrechbarkeit des Kompiliervorgangs sehe ich nicht...

    Das ist mal wieder das Blub-Paradox 🙂 Oder du siehst den Wald vor Bäumen nicht, das ist nämlich nichts anderes als Metaprogrammierung.



  • !rr!rr_. schrieb:

    und in Forth kann man - bei laufender Compilierung eines Wortes (!) - den Compiler ausschalten, den Interpreter anschalten, die fragliche Zahl (min x mit 2|x und x^2>=110) vom Interpreter berechnen lassen, das Ergebnis festverdrahtet in den Maschinencode einfügen, den Compiler wieder anschalten und den Rest des Programms compilieren.

    Zeigst Du auch hier bitte den Code, der genau das schafft?



  • Oder du siehst den Wald vor Bäumen nicht, das ist nämlich nichts anderes als Metaprogrammierung.

    So kann man das natürlich auch sehen. Ich kenne mich mit Forth nicht aus, weswegen die Frage bestehen bleibt, ob der Compiler das selber macht - also Werte (auch komplexere wie diese Funktion) vorab berechnet - oder ob ich da selber die Kompilation unterbrechen muss (so wie es !rr!rr_. beschrieben hat).



  • volkard schrieb:

    Zeigst Du auch hier bitte den Code, der genau das schafft?

    : foo [ bar ] literal ... ;
    

    "[" schaltet den Compiler aus und den Interpreter an, "]" bewirkt das Umgekehrte, und "literal" baut das vom Interpreter errechnete Ergebnis von bar festverdrahtet in das Compilat ein.

    "bar" ist ein Wort, das das gefragte min x berechnet und ist dem geneigten Leser als Übung überlassen 😃



  • komplettes Beispiel für die wo kein Forth können:

    : bar 110 1 do i dup * 110 >= i 2 mod 0= and if i unloop exit then loop ;
    : foo [ bar ] literal ;
    foo . cr  ( "12" )
    


  • Danke. Habe anscheinend viel Forth vergessen.


Anmelden zum Antworten