Guillaum a écrit 472 commentaires

  • [^] # Re: typage statique automatique ?

    Posté par  (site web personnel) . En réponse au journal Typage statique pour Python. Évalué à 2.

    C'est vraiment des interfaces, en haskell cela se nomme des typeclass. Sauf qu'elles sont définie de façon découplée du type associé, mais l'association est explicite.

    Par example, si je définis :

    class Shape t where
        area :: t -> Float

    Et que j'ai le type :

    data Square = Square {side :: Float}

    Alors je peux associer la typeclass au type avec :

    instance Shape Square where
        area square = let
                         c = side square
                      in c * c

    D'autre part, et c'est là que je me suis fourvoyé, je pensais que tu n'avais pas besoin de définir d'interface Num (que ta méthode pourrait prendre Num ou Bar), mais pour ça je présume qu'il faut explicitement dire qu'elle prend l'un ou l'autre.

    Tu n'as pas besoin de définir que la fonction prend un Num ou un Bar, c'est l'inférence de type qui le fait tout seul (mais tu peux lui forcer la main en faisant quelque chose de plus restrictif). Donc en effet c'est en quelque sort du duck-typing statiquement typé. Example :

    foo s = area s * 2

    aura comme type foo :: Shape t => t -> Float

    Ici n'importe quelle type qui implémente l'interface Shape fonctionne avec foo`.

  • [^] # Re: typage statique automatique ?

    Posté par  (site web personnel) . En réponse au journal Typage statique pour Python. Évalué à 5.

    En pratique il y a l'inférence de type qui fait cela très bien. Dans un langage comme Haskell, tu peux ne jamais écrire un bout de type, mais avoir une inférence qui te calcul le type tout seul et le vérifie à la compilation.

    Par example une fonction qui calcul la somme des éléments d'une liste :

    sum list = if null list
                 then 0
                 else (head list) + sum (tail list)

    Note, dans la vraie vie je ne l'écrirais pas comme cela, mais plutôt :

    sum [] = 0
    sum (x:xs) = x + sum xs

    Enfin bon, le compilateur infère tout seul le type de sum comme étant Num a => [a] -> a où dit autrement, une fonction qui prend une liste de a ([a]) et renvoie un a avec la contrainte que le a est un truc qui suit une "interface" Num.

    De mon point de vue limité, cela donne la facilité d'écriture du code, tu ne te tracasses pas avec les types, mais au final tu as les outils du typage statique qui sont présents.

  • [^] # Re: Fonction

    Posté par  (site web personnel) . En réponse au journal Plonk. Évalué à 2.

    Et tout ces gens qui se moquent d'haskell ou autre à cause de la présence du mot-clé let vont pouvoir maintenant aussi se moquer de javascript ;)

  • [^] # Re: Le point de vue d'un développeur

    Posté par  (site web personnel) . En réponse au journal Traduction des logiciels libres. Évalué à 7.

    Pour le débat sur la traduction ponctuelle versus accompagnement longue durée, je suis tout à fait d'accord avec toi. Le monde de l'informatique est rempli de projet prometteur non maintenus.

    Concernant ta vision de la différence entre langage de script et langage compilé je ne suis absolument pas d'accord avec toi pour plusieurs raisons.

    En premier lieu, il n'y a pas que ELF dans la liste de dépendances d'un programme compilé, il y a une tonne de librairies qui peuvent changer d'ABI ou d'API et il y a aussi le compilateur et les librairies standards qui évoluent. Dernièrement, le passage à c++11 a cassé beaucoup de code. La librairie standard C++ a évoluée de façon non compatible avec l'existant. Pas plus tard que ce matin, j'ai fais évoluer la toolchain de notre entreprise de gcc 5 à gcc 6 et de clang 3.7 à clang 3.8 et j'ai réalisé une trentaine de commit lors de ce changement. Trois étaient nécessaires pour que cela compile !

    Tu fais la différence entre langages compilés et langages de script avec un interpréteur, mais il existent de nombreux langages compilés qui ont un interpréteur (ou une VM, un runtime), comme Java, ou haskell, … La pluspart des langages "de script" que je connais sont compilées d'une manière où d'une autre. Python est compilé en byte-code intermédiaire pour sa VM mais il existe des projets de génération de code natif. Il existe des interpréteurs pour le C… Bref, la définition de langage de script est plus que floue.

    Il en ressort que dans le cas d'un langage compilé, une seule branche sur le repository suffit à sa seule maintenance contrairement au langage de script qui doit être maintenu dans l'ancienne version de l'interpréteur, celle en cours et celle à venir. Donc 3 branches au lieu d'une.

    Actuellement il est "simple" de garantir une unique base de code pour des technologies comme python, ruby, haskell, … qui fournissent des gestionnaire de dépendances dans lequels tu peux spécifier la version de l'intepreteur à utiliser ainsi que la version spécifique de toutes les dépendances. Tu es presque garantie que cela compilera / s'executera à l'identique. Et souvent tu n'as qu'une seul projet d'intepreteur à supporter.

    D'un autre coté, en C++ par exemple, le roi des langages "compilé", il n'y a aucun consensus sur un outil de dependence / packaging / déploiement qui te donne les même garanties, il existe au moins 4 compilateurs différents que tu dois supporter (gcc / clang / visual studio / Intel cc) et des spécificité de programmation sur chaque système et des versions de compilateur différentes. Et crois moi (Je maintient une base de code C++ de plusieurs centaines de milliers de ligne qui tourne sur les 3 OS majeurs et sur plusieurs architecture matériel et sur les 4 compilateurs), on gère cela avec des directives de précompilation et c'est un enfer. Une des meilleure solution à l'heure actuelle c'est de distribuer ton logiciel dans une machine virtuelle, ou un conteneur léger (comme docker), mais ce n'est pas applicable à tous les projets.

    Donc ma conclusion c'est que j'aimerais que on arrête de catégoriser certains outils dans ce groupe magique des "langages de script" qui est très péjoratif et souvent sur des arguments qui ne tiennent pas.

    Petite histoire vraie vécue dans ma vraie vie de développeur. Etant à la R&D d'une boite, j'ai écris en python un prototype d'un outil. La solution a rapidement été validée et s'est posé la question de la mise en production du produit. Il fallait donc passer du prototype (100 lignes de python dans un seul fichier, pas de dépendance autre que l’interpréteur, portable sous linux / windows / mac OS) à la "vraie vie". Un ingénieur talentueux a donc réalisé le portage du langage de "script" en C++, parce que ce serait clairement plus rapide et plus robuste (compilé versus langage de script, y a pas photo non ?!). Quelques jour / homme plus tard, il revenait avec une version (quelques milliers de ligne de C++, qui ne marchait que dans visual studio, pas portable et avec 5 librairies en dépendance) et surtout un GROS problème. Quand ma version en python tournait en 2s, la sienne prenait 10 minutes. Impossible ! Bon, alors en fait, il avait utilisé un algo en O(N2) au lieu du mien qui était en O(N) pour la raison simple qu'il pensait que le C++ irait de toute manière plus vite. Une nouvelle dépendence plus tard (Pour avoir une hash map en C++ car il n'y en avait pas dans la standard libraire à cette époque, on aurait pu utiliser un std::set), son programme tournait en 3s… Toujours plus lent. Damned… Après profiling, c'était un problème d'allocation mémoire parce que l’algorithme créait énormément d'objet. Après avoir changé d'allocateur (à l'époque le gars a écrit un allocateur maison, mais bon, on aurait pu prendre un projet existant, je ne sais juste pas ce qui existait de bien à l'époque), le temps est passé à 2s. Pas plus rapide que la version python… Drame… En fait le temps était passé en entrée sortie avec le disque, donc il serait pas aller plus vite quoi qu'il arrive… Plus tard beaucoup de temps on été investis pour réaliser le portage de ce programme sous linux et sous mac os X et pour corriger les bugs dans l'allocateur custom. Encore plus tard, il a fallu corriger de nombreux problèmes liés à la portabilité sur différents compilateurs. Et pendant ce temps là la version python marchait toujours car elle servait de test de non régression ;)

    Alors oui, ce cas est extrême, mais c'est l’exemple même de la peur des langages de "scripts". On peut reprocher beaucoup de choses à tous ces langages qui ne sont pas tout roses (python 3 ;), mais de là à dire que du code "compilé" à une meilleure durée de vie… Cela n'a rien à voir ;)

  • [^] # Re: Le point de vue d'un développeur

    Posté par  (site web personnel) . En réponse au journal Traduction des logiciels libres. Évalué à 2.

    Aujourd'hui, grosso modo, il y a deux grandes familles de langage : le langage C avec ses multiples dérivés et les langages de script comme Python. Ces langages ont, au fil des ans, développés des procédures de traduction. L'une des plus connue est gettext.

    Je supplie tout le monde de ne pas faire cette classification "langages de script". Après on se retrouve avec des gens qui pensent que le "script" (python, ruby, …) ne sert qu'à initialiser des vrais programmes (En Java / C / C++)… Je l'ai déjà vécu en école d’ingénieur et cela fait mal de se prendre une sale note parce "qu'un script n'est pas un programme professionnel".

    D'autant que dans le contexte que tu donnes, la différence entre python et C d'un point de vu gettext est minimum…

  • [^] # Re: Visualiser les réductions

    Posté par  (site web personnel) . En réponse au journal Haskell -- Évaluation paresseuse. Évalué à 4.

    C'est le mieux que je connaisse pour l'instant :

    http://chrisuehlinger.com/LambdaBubblePop/

  • [^] # Re: pour implémenter : enrober dans une fonction

    Posté par  (site web personnel) . En réponse au journal Haskell -- Évaluation paresseuse. Évalué à 2.

    Du coup, je rapproche ça à un pointeur (oui, je suis développeur c principalement…) avec le contenu de s' mis à jour dans g

    Oui mais avec quelques subtilités. Principalement l'ordonnancement des opérations est garantie. En gros tu est certain que une utilisation de s' te retourne la somme complète, et pas une somme partielle en milieu de route.

  • [^] # Re: Dernier cas

    Posté par  (site web personnel) . En réponse au journal Haskell -- Évaluation paresseuse. Évalué à 2.

    Oui, l’implémentation doit être faite avec attention pour ne pas boucler.

    let g s l =  ((map (/s) l),sum l)

    Je n'arrive pas à reproduire ta boucle en copiant ton implémentation de g. Es-tu certain que c'est bien celle que tu utilises ?

    Voici mon implémentation de g :

    g s (x:[]) = ([x / s], x)              -- cas 0
    g s (x:xs) = let (res, s') = g s xs    -- cas 1
                 in ((x / s) : res, s' + x)

    Ici, dans le cas 0, je traite la liste d'un seul élément. Le résultat c'est [x / s] et la somme pour un unique élément c'est x.

    Dans le cas 1, je traite la liste de plus d'un élément. Je réalise d'abord un appel récursif g s xs pour récupérer le résultat res sur la sous liste et la somme s' sur la sous liste. Puis je renvois le résultat courant (x / s) : res) et la somme mise à jours s' + x.

  • [^] # Re: pour implémenter : enrober dans une fonction

    Posté par  (site web personnel) . En réponse au journal Haskell -- Évaluation paresseuse. Évalué à 4.

    Je viens de réaliser une explication d'une heure et j'ai fais un raccourcis clavier à la con sur mon navigateur et j'ai tout perdu… Je me déteste.

    Ici, le s' que tu passes en argument , il vient d'où ?

    C'est autant un argument qu'un résultat. On peut s’intéresser à un cas très simple :

    blork = let l = 1:l
            in l

    Ici (:) représente l'ajout en tête d'une valeur sur une liste tel que 1:2:[3,4] == [1,2,3,4]

    On peut appeler la fonction blork avec une valeur et voir son comportement :

    blork     -- équivalent à
    l         -- équivalent à
    1:l       -- équivalent à
    1:1:l     -- équivalent à
    1:1:1:l

    J'ai crée une liste infinie de 1 avec une expression qui s'auto-référence. L'évaluation paresseuse fait que la liste ne sera jamais évaluée, seul les éléments nécessaires le seront. Si je demande avec take 2 les deux premiers éléments, il peux s’arrêter à 1:1:l sans évaluer la suite.

    La liste d'origine est donc parcourue pour créer la 2nde liste, contenant les fonctions non évaluées. Mais pour évaluer les résultats, la 2nde liste devra aussi être parcourue, non ?

    Oui. Mais de toute façon tu feras surement un traitement sur cette seconde liste, pour l'afficher par exemple. Les résultats seront évalués à ce moment. Il y a ce site que j'aime beaucoup qui permet d'observer les évaluations.

  • [^] # Re: pour implémenter : enrober dans une fonction

    Posté par  (site web personnel) . En réponse au journal Haskell -- Évaluation paresseuse. Évalué à 2.

    C'est marrant comment les seuls réponses que j'ai eu c'est "Je peux le faire en C / Javascript / Brainfuck".

    J'ai vraiment du rater mon discours parce que je trouve le dernier point vraiment intéressant, mais il n'est pas discuté. Dommage.

    Alors simplement que je ne meurs pas idiot, comment on peut faire l’exemple de structure de contrôle custom et l’exemple de parcours de liste en une fois en javascript ? A ma connaissance l'un est difficile / pas élégant / pas safe et l'autre est impossible, mais je me trompe surement.

  • [^] # Re: valeur vs pointeur

    Posté par  (site web personnel) . En réponse au journal Haskell -- Évaluation paresseuse. Évalué à 4.

    Tu es forcé de explicitement gérer toi même ces fonctions.

    Si par exemple tu veux faire quelque chose du genre :

    void main()
    {
           a(&longCalculA, &longCalculB);
           b(&longCalculA, &longCalculC);
           c(&longCalculB, &longCalculC);
    }

    Sans connaitre le contenu de a, b et c, tu ne peux avoir aucune certitude sur le nombre d'appel aux fonctions longCalculA, longCalculB.

  • [^] # Re: astuce

    Posté par  (site web personnel) . En réponse au journal Haskell -- Évaluation paresseuse. Évalué à 2.

    Au départ c'est ce que je voulais faire, mais +s ne prend que le temps cpu en compte ;) Et comme ma fonction longId est un threadDelay, cela ne marche pas ;(

    longID x = unsafePerformIO (threadDelay 1000000 >> return x)
  • [^] # Re: pour implémenter : enrober dans une fonction

    Posté par  (site web personnel) . En réponse au journal Haskell -- Évaluation paresseuse. Évalué à 2.

    Ce que j'aime en haskell c'est le coté simple de la syntaxe à ce niveau, tu n'as pas à te préoccuper de qui va évaluer ton thunk, où qu'il soit évalué plusieurs fois.. Tu peux écrire une fonction qui marche sur n'importe quelle type de donnée que ce soit une donnée lazy ou pas.

    Alors il est certain que cela a des défauts par certains cotés.

  • # Disposition clavier par érgonomique

    Posté par  (site web personnel) . En réponse au journal SSH Tron. Évalué à 6.

    Remember to use WASD to move!

    Je ne comprend pas ces touches, elles sont placées n'importe où…

  • [^] # Re: Skydive...

    Posté par  (site web personnel) . En réponse à la dépêche Skydive, un nouvel outil d’analyse de votre réseau. Évalué à 2.

    Le parachute ne sert qu'à pouvoir recommencer.

    Tellement pas d'accord. Le parachute fait partie intégral de la pratique. Le moment sous voile est agréable, c'est beau, c'est lent, c'est rapide, c'est très rapide, c'est loin des autres, c'est proche des autres, c'est posé sur la cible, c'est posé hors zone. C'est une sous discipline a part entière qui me procure autant de plaisir que la partie chute.

  • [^] # Re: Skydive...

    Posté par  (site web personnel) . En réponse à la dépêche Skydive, un nouvel outil d’analyse de votre réseau. Évalué à 3.

    Il me faut un sticker linuxfr à coller sur mon casque pour pouvoir se reconnaître entre moules volantes.

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

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

    Qu'entends-tu par union sécurisée? Une union avec de la "RTTI"?

    Exemple :

    data Object = Square Float | Rectangle Float Float

    Ici, le type Object peut-être soit un carré Square avec un argument Float (le coté), soit un Rectangle avec deux arguments Float (les deux cotés). Pour faire la difference entre les différents objets, tu es forcé de "pattern match", examples :

    area :: Object -> Float
    area (Square c) = c * c
    area (Rectangle c0 c1) = c0 * c1
    
    -- ou
    
    area' :: Object -> Float
    area' object = case object of
        (Square c) -> c * c
        (Rectangle c0 c1) = c0 * c1

    Ici tu ne peux pas te tromper, tu ne peux pas utiliser un carré à la place d'un rectangle.

    Example plus avancé, si tu veux faire une fonction qui peut ne pas renvoyer la valeur, tu fais sois appel à une valeur "sentinelle" soit tu passe par un booléen en retour. Exemple avec une fonction find qui te renvoie la position dans un tableau d'un élément.

    // Valeur sentinelle, c'est l'approche stl qui renvoie std::end()
    // Si l’élément est trouvé, cela renvoi son index dans le vecteur
    // Sinon, cela renvoie "-1"
    template<typename T>
    int find(const std::vector<T> &v, const T value);
    
    
    // Approche booléenne
    // Si l’élément est trouvé, cela renvoie true et met l'index dans `result`
    // Sinon, cela renvoi false et la valeur de result est indéterminé
    template<typename T>
    bool find(const std::vector<T> &v, const T value, int &result);

    Chaque approche a ses limitation. L'approche "sentinelle" fait que tu peux utiliser une valeur qui n'a aucun sens et l'approche Booléen fait que tu peux utiliser par erreur une valeur non initialisée.

    En Haskell (et Rust, et OCaml, et sans doute d'autres), tu vas utiliser une "union typée safe" représentant le résultat où son absence :

    data Maybe t = Nothing | Just t

    Ici c'est un type polymorphique paramétré par t. Il peut donc contenir soit Nothing, soit Just une valeur de type t.

    Maintenant la fonction find :

    -- Cette fonction prend une liste de t `[t]`, une valeur
    -- de type `t` à trouver et renvoie `Just offset` si il est trouvé, ou alors `Nothing`
    find :: [t] -> t -> Maybe Int

    Lors de l'utilisation, tu n'as pas de valeur "sentinelle" abstraite ni de valeur non initialisée et c'est garantie par la compilation :

    case (find maListe maValeur) of
       Just offset -> "La valeur est trouvée à l'offset: " ++ show (offset)
       Nothing -> "Valeur non trouvée"

    Ici la branche Nothing n'a pas le même scope que l'autre branche, ainsi tu ne peux pas utiliser offset dans la mauvaise branche.

    Note que tu peux faire quelque chose de proche avec les exceptions dans de nombreux langage, mais ce n'est pas aussi propre car l'exception n’apparaît pas dans le type de la fonction et n'est pas forcement vérifiée par le compilateur.

    Le prochain langage que j'apprendrais, ce sera sûrement Rust, il a pas mal de points intéressants à ce qu'il semble. (pas de GC, se base sur des techniques éprouvées, multi-paradigmes, pas d'héritage direct du C)

    Rust est assez extra en effet. C'est pas aussi beau et pure que Haskell à mon sens, mais c'est un beau compromis.

    (Je fournis des examples si vous voulez).

    Je suis preneur, à titre de curiosité.

    C'était les exemples de compositions. Continuons sur le Maybe que j'ai décris quelques lignes plus haut. On va supposer que nous avons une fonction getResult :: Int -> Maybe Int qui renvoie peut-être un Int. Tu veux renvoyer un Maybe contenant le résultat multiplié par deux ou Nothing si le résultat n'existe pas. Une implementation naive serait :

    case (getResult v) of
            Just res -> Just (res * 2)
            Nothing -> Nothing

    Cela devient vite lourd. Mais tu as pleins de fonction pour composer directement dans le Maybe, comme la fonction fmap ou <$> ce qui à la place donne de façon totalement équivalente :

    (*2) <$> (getResult v)

    Cela te permet de remonter au cran d'au dessus la gestion de l’échec. Malheureusement cela peut vite devenir un peu l'horreur si tu dois combiner plusieurs fonctions. Example, tu as 3 fonctions pour gérer tes utilisateurs qui associe leur Id à leur nom, prénom et leur salaire. Toutes ces fonctions peuvent échouer pour une raison ou une autre. Tu veux une fonction qui connaissant un Id te retourne une String présentant l'employé, ou Nothing si ce n'est pas possible :

    getLastName :: Id -> Maybe String
    getFirstName :: Id -> Maybe String
    getSalary :: Id -> Maybe Int
    
    getGreetings :: Id -> Maybe String
    getGreetings id = case getFirstName id of
                        Nothing -> Nothing
                        Just firstName -> case getLastName id of
                             Nothing -> Nothing
                             Just lastName -> case getSalary id of
                                   Nothing -> Nothing
                                   Just salary -> Just ("Bonjour, " ++ firstName ++ " " ++ lastName ++ ". Vous gagnez " ++ (show salary) ++ " peanuts / jours.")

    C'est lourd. Il y a d'autres solutions, mais rien de bien sympathique, sauf la syntaxe do qui permet d'exprimer cela :

    getGreetings :: Id -> Maybe String
    getGreetings id = do
        firstName <- getFirstName id
        lastName <- getLastName id
        salary <- getSalary id
    
        Just ("Bonjour, " ++ firstName ++ " " ++ lastName ++ ". Vous gagnez " ++ (show salary) ++ " peanuts / jours.")

    Ici le <- signifie de sortir le résultat du Maybe où d’arrêter le calcul directement si celui-ci contient un Nothing. Cette syntaxe est polymorphique et dépend du type manipulé à condition que celui-ci correspond à une interface (la fameuse Monad). Comme cela tu peux faire une API très propre, tu pourrais imaginer une API de réseau :

    getRessource host port login password ressourceId = do
        ip <- resolveHost host
        connection <- connect ip port
        auth <- authenticate connection (login, password)
        downloadRessource connection auth ressourceId

    Sachant que chaque fonction peut échouer sur le chemin en renvoyant un type d'erreur, tu peux crée ta fonction de plus haut niveau getRessource qui te renverra soit l'erreur soit la ressource finale, sans devoir écrire la tuyauterie et l’enchaînement de cas pour gérer toutes les erreurs.

    Perso, je pense que quand on fait une fonction qui prend en paramètre un jeu de constantes, le plus simple est de définir une enum qui les définit, et si il s'avère qu'un utilisateur normal (du logiciel et non de la lib) peut impacter dessus via de la config, alors on file une fonction de conversion string<->enum.

    Oui ;) Et si on pouvait avoir des vrais enums typés (i.e: pas de faux int) ce serait mieux ;)

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

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

    Par défaut c'était beaucoup dire, je faisais référence par exemple au fait que les tableaux sont souvent passés par référence, et par exemple en Python dans l'exemple donné par Guillaum une copie b = a d'un tableau ne crée pas une copie du tableau, juste une copie de la référence au tableau (ce qui peut être surprenant, car la même syntaxe crée une vraie copie pour les nombres).

    Techniquement ce n'est pas = qui est en faute ici, mais la sémantique de += ;)

    >>> a = [1,2,3]
    >>> b = a
    >>> a = a + [4,5,6]
    >>> a
    [1, 2, 3, 4, 5, 6]
    >>> b
    [1, 2, 3]

    Pour la suite je répond au deux ;)

    Je pense que c'est justement là le point le plus difficile en programmation: exposer une interface assez simple et claire pour être compréhensible rapidement. Je doute que changer de langage change ça, c'est plus du niveau de l'architecture logicielle?

    Le langage peut aider avec certaines construction syntaxiques. Par example, l'absence de type algébriques en C++, (i.e. des unions "safe") ne permet pas de facilement retourner un "optional". Boost et c++17 proposent des optionals, mais ils sont limités car il n'y a pas de pattern matching et donc tu peux te tromper facilement.

    En Haskell ou Rust ou OCaml, tu peux retourner un Maybe, un type qui contient soit ta valeur de retour Just v soit Nothing et pour t'en servir tu es forcé de pattern matché dessus. C'est simple, clair, sécurisé et sympatique, et il faut un support du langage pour. Après tu ajoutes la syntaxe do en Haskell qui, de ce que je sais, est plutôt unique dans son genre, et tu peut t'exprimer encore plus clairement. (Je fournis des examples si vous voulez).

    Ce que je veux dire c'est que si l'interface est pas claire, tu risque de passer un pire moment en Haskell (pas forcément non plus), mais surtout que même si elle est bien faite, il y a plus de chances qu'elle fasse appel à des notions théoriques nouvelles pour être comprise (par exemple la notion d'Arrow permet de voire certaines choses comme faire un filtre sur du xml de façon assez élégante, mais on comprend pas immédiatement pourquoi ça peut être bien, d'ailleurs je ne me souviens plus très bien).

    Oui, c'est vrai, mais il n'est pas forcement nécessaire de comprendre une abstraction pour s'en servir et l'utiliser. Prend l'exemple des parseurs type Parsec, je trouve la syntaxe plutôt simple… Pourtant derrière cela utilise des Monades plutôt trapues.

    Et puis après, d'expérience, j'ai l'impression que plus un langage propose des moyens de documenter automatiquement et formellement (signatures des fonctions, etc.), moins il y a d'exemples dans la doc en moyenne (c'est loin d'être un théorème ce que je dis là, juste une impression et qui a sans doute plus à voir avec les habitudes et traditions du public du langage en question, il y a des exceptions).

    Je suis malheureusement d'accord avec toi, et surtout, en tout cas dans le cas d'Haskell, les auteurs pensent que cracher une phrase genre "Cette libraire gère des endo functors polymorphiques de rang 4 isomorphiques à des bifunctors tribales, cf. types." va aider ;)

    Mais clairement le même problème existe dans de nombreuses librairies de la vraie vie en python ou en c++. Dernièrement avec une librairie standard de l'industrie, une fonction qui prend en paramètre une chaîne de caractère pouvant avoir 10 valeurs différentes. Pas documentée. Je dois regarder le code source du binding python pour voir que la chaîne est utilisée tel qu'elle pour construire une constante, genre constante = CONSTANTES["MODE_" + laChaine] et passée à la lib C++ associée qui n'est pas documentée pour cette fonction si ce n'est qu'elle prend une constante int en paramètre. Je regarde le code source de la lib C++ et après être descendu de 3 appels de fonction, je trouve enfin la comparaison avec des constantes et grâce à la lecture du code j'en deduis celle qui fait ce que je veux… J'aurais eu une fonction non documenté mais avec un type sérieux, j'aurais été plus heureux ;)

  • [^] # Re: merci

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

    Du coup, le tri se fait dans le même sens pour les deux membres, c'est la même erreur que j'ai faite moi-même, non?

    Non, j'ai inversé l'ordre des champs ;)

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

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

    Tout n'est pas rose dans la surcharge d'opérateurs.

    C'est pour cela que certains langages (Comme Haskell) interdisent la surcharge d’opérateurs et propose, si tu penses que certains types méritent de partager une interface commune, de définir une interface et des lois pour les types qui suivent cette interface. Haskell ne garantie pas que les lois soient respectés, mais la plupart des auteurs de librairies s'y collent.

    for(auto &&item : etudiants), tu aurais pu écrire for(auto const& item : etudiants). C'est ce que j'aurai fait en fait. Ça aurait permis de pouvoir réutiliser la liste après les yeux fermés, parce que là j'ai un doute sur l'état des éléments de etudiants après coup.

    Oui, erreur de ma part, même si je fais beaucoup d'effort pour ne pas oublier de const, j'en oublie toujours. Preuve que cela devrait être pensé dans l'autre sens ;)

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

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

    Tu soulèves des points importants que je veux discuter ;) J'aime discuter, il est Dimanche, mon fils dors, j'ai rien à faire, alors discutons ;)

    Juste une chose, je ne défend pas Haskell bec et ongles, je gagne ma vie en faisant du C++, j'ai contribué et je contribue encore a Python où à des projets en Python et j'apprécie Haskell. La suite de ce message peut donner l'impression que j'essaye de montrer qu'Haskell est bien meilleurs que ces langages, je ne le pense pas, je pense juste que c'est different, mais en répondant point à point à ton message, on est tenté de faire des comparaisons, j’espère donc ne pas être jugé comme un extrémiste Haskell ;)

    D'un autre côté, Haskell n'est pas le langage fonctionnel le plus simple pour commencer. OCaml est par exemple bien plus accessible (afficher un "Hello world" ne fait pas apparaître des concepts compliqués, contrairement à Haskell avec la monade IO).

    (Je ne connais pas du tout OCaml, mais bon…, je viens de jeter un œil, mais le hello world Ocaml print_string "Hello World!\n";; me fait peur avec ses deux ;; alors que le hello world Haskell main = putStrLn "Hello World" me fait moins peur et n'introduit aucun concept compliqué… Je troll, mais bon, j'essaye de montrer à quel point tous les arguments de cette discussion sont subjectifs.)

    J'ai vraiment l'impression que Haskell fait peur alors qu'il ne devrait pas. Sans doute à cause de sa réputation de langage pour Matheux et une communauté qui n'a pas vraiment la motivation pour rendre cela accessible. J'ai aussi l'impression que beaucoup de gens oublient les concepts avancés qu'ils ont du comprendre pour écrire leur première ligne de code dans n'importe quel langage. Blague à part, je n'ai jamais fais de OCaml car en voyant que tous les étudiants de prépas "prestigieuses" en faisait, j'ai eu l'impression que c'était un langage théorique pour matheux, surtout sachant que c'était poussé par l'INRIA. J'ai donc choisis Haskell qui me semblait moins élitiste du fait de mon historique ;)

    On peut écrire beaucoup de code sans rien comprendre aux monades, sans rien comprendre à IO, à partir du moment où tu acceptes sans te poser de question que pour récupérer la valeur de retour d'une fonction à effet de bord, c'est <- et pas = et que les fonctions qui font des IO retournent leur résultat avec return.

    Dans un hello world C++, avant de pouvoir faire quoi que ce soit, tu dois faire au moins un include de iostream. Tu vas devoir mettre explicitement des types alors que tu ne sais pas forcement ce que cela signifie. Tu vas devoir utiliser des namespace (std::cout ou std::cin) ou le trop classique using namespace std. Tu vas devoir utiliser les opérateurs << et >>, et j'en passe. Tout autant de concepts compliqués que tu vas accepter en tant que débutant.

    je n'ai pas toujours l'impression que les fold_left apportent beaucoup par rapport à une boucle.

    J'étais initialement d'accord avec toi. Finalement, je trouve agréable le fait qu'un fold représente vraiment une opération de plus haut niveau. Puis une boucle, c'est ENORMEMENT de concepts d'un coup. Aux heures sombres de ma vie, j'ai enseigné à l'IUT, la fac et en école d'ingénieur à des groupes de débutant et je peux t'assurer que les boucles ce n'est pas une mince affaire, les étudiants oublient de l'initialiser, d’incrémenter l'iterateur, ne comprennent pas ce que cela fait vraiment.

    Dans le cas de Haskell, c'est aussi un peu un terrain de prototypage pour plein d'idées sur les système de types, du coup il y a plein d'extensions, c'est complexe et on se cogne à un moment ou à un autre avec des concepts théoriques.

    À l'inverse, beaucoup de dev C++ se cassent les dents sur certains concepts "théoriques" spécifiques au langage comme l'abondance de constructeurs / destructeurs spécifiques et les subtilités de leur implémentation. Je cite l'exemple de python sur la mutabilité des objets :

    >>> a = 5
    >>> b = a
    >>> b += 2
    >>> a
    5
    >>> b
    7
    >>> a = [1,2,3]
    >>> b = a
    >>> a += [4, 5, 6]
    >>> a
    [1, 2, 3, 4, 5, 6]
    >>> b
    [1, 2, 3, 4, 5, 6]

    Ça c'est deux bonnes heures d'explication à des étudiants en stress énorme… Même après plus de 12 ans de python, je me plante encore des fois sur ce genre de bêtise…

    Bref, là où je veux en venir c'est que je suis d'accord Haskell est un language complexe, comme beaucoup, mais pas forcement plus compliqué à comprendre que d'autre, c'est juste que les problèmes sont autre part.

    Ceci dit, ce qui en pratique freine le plus sont des choses pas vraiment liées aux langages en soi : le fait que le langage offre des possibilités complexes ne veut pas dire que faire des choses simples va devenir compliqué. Mais en prenant un livre un peu au hasard sur Lisp ou Haskell, il y a de bonnes chances qu'une bonne partie du livre, surtout l'introduction, soit dédiée à expliquer pourquoi le langage est si génial (super macros ou super système de types), ce qui a de bonnes chances d'arrêter quelqu'un qui n'a pas assez de patience ou de temps pour arriver au stade où il peut écrire des choses utiles de base avant de faire des choses géniales. Et puis même une fois les bases du langage maîtrisées, c'est facile de tomber sur un package documenté à l'aide seul de la signature de ses fonctions et une ligne de description pour chacune (ou zéro), et c'est pas souvent que l'on trouve des exemples tout prêts qui permettent d'emblée de se faire une idée de comment on va utiliser le truc : on attend souvent de l'utilisateur qu'il étudie l'API et réfléchisse lui-même à la bonne façon d'agencer les types, et ça fait perdre du temps.

    On en reviens au problème d’évangélisme et je suis d'accord avec toi. Mais ce n'est pas particulièrement une pathologie Haskell, comme partout, certaines libs sont bien documentées, d'autres non. Mais aux moins il y a des types expressifs qui aident et un compilateur qui gueule. Parce que les projets non documentés en python avec des API qui acceptent des chaînes de caractères à la place d'un Enum et pour lesquelles tu dois chercher dans le code source pour savoir quels sont les valeurs possibles et qui ne pètent pas le jour où la valeur choisie disparaît de la lib. Ou les API qui utilisent des types "fourre-tout", comme un vecteur à trois dimension, pour représenter des objets plus contraints, comme des directions ou des couleurs, il y en a partout et j'ai l'impression qu'en Haskell il y en a moins car les auteurs de librairies ont tendance aux types.

    Il y a aussi le fait que ce n'est pas parce qu'on commence à comprendre Haskell et faire des choses utiles facilement que l'on va être capable de lire le code des autres (si ça se trouve, ils utilisent des Lens ou des Arrows ou autre nouveauté, et il faut potasser un peu de théorie avant de pouvoir faire quelque chose :)

    Oui… Et non… Si l'abstraction est bien faite, tu peux comprendre ce que le code représente sans pour autant comprendre comment cela fonctionne. On fait cela tous les jours avec de nombreuses fonctions "simples" (Qui sait vraiment ce qui se passe lors d'un printf ?). Mais encore une fois, c'est un argument qui est valable dans n'importe quel langage. Une librairie C++ utilisant un peu de meta programmation template. Il faut énormément potasser de théorie pour avoir la moindre idée de ce qui est fait. Aller lire un peu de python "intelligent" où le code s'introspecte et se modifie tout seul en fonction du sens du vent. Aller lire un peu de javascript qui basé sur le nom de ta méthode injecte du html autour d'un tag dont la classe css correspond à ta méthode via un mapping auto généré selon des expression régulière… Il y a le même problème partout, seulement j'ai l'impression que quand quelqu'un n'est pas fan d'une technologie, il reproche ces problèmes a la technologie en question en ignorant que les autres souffrent de la même pathologie.

  • [^] # Re: merci

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

    Mais je trouve ça trivial, par contre. L'écriture du lambda… on peut considérer ça comme une fonction je suppose, je suis mitigé à ce sujet.

    Je n'ai jamais dis que on ne pouvais pas le faire en C++ attention. Mais quand tu regardes le lambda, avec l’opérateur ternaire, la subtilité sur l'ordre du test pour l'ascendant / descendant (que tu as mal pris en compte, de là à conclure que c'est source d'erreur ;) et la logique à écrire pour chaîner les éléments.

    Tient, j'ai une autre proposition marrante, en C++ :

    std::sort( std::begin(listeEtudiants), std::end(listeEtudiants.end), [](TypeEtudiant const& e1, TypeEtudiant const& e2){return std::make_tuple(e2.note, e1.nom) < std::make_tuple(e1.note, e2.nom)});

    Je pense que si on pouvais passer un conteneur d'un coup plutôt que de spécifier les bornes, ça allègerait déjà pas mal de code (perso, j'utilise les conteneurs entiers la plupart du temps…).

    C'est en discussion pour c++17 et j’espère que cela aboutira.

  • [^] # 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.

  • [^] # Re: En ce qui concerne Python

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

    Au final je suis d'accord avec toi, je crois que je suis juste nostalgique du bon vieux temps ;)

  • [^] # Re: merci

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

    Ce qui est imonde c'est cet article, mais par contre je trouve que ce code au contraire est "lisible" et "simple à maintenir" (Et je rentre dans ton troll ;)

    Pour trier une liste d'étudiant par note descendentes et par nom ascendant :

    sortBy (descending note <> ascending nom) listeEtudiants

    Je trouve cela très lisible et simple à maintenir. Après c'est le problème de tous les DSL, c'est qu'il y a une abstraction (ici <> et ce qui se cache sous ascending et descending), mais celle-ci n'est pas nécessaire à comprendre pour comprendre ce que fait réellement le code ni le modifier. Au pire on peut renommer <> en andThenSortBy pour rendre cela encore plus lisible.

    Je ne suis pas certain que je pourrais écrire plus clair en haskell et je sais que écrire aussi clair en python ou c++ me demandera d'ajouter des fonctions non triviales qui ne sont pas dans la librairie standard.