• [^] # Re: Moi == pas doué, je suppose....

    Posté par (site web personnel) . En réponse au journal Haskell et le tri. Évalué à 7.

    Je te remercie pour ta critique et je vais me permettre d'y répondre.

    Prof, c'est manifestement ni mon métier (sachant que, pour le moment, j'ai pas de taf, si ça veut dire quelque chose), ni le tien.

    Et merde... 30% de mon salaire vient de mes enseignements à l'université... Mais bon, comme on dit, "Ceux qui savent font, les autres enseignent".

    Je ne dis pas ça pour t'empêcher de faire de nouveaux journaux, au contraire... et puis merde, je vais balancer le reste sans politiquement correct, et faire confiance à ton sens critique, puisqu'après tout, tu postes sur linuxfr.

    Pas de souci ;)

    Avant de continuer je voulais mettre un truc au point. Ce n'est pas un cours d'introduction à Haskell, pour cela il y en a des dizaines écrits par des gens plus brillants que moi et avec de beaux dessins comme LHAH.

    La première chose qu'il me semble importante de noter, c'est l'aspect très... matheux, du langage. C'est con à dire, mais, moi, j'ai jamais été un matheux. Ouai, ok, j'ai un bac sti "génie" électronique, mais je me tapais des 8 en maths, quand, en physique je tapais du 13+ et du 15+ en élec.

    Il n'y a rien de matheux dans mon article, peut-être la notion de relation d'ordre et les monoids, mais j'ai juste donné un nom à un concept connu (l'addition) et montré que ce concept est plus général et que Haskell propose une façon standard de le manipuler. Ca ne choque personne que le + soit utilisé en python ou en C++ pour concaténer des chaines de caractères, mais si je t'explique que c'est parce que on peut voir une similarité entre le + des chaînes de caractère et celui des entiers, que cette similarité se nomme Monoid et que les développeurs d'Haskell ont choisis l'opérateur <> pour la représenter et ainsi éviter la confusion avec le +.

    Soyons sérieux, un instant. Ta fonction, elle prend [a] en argument. Soit. dans ton exemple de déclaration, je vois le [a] 2 fois. Quelle occurrence est donc le paramètre, histoire que l'on puisse au moins deviner quelle autre occurrence est le retour?

    Je l'admet, je ne prend personne par la main dans ce passage et la clarification qui est quelques paragraphes plus bas arrive trop tard, c'est clairement une erreur de pédagogie de ma part. a -> b, a est le paramètre, b est la valeur de retour, la flèche indiquant que a donne b.

    (Remarque sur l'orthographe, rien à dire, j'ai laissé quelques coquilles. Moi c'était plutôt du genre 15+ en math et 2 en français et en physique, chacun son handicap ;)

    Le coup du ordonnable (2n, au passage, me dit mon correcteur), on le remarque comment?

    Grâce à la contrainte Ord a en début de ligne. Je n'ai pas voulu faire un cours de syntaxe Haskell très précis et j'ai supposé que ma phrase "On remarque que a est Ordonable" laisserait comprendre la relation entre Ord et Ordonable...

    Encore une fois, tu me rejette dans mon ignorance crasse, et ça me semble mauvais en terme de pédagogie.

    Oui, toutes mes excuses, tu n'imagines pas le temps que j'ai passé à rajouter des paragraphs pour rendre mon histoire plus clair. Au début je voulais juste hurler "Énorme, (->) est une instance de Monoid et cela permet de faire du tri de façon trop super"...

    Ensuite, une "liste simplement chaînées" (j'ai reproduit le 's' après quelques secondes d'hésitations, parce que ça me fait chier de jouer les nazi du french) et non modifiable. Le haskell semble séduire quelques utilisateur du C++, mais je n'ai jamais compris (ni cherché) pourquoi? Ok, une liste simple, ça veut dire 1 pointeur de moins par élément, soit 8 bytes (ou octets en français, mais le terme est moins précis, niveau sémantique, un byte n'étant pas forcément constitués de 8 bits) et dans le cas de haskell c'est du const. Bien. Pourquoi pas. Mais dans ce cas, quel est l'impact en terme de perfs lorsqu'une fonction à pour simple but de construire une liste chaînée? Doit-elle être copiée-collée? J'espère bien que non.

    Toi tu fais partie de ces étudiants qui n'aime pas comprendre l'ensemble et qui s'attachent aux points de détails. C'est intéressant ;) Ton "pourquoi" est vague donc je vais essayer de répondre à de nombreuses questions possibles :

    • Pourquoi non mutable ? C'est subjectif. Parce que on aime bien cela en fonctionnel et surtout en Haskell. Cela permet de ne pas se demander qui modifie quoi. Cela simplifie le parallélisme (personne ne va venir modifier ta donnée sans te prévenir) donc on préfère commencer non mutable et introduire de la mutabilité contrôlée quand c'est nécessaire. Cela rend aussi les tests plus simple (pas d'état caché) et le raisonnement sur le code plus simple, car il n'y a pas de modification d'état caché possible.
    • Doit elle être copiée collée en permanence ? Non, de temps en temps plus qu'en C++, de temps en temps moins. Exemple, soit une liste chainée list0 qui contient A, B et C, les pointeurs étant representés par des -> :
    list0 -> A -> B -> C -> FIN
    

    Si tu veux ajouter Z en tête de liste pour créer la liste list1, tu peux crée une nouvelle liste et faire pointer Z vers le A d'avant, tu auras :

    list1 -> Z -> list0
    

    Mais pas de copie, puisque Z pointe vers l'ancienne liste en mémoire. Il y a un partage de A B et C entre les deux listes. Si tu veux maintenant supprimer un élément de list0, tu vas faire pointer ta nouvelle liste vers le B.

    • Pourquoi pas doublement chaînée ? Parce que ce n'est pas possible en non mutable, car tu devrais modifier le nœud d'après pour lui faire pointer sur le nœud d'avant, donc en fait tu devrais crée un nouveau nœud, qui invaliderait le pointeur du nœud encore avant, et ainsi de suite...
    • Performance. C'est LENT, comme le diable, mais tout autant que les listes chaînées en C++ ou en python. C'est pour cela que on a aussi des vecteurs et d'autres structures et que le compilateur est généralement pas trop bête, quand tu "crée" un nouveau vecteur en changeant juste une case, il peut utiliser l'ancien si celui-ci n'est plus nécessaire. La plupart des mes algos en Haskell sont en moyenne 2x plus lent que du c++ optimisés, ce qui est intolerable dans mon domaine d'activité, mais est carrément supportable autre part ;)
    • Performance. Haskell est paresseux et les listes sont rarement utilisées comme conteneur, mais plutôt comme générateur de valeur. Souvent cela permet d'exprimer des algorithmes complexes simplement tout en ayant des performances très bonnes.
    • En résumé, c'est un paradigme. Certains algorithmes s'expriment super bien en non mutable, d'autres moins. Certains ne sont pas possibles dans l'un ou l'autre paradigme. A titre de comparaison, à l'université, j'ai fais deux groupe dans une UE, la première semaine, j'ai fais à un groupe des arbres binaires mutables et à l'autre groupe des arbres binaires non mutable. Et bien le groupe mutable galérait encore avec les pointeurs mutée pour faire une simple insertion tandis que le groupe non mutable n'avait eu aucun problème. Cela dépend des fois.

    Bref, ton journal est bien trop... hum... c'est cru, mais mon sentiment, alors, voilà: imbu de ta connaissance.

    Le drame de ma vie, je suis un pauvre type dépressif sans aucune confiance en moi et tout le monde me reproche d'être trop confiant. Désolé si je t'ai donné ce sentiment.

    Le C, à imposé sa syntaxe comme un quasi-standard. Le pascal aussi. Les paradigmes de ces langages sont peut-être moins efficaces (ref. souhaitée) mais plus adaptés à la façon de penser des gens normaux (ref souhaitée aussi), c'est à dire pas théoriciens des langages ni matheux (ref souhaitée encore, notez comme je m'auto-troll).

    Excuses moi de te dire cela, mais tu es biaisé par ton bagage et tu penses que la façon de penser des gens normaux est la tienne. Mais les gens normaux qui n'ont jamais programmé, je peux t'assurer qu'ils ne pensent ni en C ni en Haskell. On peut troller autant que on veut, mais en haskell l'ordre sur la signature d'une fonction vas de la gauche vers la droite, en premier les arguments, à la fin le résultat. En C c'est le résultat en premier, et après les arguments, sauf en C++ où on peut aussi mettre le résultat à la fin. Et une convention fait que on trouve des résultats en argument au milieu... Je rentre dans le troll, mais je n'ai jamais trouvé l'ordre des arguments en C++, python, java, ... "Naturel". Et pourtant j'ai utilisé ces langages avant Haskell et la plupart sont plus jeunes que Haskell. Essaye de te rappeler tes débuts en programmation et ce sur quoi tu bloquais et tu verras que rien n'est naturel ;) Mes bêtes noires à moi c'était la post et pré incrémentation (a++ ou ++a), les constructeurs en C++ et les boucles...

    Une question simple, pour clore ce déjà trop long post: pourquoi ne pas nous montrer le traditionnel hello world, en C, puis en C++ (2 langages différents pour moi, bien que très proches), puis en JS (à la mode), python (idem), et enfin haskell?
    Même si le hello world ne montre quasiment rien du langage, il permets au moins aux devs de savoir comment écrire un programme qui ne fait rien. C'est, l'ai de rien, très important de savoir faire un truc qui ne fait rien avec le minimum de code. En fait, c'est la base pour savoir écrire un programme qui fait uniquement ce qu'on lui demande.

    Mais je ne faisais pas un cours d'Haskell... Je me suis embêté à comparer 2 langages (python et Haskell) et j'aurais du en comparer plus en refaisant une introduction complète sur la syntaxe et les subtilités de chaque langage...

    Mais si tu y tient, voila l'implementation comparée de mon histoire de tri en Haskell et en C++... Je sais lequel est le plus naturel pour moi ;)

    #include <iostream>
    #include <algorithm>
    #include <string>
    #include <vector>
    // Mon type
    struct Etudiant
    {
     std::string nom;
     float note;
    };
    // Et sa fonction d'affichage
    std::ostream& operator<<(std::ostream &stream, const Etudiant &etu)
    {
     stream << "Etudiant{" << etu.nom << ", " << etu.note << "}";
     return stream;
    }
    int main()
    {
     std::cout << "Hello World" << std::endl;
     // Création de la liste, j'ai utilisé un vecteur ici contre une liste en Haskell, c'est débatable.
     std::vector<Etudiant> etudiants{{"Batman", 20}, {"Hulk", 12}, {"Chuck Norris", 12}};
     // Tri suivant les notes descendant et les noms ascendants
     std::sort(std::begin(etudiants), std::end(etudiants), [](const Etudiant &etu0, const Etudiant &etu1)
     {
     if(etu0.note == etu1.note)
     {
     // second critère de tri: le nom, ascendant
     return etu0.nom < etu1.nom;
     }
     else
     {
     // premier critère de tri: la note, descendant
     // ATTENTION, c'est normal d'avoir etu1 < etu0
     return etu1.note < etu0.note;
     }
     });
     // affichage de la liste triée
     std::cout << "{";
     for(auto &&item : etudiants)
     {
     std::cout << item;
     std::cout << ", ";
     }
     std::cout << "}";
     std::cout << std::endl;
     return 0;
    }
    import Data.List (sortBy)
    import Data.Monoid ((<>))
    import Data.Ord (comparing)
    -- Fonctions utilitaires pour rendre le code plus lisible
    ascending = comparing
    descending = flip . comparing
    -- Mon type étudiant et sa fonction d'affichage automatiquement derivée
    data Etudiant = Etudiant {nom :: String, note :: Float} deriving (Show)
    -- Fonction main
    -- petite subtilité du haskell, il y a une distinction entre les données pures dans le let
    -- et les effets de bords dans le do. Ici je mens et je cache pleins de choses, mais bon ;)
    main = let
     -- ma liste d'étudiant
     etudiants = [Etudiant "Batman" 20, Etudiant "Hulk" 12, Etudiant "Chuck Norris" 12]
     -- Que je tri par ordre descendant de note et ascendant de noms...
     -- Ce commentaire est il inutile ?
     sortedEtudiants = sortBy (descending note <> ascending nom) etudiants
     in do
     -- Affichage
     putStrLn "Hello World"
     -- Il faut appeler show pour convertir en String un étudiant
     putStrLn (show sortedEtudiants)

    PS: je n'ai lu que le court passage que j'ai cité. Ça m'a déjà semblé trop inaccessible, trop chiant. Cool, on est dredi.

    Merci d'avoir pris le temps de donner ton avis sur le début. J'espere que mes réponses t’intéresserons.