Guillaum a écrit 476 commentaires

  • [^] # Re: Pas uniquement string

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 4.

    L'initialisation par défaut a du sens et c'est évident. Je sais que les programmeurs Haskell adore rappeler au autres à quel point leur langage est supérieur à la plèbe de ce monde car il n'autorise pas l'initialisation, mais la tu fais preuve d'un poil de mauvaise fois :)

    Désolé, mon commentaire était sans doute puant à la mode élitiste Haskell, ce n'était pas mon but et je me suis mal exprimé.

    Je communique mon intérêt pour Haskell, et que ce langage influence beaucoup ma façon de travailler, mais au final le C++ reste mon outil de travail et d'enseignement. C'est un langage que j'apprécie, malgré ses défauts. Il me permet de faire mon travail alors que je ne pourrais pas le faire en Haskell pour des raisons de performance.

    Ai-je tort d'essayer d'appliquer des principes inspirés d'autres langages pour rendre mon code C++ plus robuste ? Ai-je tort de critiquer dans un but d'amélioration, un langage que j'utilise tous les jours? Par exemple, je suis très heureux de l'arrivée des std::optional en C++17, mais je trouve que l'api proposée est un échec total puisqu'elle permet beaucoup d'erreurs qu'une api différente aurait évitée et à ce niveau ils auraient pu s'inspirer d'Haskell (ou de Rust, ou de OCaml, …). Au final, je ne me servirais des std::optional qu'une fois enrobés dans une API plus sécurisée.

    Pour le reste, d'autres ont déjà montrés qu'un pointeur initialisé à null par défaut est potentiellement un comportement indéfini, et dans ce cas tout aussi dangereux qu'un pointeur non initialisé. Mais mon point n'était pas que je conseil de privilégier la non initialisation à l'initialisation par défaut. Je veux juste bannir les deux autant que possible et je pense qu'il est possible de construire des API qui apportent cette sécurité.

    Pour finir, je préfère les valeurs explicites car je pense que cela augmente la lisibilité du code. Et à ce niveau, les constructeurs par défaut ne sont pas explicite dans leur intentions. Encore une fois, quel doit être le constructeur par défaut d'une Matrice 4x4 ? d'une couleur ? d'un répertoire ? d'une image ? Je ne sais pas répondre à ces questions, et c'est pour cela que je préfère des fonctions / méthodes statiques avec des noms explicites que des constructeurs dont le comportement est arbitraire et implicite.

  • [^] # Re: Pas uniquement string

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 1.

    L'exemple flagrant est les pointeurs, notamment pointeurs de fonction. Si tu le garde non-initialisé tu as un énorme trou de sécurité potentiel.

    C'est quoi la valeur par défaut d'un pointeur de fonction qui as du sens ? nullptr, ou une fonction au hasard ? Les deux cas n'ont pas de sens et sont dangereux, bref, avoir un pointeur "default-initialised" n'as pas de sens.

    Ensuite, il n'y a absolument aucun besoin d'avoir un code instable pour aller chercher ces erreurs, n'importe quel compilateur décent te détectera une variable non-initialisée sur demande.

    Justement ;) : les valeurs non-initialisées sont moins dangereuses que les valeurs initialisée par défaut, car souvent les valeurs non initialisée vont te générer une erreur, alors que les valeurs par défaut ne vont pas t'en générer et tu continues sur un bout de code qui n'a plus de sens.

    On parle bien du cas tu as un bug dans ton code et alors que tu pensais gérer tous les cas de figure, il en manque certains et au lieu de mettre une valeur qui a du sens dans ton code, tu finis avec une valeur par défaut qui n'en a pas.

    Gni ? Tu demandes quoi là ? Evidemment que l'auteur de la classe va mettre ce qu'il désire dans le constructeur par défaut, c'est son code ! Tu veux faire quoi à la place ?

    Je ne veux pas de constructeur par défaut ! Exemple, si tu croises le bout de code suivant Color c;, quel est la valeur d'une Color ? Maintenant tu croises Color c = Color::Black(). Cette seconde solution est :

    a) Bien plus facile à lire / comprendre. Il est plus explicite. Il demande aussi moins de surcharge intellectuel au développeur qui n'a pas à retenir la liste des cas par défaut pour toutes les classes.
    b) Il est moins sujet aux changements des comportements par défaut, je détail ce point qui te faisait bondir.

    Imagine une interface de la classe Color comme suit :

    // Color Triple
    class Color
    {
    ....
    public:
        // Default Color (black)
        Color();
    
        // Init the triple with (a, b, c)
        Color(const float a, const float b, const float c);
    };

    Ici mon seul contrat concernant le constructeur par défaut c'est le petit commentaire qui dit "black". C'est faible, et demain ce constructeur par défaut peut changer très facilement pour construire une autre couleur, sans doute le blanc, car pour les besoins de ce projet on fait plus du multiplicatif sur les couleurs que de l'additif, donc le blanc semble un meilleur choix par défaut. Tu peux aussi imaginer un refactoring dans lequel on change la classe de gestion de couleur, la plupart des opérations ont la même sémantique, sauf ? Les choix arbitraire des constructeurs par défaut.

    Une autre conception pourrait donner :

    // Color Triple
    class Color
    {
    ....
    public:
        // Smart constructors
        static Color Black();
        static Color White();
    
        // Init the triple with (a, b, c)
        Color(const float a, const float b, const float c);
    };

    Dans ce cas là, pas de constructeur par défaut, et des constructeurs aux noms bien plus explicites et plus plus robustes, ce n'est pas une valeur par défaut arbitraire, c'est Black ou White, c'est écrit en dur dans le nom de la fonction, donc il faudrait vraiment être pervers pour faire un refactoring dans lequel la nouvelle classe Color renvoie du vert dans sa fonction White.

    En résumé, je préfère l'explicite et j'évite les les valeurs par défauts, les constructeurs par défauts, les arguments par défauts sur les fonction. Tout cela parce que de mon expérience, c'est trop facile de se tromper, et cela coûte trop cher. J'évite aussi les variables non initialisées (ou initialisées par défaut) qui servent de valeur de sortie d'une fonction en passage par référence, parce que c'est encore un coup à se planter. Je veux qu'à tout moment dans mon code, si j'ai accès à une variable, elle ai un sens.

  • [^] # Re: Pas uniquement string

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 0.

    Ma question n'était pas la raison technique qui fait que c'est plutôt des bits à zéro qu'autre chose. Je voulais plutôt discuter du coté totalement arbitraire des initialisation par défaut.

    Les valeur par défaut sont, à mon sens, plus dangereuses que les valeur non-initialisée. Parce que non-initialisée, cela fait souvent n'importe quoi, et dans le meilleur des cas, cela plante, et tu trouves vite le bug et au moins tu obtient un comportement instable qui t'invites à chercher une erreur. Mais lorsque c'est initialisé par défaut, tu vas obtenir un comportement stable, souvent cohérent, mais qui est totalement faux.

    De plus, les valeurs par défaut des types primitifs sont assez évidentes, mais celles de types plus élaborés sont totalement dépendants des choix des auteurs de la classe, et cela rend le code bien moins lisible. Il faut aussi voir que les valeurs par défaut peuvent changer au bon vouloir du l'auteur de la classe, et celui-ci peut penser que son changement est indolore alors qu'en fait il détruit ton code.

  • [^] # Re: Pas uniquement string

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 2.

    En C++, var m; est default-initialized.

    C'est comme dire non initialisé, mais en pire ;)

    Autant cela à un sens plus ou moins arbitraire pour certains types (genre std::vector), mais pourquoi un bool serait plus false que true ou un int mérite-t-il plus d'être 0 que 30. Tant qu'on est dans le code-smell, j'ai tendance à faire des bonds sur les classes qui proposent un constructeur par défaut vide avec des valeurs par défaut. Il faut vraiment justifier que le choix par défaut à du sens.

    Généralement je préfère une fonction static ou free avec un nom explicite pour les cas par défaut.

  • [^] # Re: Lisibilité du code

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 4.

    Question d'habitude. Quand tu commence à jouer avec des API fluent, ça se lit vite.

    En fait l’implémentation est complexe, mais l'usage est relativement simple et lisible. De mon point de vue il n'est pas forcement nécessaire de comprendre l’implémentation d'une abstraction pour l'utiliser correctement.

    Ainsi j'aime bien l'approche StringSwitch qui est très claire (à mon sens). Je trouve que l'approche à base de std::map ne devrait pas exposer la std::map directement ni le find. Je ferais un truc du genre : (Api non testé).

    template<typename Result, typename Value>
    Result Switch(Value v, std::map<Value, std::function<Result()>>);
  • [^] # Re: Pas uniquement string

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 2.

    Question de goût. Je trouve la syntaxe au contraire très clair pour du code templaté.

    La dessus, rien à dire ;) Mais bon, j'ai un peu pris l'habitude d'Haskell (ou autre). Pour le troll, comparons :

    int fact(int n)
    {
        var<int> m;
    
        Match(n)
        {
          When(0)     return 1;
          When(1)     return 1;
          When(_)     return n * fact(n-1);
        }
        EndMatch
    }
    fact n = case n of
       0 -> 1
       1 -> 1
       _ -> n * fact (n - 1)
    
    -- ou
    
    fact 0 = 1
    fact 1 = 1
    fact n = n * fact (n - 1)

    Vachement plus compact, lisible et polymorphique ;) (mais bon, je troll ;)

  • [^] # Re: Pas uniquement string

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 2.

    Je m'auto répond. Un point qui sent vraiment mauvais dans cette approche c'est que pour stocker le résultat d'un match, il faut déclarer une variable non initialisée, exemple :

    int fib2(int n)
    {
        var<int> m;
    
        Match(n)
        {
          When(1)     return 1;
          When(2)     return 1;
          When(2*m)   return sqr(fib2(m+1)) - sqr(fib2(m-1));
          When(2*m+1) return sqr(fib2(m+1)) + sqr(fib2(m));
        }
        EndMatch
    }

    Ce qui veut dire que on peut se servir d'une variable mal initialisée dans une branche. J'ai tendance à ne plus supporter çà et à refuser les revues de code qui en contiennent.

  • [^] # Re: Pas uniquement string

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 2.

    C'est pas clair à quel point c'est du C++ ou à quel point c'est une extension au standard actuel. La syntaxe est tout de même vraiment répugnante.

    Avec d'autres langages, le pattern matching c'est extrêmement puissant, concis et clair et cela fait partie des outils qu'il me manque dramatiquement quand je fais du C++.

  • # Arguments ?

    Posté par  (site web personnel) . En réponse au journal Java (EE) Sapu cépalibre.. Évalué à 10.

    Ne me parlez pas de trucs à base de Python ou pire à base de PHP SVP, c'est pas sérieux

    Peux-tu développer ?

    Note : bien que j'imagine que ma question soit surement hautement sujette à troll, ce n'est pas mon but. Je ne connais absolument pas l'écosystème Java et donc je ne peux pas vraiment comprendre pourquoi ce n'est pas sérieux. C'est juste le débat statique / dynamique ou c'est autre chose ?

  • [^] # Re: Grammaire en boucle infinie ?

    Posté par  (site web personnel) . En réponse au journal Génération de code (Python) avec Grako. Évalué à 4.

    Merci.

  • [^] # Re: Grammaire en boucle infinie ?

    Posté par  (site web personnel) . En réponse au journal Génération de code (Python) avec Grako. Évalué à 4.

    multiplication = (expression | terme) "*" (terme | expression) ; donc la première chose qu'il test c'est (expression | terme) avant de tester "*", et donc ma peur de la récursion.

    J'ai raté une étape ?

  • # Grammaire en boucle infinie ?

    Posté par  (site web personnel) . En réponse au journal Génération de code (Python) avec Grako. Évalué à 4.

    Comment la grammaire peut ne pas donner une boucle infinie ?

    Si tu veux parser 45 avec expression, il vas échouer sur le test de la présence de ( et donc vas passer dans contenu_expression puis dans multiplication puis dans expression et ainsi de suite.

  • [^] # Re: Proposition de calcul de la valeur que compte un vote selon la durée de vie restante du votant

    Posté par  (site web personnel) . En réponse au journal Démocratie et pyramide des âges. Évalué à 6.

    Les vrais programmeurs n'utilisent pas d'affectation mais seulement des équations en programmation fonctionnelle pure ;)

  • [^] # Re: Unicode et Haskell

    Posté par  (site web personnel) . En réponse à la dépêche Le compilateur GHC Haskell en version 8.0.1. Évalué à 3.

    Merci pour ces précisions. J’avoue que je me suis permis ces raccourcis car la dépêche (initialement un journal écrit entre midi et deux pendant un moment de procrastination) était déjà fort long. Comme je le disais c'est un exemple perfectible que je ne voulais pas surcharger ;)

    Notez que les opérateurs en Haskell sont des fonctions comme les autres (en dehors de leur syntaxe) et peuvent être définis dans n'importe quel module. C'est une force qui peut parfois être légèrement abusée au goût de certains… ;-)

    (lens mon amour…)

    Je me permet d'ajouter, pour tout ceux qui seraient tentés par Haskell, au début vous aller croiser quelques opérateurs dont quatre en particulier : le couple <$> et <*> ainsi que le couple >> et >>=. Ces opérateurs font initialement peur, mais il peuvent se comprendre sur des cas très simples et après s'appliquer dans de nombreux cas génériques et deviennent aussi naturel que le ; et le for peut l'être en C.

  • [^] # Re: Merci pour cette annonce

    Posté par  (site web personnel) . En réponse à la dépêche Le compilateur GHC Haskell en version 8.0.1. Évalué à 3.

    Je comprend ta réponse.

    C'est juste que je ne vois pas vraiment cela comme un problème. Au final on serait en train de reprocher à GHC de faire avancer un langage au détriment d'un standard qui stagne. Il n'y aurait pas eu de standard et une unique implémentation, comme c'est le cas pour beaucoup de langages que tu cites, cela n'aurait pas posé de problème.

  • [^] # Re: Point de vue de novice

    Posté par  (site web personnel) . En réponse à la dépêche Le compilateur GHC Haskell en version 8.0.1. Évalué à 8.

    il faut y passer beaucoup de temps, un apprentissage sérieux prendra plus de six mois.

    Un outil complexe demande forcement un apprentissage pour maitriser, mais tu peux commencer à écrire du code qui marche assez rapidement. J'ai écris un lancer de rayon dans ma première semaine d'Haskell, alors que je n'avais encore rien compris au langage (Et j'ai du tout réécrire après ;)

    il manque les déclarations de type. Celles ci ne sont pas obligatoires en Haskell mais vivement conseillées car elles permettent de mieux comprendre le code et évitent aussi les erreurs lors de la rédaction d'un programme.

    L'absence d'annotation de type est un choix de ma part pour montrer que le coté typage statique du langage n'impose pas forcement une syntaxe verbeuse. Cet exemple permet de montrer un "vrai" problème qui mixe des entrées-sorties, des structures de donnée autre que la liste et de la composition de fonctions. Internet est plein d'introduction à Haskell à base de Fibonnaci, de quick-sort (bugés) et de factorielle, mais les exemples montrent rarement d'entrées-sorties ce qui laisse sur cette fausse impression que c'est compliqué.

  • [^] # Re: Sympa la référence à docteur Folamour

    Posté par  (site web personnel) . En réponse à la dépêche Le compilateur GHC Haskell en version 8.0.1. Évalué à 2.

    Bonne séance ;)

  • [^] # Re: Merci pour cette annonce

    Posté par  (site web personnel) . En réponse à la dépêche Le compilateur GHC Haskell en version 8.0.1. Évalué à 2.

    Ce qui est un des points qui me gêne chez Haskell (mais j’aime quand même le langage par ailleurs).
    Un standard qui est là seulement pour faire joli c’est déjà moyen, mais en plus le fait qu’une grosse majorité du code qui dépends d’extension GHC rend très difficile (voir impossible) l’apparition d’un compilateur alternatif.

    Je ne connais aucune technologie qui actuellement garantie la portabilité entre différents compilateurs / système / … En C++ le standard n'est pas forcement respecté et avoir du code portable entre les différents compilateurs n'est pas si triviale que ça. En Python, il n'y a pas de standard et il existe plusieurs implémentation différentes.

    Alors si demain quelqu'un veut faire un nouveau compilateur Haskell, il lui faudra une sacrée bonne raison et si elle est suffisamment valable, je suis près à limiter mon usage de certains extensions pour assurer la portabilité.

    On pourrait aussi se dire que le standard Haskell pourrait évoluer pour intégrer ces extensions, mais au final cela ne ferait que repousser le problème qui est qu'il devient difficile avec toute technologie complexe de repartir de zéro.

  • # Autres changements interessants

    Posté par  (site web personnel) . En réponse à la dépêche Le compilateur GHC Haskell en version 8.0.1. Évalué à 10.

    Je vais compléter ma propre dépêche de nouveaux trucs intéressants que je n'avais pas encore noté ou réalisé l’intérêt.

    Amélioration du Shell ghci.

    Trois petites améliorations simple du shell :

    • Les erreurs sont maintenant listées avec de beaux caractères unicode avancés pour faire les points d'une liste.
    • Il est maintenant possible de définir dans le shell des fonctions et des noms sans devoir utiliser let. Au lieu de taper let x = 5, on peut taper x = 5.
    • Dernier point que j'apprécie particulièrement. En haskell, une erreur de type est critique et fait planter la compilation. C'est énervant lors d'un refactoring car on ne peut pas lancer ses tests unitaires tant que tout n'est pas fixé. Heureusement GHC permet de compiler avec -fdefer-type-errors qui transforme les erreurs de type en erreur au runtime. Maintenant le shell permet de charger un module normalement avec :load ou en reportant les erreurs au runtime, avec :load!.

    Extensions XTypeApplications

    Cette extension permet de spécialiser lors de l'appel une fonction polymorphique.

    Prenons par exemple la paire de fonction encode et decode de Data.Serialize (package cereal) présentée rapidement dans la dépêche.

    > :type decode
    decode :: Serialize a => ByteString -> Either String a
    > :type encode
    encode :: Serialize a => a -> ByteString

    La fonction encode prend n'importe quel type a et retourne une ByteString, bref elle serialize. La fonction decode réalise l'opération inverse, prenant une ByteString et retournant un Either String a, c'est à dire soit une erreur sous forme de String, soit un type a.

    Le problème c'est que encoder est simple, le type a est connu puisque on le passe directement en paramètre, ici avec une liste de booléens [Bool] :

    > encode [True, False, True]
    "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\ETX\SOH\NUL\SOH"

    Pour le décodage c'est plus complexe, puisque on ne connait pas le type de sortie :

    > :type decode (encode [True, False, True])
    decode (encode True) :: Serialize a => Either String a

    Très souvent le moteur d'inférence s'en sort car ce type va être utilisé directement après, par exemple :

    > (decode (encode [True, False, True])) == Right [True, False, True]
    True

    Ici on compare le contenu décodé à Right [True, False, True], ce qui veut dire qu'il a bien réussis à décoder. En écrivant cette comparaison, le type à décoder ne peut qu'être un Bool.

    Mais si on se sert après d'une fonction polymorphique pour lequel le type peut être ambigu, par exemple la fonction length qui accepte tout type de conteneur, alors il faut spécifier le type.

    Plusieurs solutions s'offrait à nous avant :

    • spécifier le type de la fonction decode. Rappel, celui-ci est ByteString -> Either String a et il faut forcer a, ce qui donne une annotation de type :: ByteString -> Either String [Bool]
    • spécifier le type de la valeur de sortie. Rappel, celui-ci est Either String a, il faut donc forcer a, ce qui donne :: Either String [Bool].

    Dans ce cas simple c'est supportable, mais dans le cas d'un type bien plus complexe, il est rébarbatif de recopier tous les types déjà connus (ici ByteString et Either String) pour ne specifier qu'un petit type polymorphique. GHC 8.0 permet de spécifier spetialement les types polymorphique d'une fonction gràce à @. Dans le cas de decode il n'y a qu'un seul type à spécifier et cela se fait donc de la façon suivante : decode @[Bool].

    Comparez les trois approches :

    (decode :: ByteString -> Either String [Bool]) (encode [True, False, True)
    (decode (encode [True, False, True])) :: Either String [Bool]
    decode @[Bool] (encode [True, False, True])

    Plus compact et plus clair.

  • [^] # Re: Erreur dernier lien

    Posté par  (site web personnel) . En réponse à la dépêche Le compilateur GHC Haskell en version 8.0.1. Évalué à 2.

    Merci. J’espère que quelqu'un fera l'édition.

    Ce lien est d'ailleurs un peu dur à suivre et j'aurais dû recommander le tutoriel liquid haskell à la place.

  • [^] # Re: typage statique automatique ?

    Posté par  (site web personnel) . En réponse au journal Typage statique pour Python. Évalué à 2. Dernière modification le 01 juin 2016 à 18:09.

    Tu as la chance de faire du haskell professionnellement, j'aimerais bien ;)

    De mon expérience amateur, je met des types sur les "top level", mais c'est tout. Il y a deux-trois cas tordus, quand tu commences à abuser de typeclass ou OverloadedLists / OverloadedStrings, mais en pratique cela ne m'a jamais trop embeté.

    Mais il est clair que ma remarque était en comparaison avec par exemple C++ où tu met des types partout.

    (Edit: la raison qui fait que ce problème existe moins en OCaml est surement du justement à l'absence de typeclass, donc l'absence des ambiguïtés qui peuvent en résulter).

  • [^] # Re: On s'en bat le steak

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

    C'est maintenant rarissime que j'obtienne des erreurs de type au lancement.

    Rarissime c'est trop ;) De mon coté, je suis moins chanceux / doué et je ne compte plus le nombre de fois où je lance un code python qui se vautre immédiatement sur une TypeError, où un mauvais nombre d'argument à une fonction, où une typo dans le nom d'une fonction ou même une erreur d'imbrication (Je pensais que j'avais un object mais en fait je manipulais une liste d'objets). Et le pire c'est quand cela ne se vautre pas immédiatement ;)

    Pour ne pas me contenter d'un troll, quel type d'erreurs obtient-tu au lancement qui ne sont pas des erreurs de type ? Plus le typage est avancé, plus de nombreuses erreurs de "logique" peuvent être captées par le système de type lors de la compilation, enfin du moins c'est l’expérience que j'ai.

  • [^] # Re: C++, où quand le code asm généré est plus lisible

    Posté par  (site web personnel) . En réponse au journal [C++14 ] Expressions template pour les nuls. Évalué à 2.

    D'autres langages vont proposer soit de résoudre le problème au runtime en créant l'arbre d'opération à l’exécution et en générant du code avec un compilateur intégré (JIT), c'est ce que font pas mal d'approches au dessus de numpy, numba, … Tu peux aussi générer du code OpenCL à la volée. Ces approches ont l'avantage de pouvoir en plus avoir une vision sur la taille de la structure, ses caractéristiques et la machine utilisée (Quantité de ram, taille des caches, …), et ainsi pouvoir générer un code plus adapté. En contrepartie c'est encore plus complexe et cela a un coût lors de l’exécution ;)

    D'autres langages vont proposer un outil de méta-programmation lors de la compilation qui est "plus lisible" que les templates C++.

    D'autres langages ont le support de ce type d'optimisation en natif dans le compilateur (regarde Haskell, les fusion et les rewrite rules), mais à ma connaissance cela ne donne pas ce niveau de satisfaction.

    Au final on est dans un domaine de la performance à tout prix et c'est dur, cela fait du code crade et seulement maintenable par des gars qui ont 3 PhD, dont un consacré à la maintenance du code en question, mais bon, c'est ça qui est marrant non ? (Il faut voir que l'exemple donné dans ce journal est "trivial" et "très lisible"… si si… ;(

  • [^] # Re: Avantage ?

    Posté par  (site web personnel) . En réponse au journal [C++14 ] Expressions template pour les nuls. Évalué à 7.

    Dans ton exemple c'est évident, mais imagine maintenant un pipeline avec une centaines d'opérations de haut niveau elles même définies par une dizaine d'opérations de bas niveau, tout de suite c'est moins facile à inliner à la main, regarde cet exemple :

    auto preprocess = [](const auto &im){ return malib::gammaCorrect(malib::tonemap(malib::greyscale(im))); };
    
    auto image = malib::max(malib::absdiff(preprocess(im0), preprocess(im1)));

    Un autre example intéressant, imagine que tu ai un pipeline complex et que tu ne veuilles qu'une sous partie de l'image :

    
    auto image = malib::regionOfInterest(rectangle, pipelineComplex(im0, im1, im2, im3, im4);
    

    Tu ne veux pas calculer toute l'image alors que le regionOfInterest limite le calcul à une petite sous partie. Cette technique peut te permettre de compiler un code qui n'effectue le traitement que sur sa sous partie SANS copier une seule image. Note que tu peux aussi spécialiser les templates pour générer du code vectorisé efficace ou des accès mémoire plus sympathiques. (Remarque, ce problème ne se simplifie pas en distribuant regionOfInterest sur les images d'entrée car ton pipelineComplex peut effectuer un traitement global)

  • [^] # Re: On s'en bat le steak

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

    Là où Haskell peut faire peur, c'est que comme il fait du fonctionnel pur il faut recourir à des monades pour les effets de bords : ça peut effrayer au premier abord.

    C'est ce qui m'a séduit dans Haskell.

    OCaml de son côté permet de mélanger les paradigmes impératif, fonctionnel et objet; et faire de la réutilisation de code (comme pour le paradigme objet) via les variants polymorphes (je ne sais pas s'il y a un équivalent en Haskell).

    Non, pas l'impression que ce soit possible en effet.