Haskell



  • freakC++ schrieb:

    Wie gesagt: Das ist ja nur eine Vermutung! Es kann sein, dass ich mich irre.

    spätestens, wenn du das lustige mapen und transformieren von unendlich langen Listen beginnst wirst du merken, dass da schon deutlich mehr hinter steckt.

    Ein Bekannter von mir programmiert seine eigenen Projekte immer in Haskell, ab und zu muss er aber irgendwas auf C/C++ portieren und kommt dann immer zu mir heulen wie häßlich C++ doch ist und dass in der Sprache simple Haskell 1-Zeiler dann bildschirmfüllende Funktionen sind...

    Haskell ist schon krass elegant.



  • otze schrieb:

    Ein Bekannter von mir programmiert seine eigenen Projekte immer in Haskell, ab und zu muss er aber irgendwas auf C/C++ portieren und kommt dann immer zu mir heulen wie häßlich C++ doch ist und dass in der Sprache simple Haskell 1-Zeiler dann bildschirmfüllende Funktionen sind...

    Was machen die Programme?
    Hast du mal ein Beispiel für so einen Einzeiler?



  • otze schrieb:

    Haskell ist schon krass elegant.

    Eleganz = Einfachheit x Allgemeinheit.

    Eine Programmiersprache, die I/O nur mit raffinierten Tricks aus der Kategorientheorie hinkriegt und mithilfe von Donuts veranschaulicht, ist nicht einfach, sondern unpraktikabel.



  • sagmal schrieb:

    Was machen die Programme?
    Hast du mal ein Beispiel für so einen Einzeiler?

    Das ist ne Menge Numbercrunshing stuff. Häufig irgendwelche sampling algorithmen für Wahrscheinlichkeitsverteilung. Einen solchen Einzeiler habe ich gerade nicht in der Hand, aber sowas hat er mir letztens geschickt:

    normalize w = map (/ (sum w)) w
    resampleweights particles oldP newP = normalize $ zipWith (/) (map newP particles) (map oldP particles)
    

    In C++ wäre das sowas wie:

    void normalize(Vector& w){
        double norm = std::sum(w.begin(),w.end());
        w/=norm;//angenommen wir habend en operator definiert...
    }
    Vector resampleWeights(const Vector& particles,Distribution oldP, Distribution newP){
        Vector weights(particles.size());
        for(size_t i = 0; i!= particles.size(); ++ i);
        {
            double weights[i] = oldP(particles[i])/newP(particles[i]);
        }
        normalize(weights);
        return weights;
    }
    

    @!rr!rr_.
    Ja, da muss man sich rein denken. trotzdem funktioniert es sehr gut, wenn man die Theorie verstanden hat, und so schwer ist es nicht. Man muss sich das nur so vorstellen, als wenn jede Funktion einen impliziten Parameter für den aktuellen Zustand hat und als Rückgabewert zusätzlich den aktuellen Zustand mit raus gibt. Das es so mathematisch ausgedrückt wird, liegt an der mathematischen Denkweise der Haskell programmierer.



  • Das ganze könnte man schon kürzer machen (ungetestet)

    void normalize(Vector &v) {
      Vector::value_type const sum = std::accumulate(v.begin(), v.end(), Vector::value_type());
      std::transform(v.begin(), v.end(), [sum](Vector::value_type i) { return v / sum });
    }
    
    Vector resampleWeights(Vector particles, Distribution oldP, Distribution newP){
      std::transform(particles.begin(), particles.end(), particles.begin(), [](Vector::value_type i) { return oldP(i)/newP(i); })
      normalize(particles);
      return particles;
    }
    


  • das ganze geht mit einer reinen OOP-Sprache a la Smalltalk auch in 2 Zeilen:

    weights := particles collect: [ :i | (oldP value: i) / (newP value: i) ].
    weightsNormed := weights collect: [ :x | x / weights sum ].
    

    - dafür brauche ich also kein Haskell.



  • Die Argumentation über kurze, knappe Formulierung ist einfach nicht zielführend.



  • sogar in 1 Zeile:

    weightsNormed := (particles collect: [ :i | (oldP value: i) / (newP value: i) ]) collect: [ :x | x / weights sum ].
    


  • var weights = from i in particles select oldP(i) / newP(i);
    var sum = weights.Sum();
    var weightsNormed = from i in weights select weights / sum;
    

    Hätte man auch genauso wie die Smalltalk-Version schreiben können:

    var weights = particles.Select(i => oldP(i) / newP(i));
    var weightsNormed = weights.Select(i => i / weights.Sum());
    

    aber dann würde weights.Sum() für jedes Element neuberechnet werden. Ich vermute, das ist in Smalltalk nicht anders.

    Die Einzeiler-Lösung in Smalltalk ist offensichtlich Quatsch, von welchen weights soll denn da die Summe bestimmt werden?

    Insgesamt finde ich die Haskell-Lösung am elegantesten, wegen ihrer Modularität.



  • rüdiger schrieb:

    Das ganze könnte man schon kürzer machen (ungetestet)

    Lustig, dass du dabei stark auf higher-order functions setzt, die aus funktionalen Programmiersprachen kommen.

    !rr!rr_. schrieb:

    Eine Programmiersprache, die I/O nur mit raffinierten Tricks aus der Kategorientheorie hinkriegt und mithilfe von Donuts veranschaulicht, ist nicht einfach, sondern unpraktikabel.

    Mal wieder absoluter Schwachsinn von dir...
    Inzwischen ist für mich der Haskell-Weg, nämlich jegliche Kommunikation mit der Außenwelt explizit zu machen, der schönere. Man sieht schon an der Funktionssignatur, ob IO stattfindet oder nicht. Ich muss dann nicht über Seiteneffekte nachdenken und kann sicher sein, dass ich immer das gleiche Resultat bekomme.



  • Bashar schrieb:

    von welchen weights soll denn da die Summe bestimmt werden?

    weights ist eine Abkürzung für den Ausdruck "particles collect: ...", die ich
    hier einsetzen mußte, weil mir die Zeile sonst zu lang wurde.

    Aber wenn du unbedingt willst ...

    weightsNormed := (particles collect: [ :i | (oldP value: i) / (newP value: i) ]) collect: [ :x | x / (particles collect: [ :i | (oldP value: i) / (newP value: i) ]) sum ].
    


  • funktional und 50% weniger Zeilen als die haskell-Version 🙂



  • Das ist also dein Verständnis von Eleganz? Ich hoffe mal, dass dein Smalltalk-Compiler superduperintelligent ist.



  • funktional und 50% weniger Zeilen als die haskell-Version 🙂

    xD
    Also du meinst bei der Haskell-Version hätte man normalize w = map (/ (sum w)) w nicht auch noch direkt einsetzen können?
    Mach dich nicht lächerlich. 🤡

    Es geht übrigens nicht primär um Kürze, sonder um Abstraktion und Eleganz. Haskell bietet sehr mächtige Abstraktionsmechanismen, vor allem aber komplett andere als C++ (das ist überhaupt nicht abwertend gemeint, ich mag C++ sehr).



  • Irgendwer schrieb:

    Lustig, dass du dabei stark auf higher-order functions setzt, die aus funktionalen Programmiersprachen kommen.

    Funktionszeiger gibt's schon ziemlich lange, man hat halt nur nie einen so griffigen Namen vergeben.

    Die Haskell-Version von otze ist wirklich schön, gefällt mir. Auch das Zusammenspiel von Duck Typing und statischer Typisierung in Haskell 👍



  • Mal nachgehakt schrieb:

    Irgendwer schrieb:

    Lustig, dass du dabei stark auf higher-order functions setzt, die aus funktionalen Programmiersprachen kommen.

    Funktionszeiger gibt's schon ziemlich lange, man hat halt nur nie einen so griffigen Namen vergeben.

    Funktionszeiger sind nicht ausreichend. Man braucht Closures, d.h. implementationstechnisch ein Tupel aus dem Funktionszeiger und allen "eingefangenen" Variablen.
    Der "griffige" Name higher order function dürfte übrigens deutlich älter sein als der Begriff des Funktionszeigers, den dürften schon Church&Co. in den 30ern benutzt haben.

    Die Haskell-Version von otze ist wirklich schön, gefällt mir. Auch das Zusammenspiel von Duck Typing und statischer Typisierung in Haskell 👍

    Haskell hat kein Duck Typing.



  • Bashar schrieb:

    Funktionszeiger sind nicht ausreichend. Man braucht Closures, d.h. implementationstechnisch ein Tupel aus dem Funktionszeiger und allen "eingefangenen" Variablen.

    Och jo, aber so einen weiten Schritt finde ich das nicht. In C++ darf man ja an Templatefunktionen leider keine lokal definierten Typen übergeben, sonst könnte man das auch schon 'ne Weile.
    Versteh mich nicht falsch, Higher Order Functions sind schon eine großartige Sache, aber ihr Aufkommen in den imperativen Sprachen hat imo nichts mit der Existenz der funktionalen zu tun. Genauso, wie ich den Schritt von C nach C++ logisch und wenig innovativ (wenn auch natürlich bedeutend) finde.

    Bashar schrieb:

    Der "griffige" Name higher order function dürfte übrigens deutlich älter sein als der Begriff des Funktionszeigers, den dürften schon Church&Co. in den 30ern benutzt haben.

    Oha, ok.

    Bashar schrieb:

    Haskell hat kein Duck Typing.

    Ich weiß, mein Fehler, ich meinte eigentlich die Dynamik in der Typisierung. Wenn ich da nix falsch verstanden hab, entspräche der Haskell-Code

    sum x y = x + y
    

    ja in C++0x

    template<typename T, typename U>
    auto sum( const T& t, const U& u ) -> decltype(t+u) {
        return t + u;
    }
    


  • Mal nachgehakt schrieb:

    Bashar schrieb:

    Haskell hat kein Duck Typing.

    Ich weiß, mein Fehler, ich meinte eigentlich die Dynamik in der Typisierung. Wenn ich da nix falsch verstanden hab, entspräche der Haskell-Code

    sum x y = x + y
    

    ja in C++0x

    template<typename T, typename U>
    auto sum( const T& t, const U& u ) -> decltype(t+u) {
        return t + u;
    }
    

    Nicht ganz. Templates (ohne Concepts) sind ja eigentlich statisches Duck Typing. Du setzt hier implizit voraus, dass es einen Operator + für t und u gibt. Wenn es den gibt, dann wird er benutzt. Oder, um die Parallele zum Duck Typing noch deutlicher zu machen: Der Code lässt sich genau dann compilieren, wenn t+u mit den konkreten Typen in jeder Instanz einen sinnvollen Ausdruck ergibt.
    In Haskell ist + eine Funktion mit einer bestimmten Signatur. Die ist zwar polymorph und damit für viele Typen anwendbar, aber sie erfordert, dass beide Operanden den gleichen Typ haben und dass dieser Typ eine Instanz der Typklasse Num (IIRC) ist. Dann legt sie noch fest, dass das Ergebnis auch wieder diesen Typ hat. Man kann nicht einfach irgendein + definieren und bei sum unterschieben, es muss immer über eine Instanz von Num geschehen.



  • aber ihr Aufkommen in den imperativen Sprachen hat imo nichts mit der Existenz der funktionalen zu tun.

    Genauso ist es Zufall, dass die funktionalen Sprachen anonyme Funktionen (lambdas) zuerst hatten und diese jetzt in so gut wie jeder imperativen Sprache Einzug finden?



  • higher-order functions, funktionale Programmierung und lambda hat Smalltalk auch. Da wird nur nicht so ein Theater drum gemacht, das sind da einfach Spezialfälle von Blocks:

    [ :o :x :y | o machWasMit: x undMit: y ]
    

    - und Blocks wiederum sind Objekte der Klasse BlockClosure. Bleiben wir also bei OOP 🙂


Anmelden zum Antworten