Journal Retour d'expérience sur les langages de programmation

Posté par  (site Web personnel) . Licence CC By‑SA.
Étiquettes :
40
13
nov.
2020

Sommaire

Ces derniers temps, j'apprends moins de langages nouveaux qu'il y a quelques années. Du coup, je me suis dit que c'était une occasion de faire le tour sur l'essentiel des langages que j'ai testés.

Dans ce journal, je fais un peu dans le classique du ceci ou cela m'a plu dans tel langage, telle autre chose ne m'a pas plu. Le tout est très subjectif, biaisé et reflète fortement les trucs que j'ai voulu faire avec ces langages. Mais bon, j'ai lu beaucoup d'articles de blog dans ce genre (enfin, en général sur un seul langage, ou L1 vs L2) et, même si ça n'aide pas souvent à découvrir le langage de nos rêves, ni à changer d'opinion ou à apprendre grand-chose sur un langage qu'on connait déjà, j'ai trouvé quand même ça souvent sympa à lire vite fait, même (voire surtout) quand mon ressenti est différent.

Petit tour d'expérience sur des langages

OCaml

OCaml est le premier langage que j'ai appris ! (enfin, son prédécesseur Camllight initialement, le langage qui était utilisé qu'en prépas en France)

Les trucs que j'ai aimés :

  • Compile vers du code natif assez efficace.
  • Typage expressif (types algébriques), mais pratique (inférence de types) et pas trop compliqué : langage abordable.
  • Mélange de code fonctionnel et impératif possible et plutôt facile.
  • Sympa pour manipuler des structures de données arborescentes. En particulier pour écrire des analyses ou transformations d'AST.
  • Documentation accessible en ligne de commande.

Les trucs qui me laissent dubitatif :

  • Des messages d'erreur qui se sont améliorés mais, le typage riche et l'inférence n'aidant pas, les erreurs ont toujours du mal à parler la langue des mortels.
  • Une syntaxe et un système de types pas trop compliqués, mais qui se compliquent ces dernières années : introduction des GADT (une sorte de types dépendants — en gros, des monstres surpuissants invoqués par des super héros) et les extensions de syntaxe ppx qui peuvent casser à chaque changement de version, entres autres; ça a du bon quand même.
  • La syntaxe : l'extension Reason fait plus de modifications que strictement nécessaire, mais marquer clairement la fin des pattern matching et autres structures de contrôle (comme en Rust), ce serait déjà bien (après, des accolades ou un end comme en Coq ou Ruby, c'est du détail).
  • Pas besoin de préciser les bibliothèques utilisées en préambule de fichier.
  • Un nombre ok de bibliothèques tierces.

Les trucs que j'ai moins aimés :

  • Bibliothèque standard limitée, beaucoup de variantes de fonctions de base, mais peu au-delà (pas de compression, encodage, unicode, http). Au moins deux bibliothèques alternatives existent, mais elles résolvent surtout des soucis différents.
  • Exceptions, en particulier leur sur-utilisation dans la bibliothèque standard qui a conduit à l'introduction de variantes en *_opt renvoyant plutôt un type option, du genre None ou Some x, plutôt que Not_found (mais pas pour toutes les fonctions encore).
  • Manque de structures de contrôle impératives : pas de break, continue, return ; ça peut vite devenir gênant si on manipule beaucoup les tableaux (tableaux qui d'ailleurs gagneraient en ergonomie à être dynamiques).
  • Des fonctions non récursives terminales (donc risque de débordement de pile) dans la bibliothèque standard qui ont conduit à plus de duplication avec l'introduction de fonctions récursives terminales équivalentes.
  • Les bibliothèques, à moins d'être très populaires, risquent d'être mal documentées : les types des fonctions, si on a de la chance une courte description pour chacune, parfois un exemple dans le README.
  • Certaines bibliothèques connues font dans l'ingénierie lourde (comme le framework ocsigen), pas toujours évident de trouver des alternatives plus simples et bien documentées.

Haskell

Haskell a des propriétés similaires à OCaml, à ceci près qu'il accueille avec joie la complexité. Plus amusant, mais plus frustrant aussi.

Les trucs que j'ai aimés :

  • Compile vers du code natif assez efficace.
  • Typage expressif, inférence de type.
  • Comme OCaml, pratique pour la manipulation d'AST.
  • La bibliothèque [parsec](https://en.wikipedia.org/wiki/Parsec_(parser) qui permet de parser en combinant des parseurs. Des alternatives dans d'autres langages ont vu le jour, mais parsec reste plus naturel (mais pas le plus performant par contre).

Les trucs qui me laissent dubitatif :

  • Les monades, des abstractions qui permettent de structurer les programmes de façon générique. C'est utilisé dans parsec pour combiner naturellement des parseurs, par exemple. Les monades IO et ST permettent de faire de l'impératif de façon compliquée aussi. C'est aussi utilisé pour rendre certains tutoriels très abstraits.
  • Un système de types plus complexe que celui d'OCaml et qui rencontre plus tôt les limites de l'inférence. Et une pléthore d'extensions de langage optionnelles.
  • Des messages d'erreur pour initiés à cause du typage expressif et de l'inférence de types.
  • Une communauté intéressée par des concepts comme les monades, les flèches, les catégories, etc. Ça se reflète dans de nombreux tutoriels et échanges, tout comme dans les bibliothèques tierces. C'est plus dur de trouver des contenus qui font dans le pragmatique. Ce point devient positif si on est passionné par les concepts mentionnés, ou source de frustration autrement :-)
  • Je n'aime pas trop certains éléments de syntaxe : l'indentation significative, l'abondance d'opérateurs avec priorités et associativité variables.
  • Des préambules de fichier avec souvent une suite interminable d'imports de bibliothèques et un mélange d'imports avec noms qualifiés et non qualifiés.

Les trucs que j'ai moins aimés :

  • Compilation lente.
  • Possible mais difficile de faire de l'impératif : manipuler des tableaux est tout sauf agréable (par exemple pour représenter la carte dans un jeu, faire de la recherche de chemins, etc.).
  • Il faut utiliser une bibliothèque externe pour avoir des chaînes de caractères implémentées raisonnablement.
  • Beaucoup de bibliothèques, mais c'est pas facile de s'y retrouver.
  • Beaucoup de bibliothèques font dans l'ingénierie lourde.
  • Beaucoup de bibliothèques ont un arbre conséquent de dépendances.
  • Beaucoup de bibliothèques sont mal documentées.

Exemple personnel : recherche d'une bibliothèque pour gérer le xml. Première tentative, hxt : pas moyen de trouver un indice dans la doc sur comment commencer (le théoricien remarquera que ça s'inspire de la théorie des flèches, mais ça l'aidera pas forcément tant que ça non plus). Deuxième tentative, HaXml : un peu moins abstrait peut-être, mais bon courage quand même. Troisième tentative, Text-XML-Light, le nom semble prometteur : pas d'exemples, mais ça semble en effet plus simple. Si l'on n'a pas encore capitulé, c'est le moment de chercher s'il n'y a pas un tutoriel à peu près à jour quelque part dans le wiki du langage pour une de ces bibliothèques.

Ceci dit, Haskell, c'est vraiment l'occasion de découvrir des concepts théoriques en faisant des trucs concrets, du genre découvrir à l'aide d'un framework web (appelé snap si ma mémoire est bonne) que les lentilles c'est pas seulement un truc qui se mange.

Tcl, Perl, Python, Raku

Tous ces langages se ressemblent un peu : typage dynamique, bases faciles à apprendre, plus ou moins d'OO, communauté pragmatique avec des écosystèmes de packages très variés, langages pas super performants mais suffisamment dans beaucoup de cas. Du coup, je vais parler uniquement des choses marquantes qui m'ont semblé uniques à chacun.

Pour Perl :

  • Intégration des expressions régulières dans le langage, inspirée de Sed : erreurs dans la regexp à la compilation, plein de fonctionnalités sur l'Unicode.
  • Mode de traitement de texte inspiré de Awk et adapté aux traitements rapides en ligne de commande.
  • Une documentation commode en ligne de commande et qui permet de démarrer vite, avec beaucoup d'exemples dans un style un peu « recettes » en synopsis.
  • Quelques incantations répétitives à écrire en début de chaque fichier.
  • Un peu plus fonctionnel (fonctions anonymes, portée lexicale des variables).
  • Mini typage statique partiel (scalaires vs tableaux vs tables de hachage, typos dans les noms de variables attrapées lors de la compilation).

Pour Python :

  • Beaucoup de bibliothèques dans le domaine du calcul scientifique (numpy, etc.).
  • Documentation plus OO que celle de Perl, plus orientée web que ligne de commande.
  • Listes en compréhension (perso, j'aime pas trop, ça se démarque un peu du reste du langage).

Pour Tcl :

  • Syntaxe où « tout est chaîne de caractères et commandes », mais fait proprement et sans pièges, contrairement au shell. Ça permet de faire des DSLs très naturels.
  • Par exemple, l'intégration très sympa avec SQLite : on peut écrire db eval {SELECT uid FROM table WHERE n <= $max AND time < $epoch} en mettant directement les variables $max et $epoch dans la requête sans risquer d'injections SQL (c'est pas de l'interpolation en fait). Ça évite la typique redondance où il faut passer les arguments à la requête après, souvent avec le même nom.
  • Plus fragile aux typos que Perl ou Python.
  • Intégration très naturelle avec Tk : mon langage préféré pour les petits GUI couplé à SQLite.
  • Documentation sous forme de pages de manuel proches de celles des outils en ligne de commande : plus formelle que la documentation Perl.
  • Wiki communautaire plein d'exemples, mais un peu chaotique.
  • Écosystème plus petit que les autres : pas idéal pour faire du calcul scientifique, par exemple, et moins de choix en général (par exemple pour faire du web).
  • Malgré son caractère de langage généraliste et bibliothèque standard assez vaste, Tcl peut être aussi facilement utilisé comme langage d'extension d'un programme en C (à la Lua).

Pour Raku (anciennement Perl 6) :

  • Langage généraliste à tout faire très (trop ?) ambitieux et pas effrayé par la complexité.
  • Langage plutôt cohérent et orthogonal, inspiré de Perl (mais aussi Ruby et d'autres), mais plus OO dans l'esprit.
  • Les messages d'erreur sont plutôt sympas.
  • Les expressions régulières sont intégrées dans un concept plus vaste de grammaires, très pratique pour écrire des parseurs.
  • La VM se lance un peu lentement et les modules compilent pas vite non plus.
  • Les expressions régulières, qui sont quand même fondamentales dans ce langage, étaient encore très mal optimisées il y a un ou deux ans, la dernière fois que j'ai testé.
  • L'écosystème est assez jeune encore.

Common Lisp, Racket

Common Lisp et Racket sont des langages fonctionnels, par défaut au typage dynamique, ils se prêtent très bien à la manipulation de structures arborescentes et sont très prisés pour leur extensibilité à l'aide de systèmes de macros évolués. Les deux ont pas mal de bibliothèques tierces et compilent vers du code assez efficace (normalement moins que OCaml ou Haskell, mais nettement plus que Python ou Perl).

Pour Racket :

  • Une documentation plus propre, surtout pour les bibliothèques tierces. Pour tout dire, lorsque j'ai testé, j'étais émerveillé par scribble, leur langage de documentation, qui est un dialecte de racket lui-même et permet de faire plein de validations sur la doc, dont le fait que les exemples compilent et renvoient le bon truc.
  • Plus orienté fonctionnel, mais aussi plus académique : une partie de l'objectif du langage est d'illustrer les recherches en théorie des langages extensibles.
  • Démarrage plus lent de la VM.

Pour Common Lisp :

  • Macros plus simples, mais non hygiéniques (ce qui est pas cool par les temps qui courent).
  • Un peu plus fonctionnel, en particulier la construction extrêmement flexible loop, ou peut-être encore mieux, la bibliothèque iterate : une macro d'itération très extensible !
  • Un peu le bazar pour ce qui est des bibliothèques tierces : le gestionnaire de paquets lui-même, bien que fonctionnel, est considéré bêta depuis très très longtemps.

Si l'on veut juste apprendre afin de découvrir les macros pour faire des DSLs, c'est bien plus simple de faire ça avec Tcl.

J

J est un langage fonctionnel de manipulation vectorisée de tableaux multi-dimensionnels avec une syntaxe compacte faisant usage de primitives de haut niveau. C'est une variante moderne d'APL avec une syntaxe ASCII et plus de fonctionnalités.

Les trucs que j'ai aimés :

  • La notation compacte est sympa pour expérimenter dans l'invite de commande.
  • Les primitives du langage sont très génériques et flexibles.
  • C'est amusant et ça fait réfléchir différemment à certains problèmes : je me suis amusé par exemple avec les problèmes du project euler, la génération de cartes et algos de dijkstra, ou l'écriture d'un automate pour parser des poèmes.

Les trucs que j'ai moins aimés :

  • Lorsqu'un algorithme ne se prête pas bien à une vectorisation, ça devient un casse-tête infernal.
  • J'ai beaucoup de mal à lire le code écrit par les autres.
  • De manière générale, j'ai l'impression que ce langage a tendance à facilement faire saturer ma mémoire cognitive de travail : un langage idéal pour quand j'ai besoin de me sentir idiot, ça marche à chaque fois.
  • Pour tout le code non algorithmique d'un projet, c'est aussi verbeux que n'importe quel langage et on ressent l'absence de structs/maps.

Le langage est surtout utilisé en statistiques et calcul scientifique, mais je dois dire que si j'avais un besoin dans ce domaine, je chercherais plutôt du côté de Python, R ou Julia. J'utilise J parfois comme calculatrice. En pratique je me contente souvent de la calculatrice dc du standard POSIX :-)

Coq

Coq est un assistant de preuve et un langage purement fonctionnel que j'ai pas mal utilisé pendant la thèse dans le domaine de la compilation. Je suis resté simple utilisateur, assez ignorant des théories derrière et des techniques avancées d'automatisation de preuve. Il y a eu une dépêche ici il y a quelques années par des gens qui connaissent bien mieux le truc (perso, j'avais juste contribué avec un exemple).

Les trucs que j'ai aimés :

  • C'est rigolo. Sérieusement, écrire des preuves de programme, c'est un peu comme un jeu, avec des moments de victoires épiques et de défaites accablantes.
  • C'est un langage avec un système de types extrêmement expressif : imaginez par exemple pouvoir écrire à l'aide du système de types qu'une passe d'optimisation d'un compilateur ne change pas la sémantique d'un programme et n'introduit donc pas de bugs inattendus !
  • Comme OCaml ou Haskell, le langage se prête bien à la manipulation d'AST et donc à l'écriture de compilateurs (avec des difficultés additionnelles ceci dit, comme le fait que les entiers sont représentés par un type algébrique et que Coq offre uniquement des structures de données purement fonctionnelles).

Les trucs qui me laissent dubitatif :

  • Écrire du code propre est relativement facile, mais des preuves propres, c'est une autre histoire : il y a l'approche où on essaie d'automatiser un maximum, ce qui demande de connaître très bien le langage de tactiques (donc preuve compréhensible par moins de monde), d'avoir une machine puissante (automatisation signifie plus de travail pour Coq) et compromettre la maintenabilité (du genre preuve qui passe plus avec la version suivante de Coq); il y a l'approche où on automatise pas trop et écrit beaucoup de lemmes intermédiaires et des preuves parfois répétitives, on insiste jusqu'à ce que ça passe à force de sentiments forts : je faisais partie des utilisateurs chevronnés de cette technique de jeu.

Les trucs que j'ai moins aimés :

  • Ça prend beaucoup de temps. Difficile de trouver des applications qui justifient cela, et ce même dans les domaines qui se prêtent assez bien à la preuve de programme (comme la compilation).
  • Il faut utiliser un autre langage, généralement OCaml, pour les parties non purement fonctionnelles du programme qui font de l'I/O.
  • C'est un langage complexe avec des messages d'erreur qui demandent une bonne expérience pour être appréhendés.
  • Faut pas s'attendre à trouver des contributeurs dans la nature : les programmeurs Coq se trouvent tous ou presque dans le domaine de la recherche.
  • Comme tout jeu, on finit par se lasser un peu à un moment et un jeu long dont on se lasse est un jeu qu'on ne finit pas (à moins d'être payé pour).
  • Les ressources disponibles dans la nature pour apprendre sont limitées, souvent écrites pour des gens qui font une thèse et sont intéressés par la théorie. La pratique et les astuces de preuve, faut les apprendre soi-même ou lors d'échanges avec les collègues si on a la chance d'être dans un environnement Coq. Bref, c'est peu accessible.

Go

Go est un langage que j'utilise beaucoup ces derniers temps (frundis, jeux, des petits scripts), je suis plutôt satisfait.

Les trucs que j'ai aimés :

  • Compile vers du code natif efficace. Compilation rapide, statique par défaut.
  • Langage : structures de contrôle impératives flexibles (for, switch, break, continue, labels de boucle), les essentiels du fonctionnel (fonctions de première classe et clôtures lexicales), l'essentiel de l'OO (structs, méthodes et interfaces, pas de classes), l'essentiel du typage statique (typage moyennement expressif, mais flexible au besoin et sans conversions implicites ni inférences trop génériques qui compliquent les messages d'erreur), l'essentiel des structures de données (maps et tableaux dynamiques, comme avec Perl, Python ou Ruby).
  • Une bibliothèque standard fournie, mais abordable et bien documentée.
  • Beaucoup de bibliothèques tierces bien documentées, souvent avec peu ou pas de dépendances.
  • Crosscompilation facile pour les programmes en pur Go (avec export en WebAssembly facile).
  • Programmation concurrente facile avec les channels et goroutines.
  • Un package, c'est tous les fichiers d'un dossier: pas besoin de faire un package différent pour éviter d'avoir trop de trucs dans un même fichier.
  • Documentation accessible en ligne de commande et, en général, langage pratique à utiliser dans un terminal avec plein d'outils (renommages, analyses statiques, bonne intégration vim/emacs, etc.).

Les trucs qui me laissent dubitatif :

  • URLs pour les noms d'import de package : ça conduit à devoir modifier le code si on change l'hébergement du projet. Ceci dit, le packaging n'a pas de solution magique non plus : j'ai beau ne pas vraiment aimer cette idée, c'est souvent pratique et pas clairement pire que les alternatives sur tous les points.
  • Absence de types génériques (en cours d'être résolue, peut-être pour dans un an ou deux) : ça serait bien dans certains cas (bibliothèques génériques pour structures de données complexes ou opérations génériques sur des channels), mais ça me manque assez rarement tout compte fait (je ne ressens pas le besoin de remplacer les boucles for par des fonctions génériques, par exemple).
  • Plus verbeux qu'un langage dynamique, essentiellement du fait des signatures de fonctions (en pratique rentable dans un projet qui va au-delà du script, je trouve).

Les trucs que j'ai moins aimés :

  • Difficile parfois de faire du pur Go (GUI, SQLite, etc.) : l'avantage de la crosscompilation facile disparaît dans ce cas. C'est pas vraiment un point négatif, mais une annulation courante de point positif.

Rust

Rust est un langage qui a pas mal de popularité en ce moment, pas mal de trucs sont passés sur linuxfr. J'ai lu un tutoriel, testé des exemples et lu de la doc, mais je n'ai jamais vraiment programmé avec, donc voici plutôt un retour d'apprentissage et d'utilisation :

  • Des programmes très performants, dont le génial ripgrep qui remplace avantageusement grep.
  • Des programmes avec beaucoup de dépendances et qui mettent beaucoup de temps à compiler.
  • Langage d'inspirations multiples avec typage assez expressif (types somme et filtrage par motif similaires à OCaml), des traits (mais sans classes, un peu comme en Go).
  • Langage qui facilite l'impératif et le fonctionnel, même si l'absence de GC rend certaines pratiques de programmation fonctionnelle (comme une fonction qui renvoie une fonction) un peu alambiquées à écrire.
  • Un peu complexe à apprendre du fait de quelques notions assez subtiles (ownership, borrowing) qui facilitent l'écriture de programmes concurrents memory safe, et du fait de l'ampleur du langage (macros, etc.).
  • Une documentation orientée web (même s'il me semble que j'avais trouvé un outil non officiel en ligne de commande).

J'aimerais m'y mettre un jour, mais j'ai pas d'idée de projet personnel qui profite de l'absence de GC : un peu comme pour le C et le C++, avec la différence qu'avec ceux-ci je me suis déjà retrouvé à devoir lire voire modifier du code dans les programmes que j'utilise, et ça ne m'est pas encore arrivé avec du Rust.

Ce qu'il m'est resté de tout ça

Au final, aujourd'hui, les seuls langages que j'utilise vraiment encore sont Go (pour un peu tout), Tcl (pour les GUIs et SQLite) et Perl (pour les petits scripts et CPAN). C'est sans compter des petits bouts de Javascript (dont j'ai pas parlé, car j'ai juste écrit des petits trucs en vanilla avec la doc de mozilla, sans aller chercher quoi que ce soit dans l'écosystème), ou les modifs de code C/C++ pour compiler sous OpenBSD, et mes tentatives le plus souvent couronnées d'échec pour compiler puis lancer du Java (dernière défaite cuisante en date : le jeu Mindustry qui est passé en dépêche il y a peu).

Ceci dit, même si au final on peut se dire à quoi bon avoir exploré autant de langages, j'ai bon souvenir de tout ça et ça influe probablement sur ma façon de programmer, j'espère qu'en bien :-)

Langages que j'aimerais creuser un peu un jour

Un langage relativement nouveau qui m'a l'air intéressant est txr : c'est en fait la combinaison de deux langages, un langage qui permet de capturer des motifs et parser facilement des documents, inspiré d'Awk, et un langage au style Lisp, mais différent. C'est pas un petit langage !

Dans le domaine des langages logiques, je trouve curieux Mercury, qui est un langage inspiré de Prolog pour la partie logique, et Haskell pour la partie typage.

Pour ce qui est des langages concaténatifs, inspirés de Forth, Factor semble être une approche moderne intéressante. Ceci dit, mes quelques lectures de tutos me donnent l'impression que mon cerveau ne gère pas bien l'approche concaténative de pile dès que ça devient un peu complexe (un peu la même sensation qu'avec J, mais pas aussi marquée).

J'ai vu passer assez souvent des articles sur le langage assez jeune mais plutôt actif Zig. Je me demande comment il se ressent en pratique par rapport au C voire au Rust ou C++.

  • # Erlang/Elixir

    Posté par  (site Web personnel) . Évalué à 7 (+5/-0).

    Je te recommande également de te pencher sur Elixir et Erlang qui proposent de puissants outils pour faire de la programmation concurrente (et fonctionnelle).

    • [^] # Re: Erlang/Elixir

      Posté par  (site Web personnel) . Évalué à 2 (+0/-0).

      C'est vrai, j'avais regardé à quoi ressemblait Elixir à un moment, sauf qu'à l'époque la programmation concurrente me disait pas grande chose, du coup je suis passé à côté.

  • # Mon favoRi !

    Posté par  . Évalué à 4 (+2/-0).

    Salut,

    D'autres en ont parlé ici, moi également, je trouve R super, et je crois qu'il manque à ta liste.

    La courbe d'apprentissage est assez rude, ça, c'est indéniable.

    Par contre, comme c'est un langage vectoriel, c'est super compact.

  • # un langage pour des petits GUI

    Posté par  . Évalué à 7 (+4/-0).

    Est-ce que vous connaissez un langage pour faire rapidement une GUI ? J'ai testé le Tcl/Tk il y a très longtemps, mais je n'avais pas aimé.

    "La première sécurité est la liberté"

    • [^] # Re: un langage pour des petits GUI

      Posté par  . Évalué à 7 (+5/-0).

      Python + Qt, peut-être ?

      • [^] # Re: un langage pour des petits GUI

        Posté par  . Évalué à 6 (+5/-0).

        Pythion +tkinter est beaucoup plus aisé

        • [^] # Re: un langage pour des petits GUI

          Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

          Python + Qt sans hésiter! Beaucoup plus simple d'approche et mieux documenté que tkinter . Et ca tiendra encore la route si le petit GUI se transforme en grosse application!

          • [^] # Re: un langage pour des petits GUI

            Posté par  . Évalué à 2 (+1/-0).

            c'est sûr que tkinter est assez moche et assez limité, mais autant j'arrive à peu près à faire des trucs en tkinter , autant saavec 0 connaissances en prog GUI par événement, la programmation QT est assez compliqu, u plutôt mal documenté, reservé à une élite

            • [^] # Re: un langage pour des petits GUI

              Posté par  . Évalué à 3 (+2/-0).

              Je fais pas mal de Qt et je suis loin de faire partie de l'élite ^
              Tant qu'à donner mon avis, mon langage favoris d'interface en ce moment c'est le qml, c'est déclaratif et plutôt chouette avec son scripting en pseudo js (+ la possibilité d'interagir avec du code c++).

              Sinon je vais bientôt tester d'écrire une app avec Tauri (un genre d'electron léger) donc en html/js/css. Avec les techno web on fait quand même de belles gui.

              D'ailleurs histoire de parler d'un truc que j'aime bien, je voulais essayer d’intégrer une app que j'ai codé avec Yew dans un runtime Tauri. Yew c'est le feu, ça me rappel beaucoup Vue.js .

              • [^] # Re: un langage pour des petits GUI

                Posté par  . Évalué à 2 (+0/-0).

                Je développe pas mal avec les technos du web. En perso, principalement parce que ça permet de donner une URL aux gens et ils n'ont besoin de rien installer.

                Effectivement on peut faire des trucs jolis avec HTML / CSS / JS et c'est un aspect que j'aime bien. Par contre attention au manque de composants standards, surtout quand on vient d'une boîte à outils comme Qt. On se retrouve vite à réinventer la roue dans chaque nouveau projet, ou devoir embarquer une bibliothèque lourde. Ext.js semble fournir des composants comme pourrait s'attendre quelqu'un qui a fait du Qt. Je n'ai jamais essayé. Ils n'ont pas l'air de surfer sur le modèle du DOM virtuel ou équivalent (faut dire qu'ils sont apparus avant), cher aux dev frontend d'aujourd'hui, mais peut être que quand on n'a pas besoin de recoder le monde entier, c'est moins important.

              • [^] # Re: un langage pour des petits GUI

                Posté par  . Évalué à 1 (+0/-0).

                j'aurai plutôt du écrire "réservé à des élites" (c) (comme la carte super plus premium platinium grossium) https://www.youtube.com/watch?v=OpNHovy8_7c

    • [^] # Re: un langage pour des petits GUI

      Posté par  (site Web personnel) . Évalué à 5 (+3/-0).

      Si je me souviens bien, il y a 20 ans de ça j'utilisais Glade pour faire des GUIs. Grosso modo, tu fais tout joliment dans une UI, et ça génère une config XML. Ensuite, t'as juste à coder ta logique en faisant référence aux identifiants de ta config XML. Un peu comme si tu faisais une app avec AndroidStudio.

      Il y a certainement des alternatives pour QT, Swing, etc.

      • [^] # Re: un langage pour des petits GUI

        Posté par  . Évalué à 5 (+3/-0).

        Salut,

        Meilleure librairie que j'ai pu voir pour swing : MigLayout.

        L'UI, c'est pas mon métier, donc ça traînait mon projet (mais j'avais décidé : rattraper un bout de dette technique).

        Et bin ça fait comme échanger une 2 chevaux pour une porche. D'un coup ça va super (trop ?) vite ;)

    • [^] # Re: un langage pour des petits GUI

      Posté par  . Évalué à 3 (+1/-0).

      J'avais joué avec guietta, en Python+Qt. Plutôt sympa.

    • [^] # Re: un langage pour des petits GUI

      Posté par  (site Web personnel) . Évalué à 8 (+6/-0).

      Python ne manque pas d'outils pour une interface graphique rapide. Ça va de la conversion automatique de la ligne de commande via https://github.com/chriskiehl/Gooey à des outils très complets comme Qt, cf. https://awesome-python.com/#gui-development.

      Ça vaut le coup de passer un peu de temps à tester et voir les spécificités selon ce que tu souhaites faire. Depuis quelques années j'utilise Kivy qui n'est pas assez connu à mon avis au regard de ses capacités, avec la possibilité de mélanger du Python avec un langage déclaratif (Kv), et le support pour toutes les plateformes majeures (avec plus ou moins de difficultés pour les plateformes mobiles). J'ai d'ailleurs commencé une dépêche pour présenter tout ça.

      Pascal n'est pas cité, mais il y a une communauté très active autour de Free Pascal et Lazarus. Je n'ai pas utilisé moi même, mais j'ai fait du Delphi dans ma jeunesse, c'était vraiment sympa et ça permettait d'avoir des choses concrètes très rapidement. Lazarus a l'air de gérer aussi toute les plateformes principales, et d'avoir une bibliothèque de composants assez fournie. Ça peut être un outil sympa pour faire des choses rapidement.

      Une autre option qui sort des sentiers battus, c'est d'utiliser Godot (moteur de jeu libre qui a le vent en poupe), y compris pour faire autre chose que des jeux. C'est un des outils que j'envisage d'utiliser à plus ou moins long terme, parce que je pense que ça peut permettre de faire des choses très rapidement et de manière relativement agréable.

      Attention par contre, pour les outils que j'ai cité, l'accessibilité peut être nettement moins bonne que sur des gros acteurs comme Qt ou GTK, c'est à ne surtout pas négliger.

    • [^] # Re: un langage pour des petits GUI

      Posté par  (site Web personnel) . Évalué à 6 (+4/-0).

      Je rebondis car c'est un point important pour moi: je suis souvent amené à faire des utilitaires avec interface graphique. Du coup, j'ai régulièrement voulu tester ces "nouveaux" langages mais à chaque fois, c'est un gros écueil. Il y a au choix:
      - rien
      - une pauvre tentative de début de toolkit graphique dont on sent que c'est loin d'être abouti
      - un début de binding vers des grosses lib graphiques (Qt, Gtk en général) mais à moitié fini et instable.

      Du coup, bin je reste en terrain connu avec mon Python et mon Qt.

  • # Tcl

    Posté par  . Évalué à 6 (+6/-0).

    J'ai utilisé Tcl il y a quelques années et voici ce qui m'avait séduit :

    • la simplicité du langage, il repose sur 12 règles : le dodécalogue
    • l'interpréteur est réellement thread-safe
    • le système de fichier virtuel permettant la manipulation transparente des fichiers, qu'ils soient sur un système de fichiers natif, un fichier zip ou un site FTP.
    • Grâce à la compacité de l'interpréteur et au point précédent, il est possible de distribuer un programme Tcl(/Tk) autonome en un seul exécutable avec système de fichier intégré occupant moins de 2 Mo.
    • Tk bien sûr
    • la facilité avec laquelle il est possible de communiquer avec les commandes externes
    • [^] # Re: Tcl

      Posté par  . Évalué à 6 (+6/-0).

      J'utilise ce langage tous les jours et il a de très nombreux problèmes :

      • L'accès aux éléments d'une liste via lindex
      • La commande set pour affecter une valeur à une variable
      • Impossible de créer des données structurées (sauf TclOO, voir point suivant)
      • La partie Tcl Orienté Objet est super lente, donc il ne faut pas trop l'utiliser
      • Passage des arguments par copie, on peut simuler des références avec "upvar" pour accéder au contexte de l'appelant
      • Le langage est hyper fragile, on arrive très souvent sur des "undefined variable" et le code se retrouve vite remplit de "info exists" pour savoir si la variable existe vraiment
      • L'utilisation de la pile C qui peut conduire à des deadlocks juste en utilisant plusieurs vwait
      • La communauté, j'ai écrit 3 rapports de bugs et à chaque fois on m'a envoyé bouler
      • Le wiki tcl, avec les collègues on l'appelle le musée des horreurs, chacun y va de son petit bout de code pour résoudre des problèmes qui devrait être adressés par le langage (genre enlever un élément d'une liste)

      Et concernant Tk :

      • C'est lent, par exemple TkText comporte des opérations de base en O(n3)
      • C'est moche et les thèmes n'aident vraiment pas
      • Ce n'est pas thread safe, impossible d'utiliser Tk dans deux interpréteurs Tcl, ni même d'utiliser un autre toolkit graphique
      • Difficile d'écrire une IHM robuste, le simple fait de fermer une boîte de dialogue peut très facilement conduire à un message d'erreur (à cause de la gestion des évènements Tcl)

      Le seul avantage que je trouve à ce langage : le binding C ultra simple

      • [^] # Re: Tcl

        Posté par  (site Web personnel) . Évalué à 2 (+0/-0).

        Quel sorte d'application tu développes ?

        Sinon, effectivement, Tcl est plus lent que Perl ou Python pour les trucs un peu intensifs.

        • [^] # Re: Tcl

          Posté par  . Évalué à 2 (+2/-0).

          Des simulateurs de satellites, le cœur du programme est en C/C++. Le Tcl est utilisé pour faire l'affichage graphique ainsi que l'exécution de scripts permettant d'interagir avec le simulateur.

    • [^] # Re: Tcl

      Posté par  . Évalué à 3 (+2/-0).

      Un petit truc sympa avec Tcl/Tk c'est qu'il existe une version pour Android (http://www.androwish.org) avec pas mal d'extensions et un SDK.
      Cela peut servir à piloter une machine à expresso par exemple.

  • # Raisons d'essayer Rust

    Posté par  (site Web personnel) . Évalué à 10 (+11/-0). Dernière modification le 13/11/20 à 20:53.

    J'aimerais m'y mettre un jour, mais j'ai pas d'idée de projet personnel qui profite de l'absence de GC

    L'absence de GC n'est pas la seule raison d'essayer (et de préférer) Rust. Personnellement, j'ai tenté le Go et c'est simple avec un bon écosystème. Mais y'a plein de trucs crados fans le design du langage, des librairies et dans l'esprit qui finissent par te rattraper. Avec Rust, j'ai l'impression que beaucoup de choses ont été très bien pensées pour le long terme.

    Je pourrais faire une longue liste mais je suis très flemmard alors je donnerai juste un exemple: la gestion d'erreurs. En Rust, tu as Result qui fait que soit tu retournes un résultat, soit une erreur et que l'erreur ne peut pas être ignorée. Tu dois la traiter. Tu peux utiliser unwrap si tu es flemmard, mais au moins tu sais à quoi t'attendre et c'est facile à retrouver.

    En Go, le pattern c'est de retourner (resultat, erreur). Si tu ne verifies pas l'erreur, dommage le compilateur ne t'aidera pas à t'en rendre compte. En plus il faut toujours gérer 4 cas: (result, nil), (result, err), (nil, err), (nill, nil). Et si les cas 2 et 4 semble improbables et inutiles, il n'y a pas besoin de chercher très loin dans la doc de la lib standard pour trouver des exemples (read par exemple, qui peut retourner des données et un EOF).

    Maintenant j'ai tendance à dégainer Rust pour tout et n'importe quoi. Y'a bien Python qui garde une place dans mon coeur, mais juste pour des trucs rapides, ou pour le plaisir de changer. Et en général avec pytype.

    • [^] # Re: Raisons d'essayer Rust

      Posté par  (site Web personnel) . Évalué à 7 (+5/-0).

      L'absence de GC n'est pas la seule raison d'essayer (et de préférer) Rust.

      Tout à fait, ce que je voulais dire, c'est que ce serait une raison qui me donnerait la motivation additionnelle pour me lancer sans hésitation.

      (result, nil), (result, err), (nil, err), (nill, nil). Et si les cas 2 et 4 semble improbables et inutiles, il n'y a pas besoin de chercher très loin dans la doc de la lib standard pour trouver des exemples (read par exemple, qui peut retourner des données et un EOF).

      Pour le quatrième, j'ai pas de souvenir d'avoir eu à gérer ça ?

      Autrement, il y a pas mal d'outils d'analyse statiques (en plus de go vet) pour Go qui peuvent aider (comme errcheck).

    • [^] # Re: Raisons d'essayer Rust

      Posté par  . Évalué à 3 (+2/-0).

      En Go, le pattern c'est de retourner (resultat, erreur). Si tu ne verifies pas l'erreur, dommage le compilateur ne t'aidera pas à t'en rendre compte.

      Tu peux utiliser des linters et notamment le metalinter golangci-lint qui fait référence et évitera ce genre d'erreur (entre autres).

      En plus il faut toujours gérer 4 cas: (result, nil), (result, err), (nil, err), (nill, nil). Et si les cas 2 et 4 semble improbables et inutiles, il n'y a pas besoin de chercher très loin dans la doc de la lib standard pour trouver des exemples (read par exemple, qui peut retourner des données et un EOF).

      Euh, non pas spécialement, si tu retournes la valeur et non son pointeur donc (résultat, erreur) et pas (*résultat, erreur), tu ne peux pas avoir tes deux derniers cas (nil, err) et (nil, nil). Car tu disposeras directement de la valeur qui ne peut être nulle.
      Après plus de 6 années d'expérience intensive de programmation en Go, mes fonctions ne retournent pratiquement que des valeurs donc (result,error). Si je dois retourner un pointeur, c'est qu'il y a un besoin bien particulier comme retourner une structure avec un partage d'un espace mémoire (ex : mutex et co).

    • [^] # Re: Raisons d'essayer Rust

      Posté par  . Évalué à 2 (+1/-1).

      En Go, le pattern c'est de retourner (resultat, erreur). Si tu ne verifies pas l'erreur, dommage le compilateur ne t'aidera pas à t'en rendre compte

      Je n'ai jamais compris pourquoi ça n'était pas une obligation (de traiter l'erreur retournée). Ne serait-ce que d'indiquer _ = si vraiment on ne veut pas, mais au moins ce serait explicite.
      Quelqu'un sait la raison ?

      • [^] # Re: Raisons d'essayer Rust

        Posté par  (site Web personnel) . Évalué à 4 (+2/-0).

        Go exige d'utiliser une variable définie au moins une fois : ça implique la solution que tu proposes, au détail près qu'il n'y a pas de garantie qu'on n'utilise pas malgré tout la valeur retournée par la suite.

        Ce n'est pas évident d'imposer mieux au niveau du langage. En Go les erreurs sont juste une valeur comme les autres, elles ne sont pas traités spécialement par le compilateur ou le langage : il s'agit juste de conventions et d'une interface error prédéfinie au même titre que d'autres types prédéfinis (comme int).

        La façon classique d'empêcher à 99% (à unwrap ou exception près) d'utiliser une valeur retournée malgré une erreur, c'est les types option et filtrage par motifs qu'on trouve dans les langages avec des types algébriques (OCaml, Rust, etc.).

        En Go, on a l'obligation d'utiliser au moins une fois err et, ensuite, on peut chercher plus de garanties avec des analyses statiques heuristiques (comme errcheck), indépendantes du langage avec risque de faux positifs et négatifs. En pratique, d'après l'institut de statistiques pifométriques, ça empêche uniquement 98% des problèmes (contre 99% avec OCaml ou Rust), mais permet en échange de conserver un typage simple et de ne pas forcer une imbrication du flot de contrôle. Ce n'est pas un problème facile : en cas d'erreurs imbriquées, l'approche des types option et filtrage par motifs demande, soit du sucre syntaxique ad hoc comme en Rust ou Elixir (comme ? ou with qui encouragent à traiter uniquement le chemin où tout se passe bien), soit des concepts avancés comme les monades en Haskell ou des réimplémentions ad hoc de celles-ci. Autrement, ça peut devenir difficile à lire et maladroit.

        C'est toute la question des avantages et désavantages entre algorithmes de recherche de solution approchée (souvent plus simples et plus rapides au prix de ne pas offrir de garantie absolue) et algorithmes de recherche de solution exacte (plus lents et conduisent souvent à plus de complexité).

        Ceci dit, avec les deux approches, rien ne garantie, par contre, que l'erreur est gérée proprement (d'un point de vue sémantique), ce qui arrive plus souvent en pratique et est un problème bien plus difficile qui ne peut être traité par les types que dans les langages avec types dépendants comme Coq.

        • [^] # Re: Raisons d'essayer Rust

          Posté par  . Évalué à 2 (+0/-0).

          Outre le cas de gestion des erreurs en renvoyant un type somme, il me semble étrange qu'un langage moderne ne dispose pas de types sommes. Cela ne rend pas le système de type plus complexe, ni plus dur à implémenter.

          Les adeptes des types algébriques ont un slogan qui dit « types as propositions », qui est à mon sens un abus de langage. Un type c'est un concept, mais un concept il faut d'abord le définir et pour cela utiliser une… proposition. Le slogan confond, d'une certaine façon, le défini avec sa définition. Mais, ceci étant, l'absence de type somme fait que l'on ne peut pas écrire de définition disjonctive (type somme) mais seulement des définitions conjonctives (type produit, comme les couples). La disjonction (ou somme) c'est le dual de la conjonction (ou produit) : son absence est difficilement justifiable. Que dirai-t-on d'un langage qui ne fournirait pas l'opérateur xor sur un type booléen mais seulement le and ? Il amputerait grandement les capacités d'expression du programmeur.

          En revanche, il est vrai que la gestion d'un tel type somme pour la gestion des erreurs peut rendre le code plus verbeux sans constructions adéquates dans le langage. Cela oblige à travailler dans une monade et, jusqu'à l'an dernier, c'était assez lourd syntaxiquement en OCaml. Contrairement à Haskell et sa do notation, mais je ne sais pas comment s'y prend Rust.

          D'ailleurs, pour reprendre une partie de ton journal, c'était moins lourd de travailler avec des exceptions que des type option ou result.

          let test i j =
            let i = int_of_string i in
            let j = int_of_strinf j in
            i + j
          
          test "12" "5";;
          - : int = 17
          
          test "coin" "5";;
          Exception: Failure "int_of_string".

          Si on prend une fonction de conversion qui renvoie une option au lieu d'une exception cela devient beaucoup plus lourd :

          let ios s = try Some (int_of_string s) with _ -> None
          
          let test i j =
            match ios i with
            | None -> None
            | Some i -> match ios j with
               | None -> None
               | Some j -> Some (i + j)
          
          test "12" "5";;
          - : int option = Some 17
          
          test "coin" "5";;
          - : int option = None

          Ou alors, pour être plus générique dans le pattern de code, on utilisera la monade d'option (ce qui complique grandement la charge cognitive pour le programmeur).

          let (>>=) o f = match o with None -> None | Some x -> f x
          let return x = Some x
          
          let test i j =
            ios i >>= fun i ->
            ios j >>= fun j ->
            return (i + j)
          
          test "12" "5";;
          - : int option = Some 17
          
          test "coin" "5";;
          - : int option = None

          Heureusement, depuis un an, on peut écrire un code moins abscons et plus proche de la version avec exception, mais c'est très récent.

          let (let*) = (>>=)
          
          let test i j =
            let* i = ios i in
            let* j = ios j in
            return (i + j)
          
          test "12" "5";;
          - : int option = Some 17
          
          test "coin" "5";;
          - : int option = None

          N'ayant jamais utiliser Rust, on doit s'y prendre comment pour travailler dans une telle monade ?

          Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

          • [^] # Re: Raisons d'essayer Rust

            Posté par  (site Web personnel) . Évalué à 2 (+0/-0).

            Outre le cas de gestion des erreurs en renvoyant un type somme, il me semble étrange qu'un langage moderne ne dispose pas de types sommes. Cela ne rend pas le système de type plus complexe, ni plus dur à implémenter.

            Pour ce qui est des types somme en soi, ce n'est effectivement pas compliqué à implémenter : en fait, les interfaces de Go (qui sont des valeurs) peuvent être (et au besoin sont) utilisées exactement comme les types somme à l'aide d'un type switch (le cas courant de l'énumération se fait d'habitude avec des constantes). La seule différence, c'est que le nombre de types qui peuvent implémenter une interface n'est pas limité, contrairement aux types somme, ce qui veut dire qu'on ne peut pas faire de vérification d'exhaustivité. Une façon d'implémenter les type somme en Go sans introduire un concept trop différent, ce serait une façon de spécifier une liste fixe de types implémentant une interface explicitement (les interfaces sont implémentées implicitement autrement). Pour le moment, on dirait qu'ils n'ont pas considéré le bénéfice (check d'exhaustivité) suffisant pour introduire un nouveau concept, même s'il n'y a pas non plus d'opposition claire.

            D'ailleurs, pour reprendre une partie de ton journal, c'était moins lourd de travailler avec des exceptions que des type option ou result.

            Pour le coup, les exceptions peuvent conduire à du code concis, oui, sauf que c'est la méthode la moins explicite, invisible dans les signatures de fonction en OCaml, et offrant les bugs les plus inattendus.

            Heureusement, depuis un an, on peut écrire un code moins abscons et plus proche de la version avec exception, mais c'est très récent.

            J'ai pas vu passer ça ! J'imagine que c'est une extension ppx, ou alors directement intégré dans le langage ? Est-ce que ça couvre le cas des erreurs, idéalement avec une façon permettant de l'annoter ?

            N'ayant jamais utiliser Rust, on doit s'y prendre comment pour travailler dans une telle monade

            En Rust, ils se sont contentés de sucre syntaxique à l'aide d'une macro ? spécifique au cas du type option pour une erreur (type Result). C'est possiblement un bon compromis, surtout qu'ils ont fait l'effort de trouver une solution pour les annotations simples d'erreur (implémenter un trait de contexte pour Result).

            • [^] # Re: Raisons d'essayer Rust

              Posté par  (site Web personnel) . Évalué à 2 (+0/-0).

              Pour le moment, on dirait qu'ils n'ont pas considéré le bénéfice (check d'exhaustivité) suffisant pour introduire un nouveau concept, même s'il n'y a pas non plus d'opposition claire.

              Un truc intéressant : il existe un outil d'analyse statique qui vérifie l'exhaustivité pour des types choisis sur la base d'annotations en commentaire. J'imagine que ça peut parfois valoir le coup, même si c'est un peu verbeux.

            • [^] # Re: Raisons d'essayer Rust

              Posté par  . Évalué à 2 (+0/-0).

              en fait, les interfaces de Go (qui sont des valeurs) peuvent être (et au besoin sont) utilisées exactement comme les types somme à l'aide d'un type switch (le cas courant de l'énumération se fait d'habitude avec des constantes).

              Cela peut certes être utilisé dans certain cas en lieu et place d'un type somme, mais ce n'est vraiment pas la même notion. Les interfaces, c'est les type classes de Haskell ou les traits de Rust, mais en version du pauvre. Les fonctions qui prennent un interface en paramètre c'est juste des fonctions qui prennent un paramètre implicite qui est de type produit.

              Par exemple, un type que l'on peut convertir en chaîne de caractères, on pourrait l'écrire comme cela en OCaml :

              type 'a showable = {show : 'a -> string}
              
              (* puis l'utiliser ainsi *)
              let print dict value = print_endline (dict.show value);;
              val print : 'a showable -> 'a -> unit = <fun>

              Ce que font les interfaces de Go, les types classes ou les traits c'est instancier implicitement le dictionnaire de méthodes en fonction du type du paramètre value. De telle sorte qu'il suffit d'écrire print 1 au lieu de print {show = string_of_int} 1. Mais un dictionnaire, cela reste un type produit. C'est surtout utilisé pour faire du polymorphisme ad hoc, et non pour se substituer au type somme.

              Pour faire la même chose en OCaml, avec la même généralité que les type classes de Haskell, il faudrait passer par le système des modules et foncteurs. Un module est un dictionnaire (type produit), qu'il faudrait passer comme argument implicite à une fonction. Il faut pour cela étendre le système des modules de première classes (en faire des valeurs comme les autres), puis ajouter un système de résolution pour arguments implicites. C'est un objet de recherche en cours.

              J'ai pas vu passer ça ! J'imagine que c'est une extension ppx, ou alors directement intégré dans le langage ?

              C'est du sucre syntaxique intégré au langage depuis la version 4.08, mais c'est inspiré de ce qui se faisait avant avec des extensions ppx. Cela a été fait pour simplifier le code qui utilise des monades ou des foncteurs applicatifs (cf. la doc sur les binding operators). Je trouve que cela rend mieux compte du choix du nom bind pour l'opérateur monadique que ne le fait la do notation de Haskell. Le >>= c'est juste une généralisation du pipe du shell qui est le bind de la monade identité.

              Le code avec les exceptions j'aurais pu l'écrire ainsi si j'avais utilisé le pipe :

              let test i j =
                int_of_string i |> fun i ->
                int_of_string j |> fun j ->
                i + j

              ou avec la type classe de la monade identité :

              let (>>=) = (|>)
              let return x = x
              
              let test i j =
                int_of_string i >>= fun i ->
                int_of_string j >>= fun j ->
                return (i + j)

              Ce qui est le même code que la version monadique avec le type option. Réciproquement, le sucre syntaxique des binding operators permet de récupérer la forme non monadique pour du code monadique.

              En Rust, ils se sont contentés de sucre syntaxique à l'aide d'une macro ? spécifique au cas du type option pour une erreur (type Result).

              Comme OCaml maintenant, si je comprends bien.

              Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

              • [^] # Re: Raisons d'essayer Rust

                Posté par  (site Web personnel) . Évalué à 2 (+0/-0).

                Cela peut certes être utilisé dans certain cas en lieu et place d'un type somme, mais ce n'est vraiment pas la même notion. Les interfaces, c'est les type classes de Haskell ou les traits de Rust, mais en version du pauvre. Les fonctions qui prennent un interface en paramètre c'est juste des fonctions qui prennent un paramètre implicite qui est de type produit.

                Non, pas vraiment : en Go les interfaces sont des valeurs à part entière assimilables à un couple (type, valeur) dont on peut inspecter chaque composant à l'exécution. L'application de méthodes permet d'en faire une utilisation en tant que type produit (instatiation implicite du dictionnaire des méthodes), mais un switch sur le type (non effacé par la compilation, contrairement aux types classes ou traits) permet d'en faire une utilisation en tant que type somme.

                C'est bien une notion à cheval entre les deux, qui regroupe des applications des types somme (par exemple pour représenter un ast) et types classes/traits (utilisation type produit) sous une même notion. Elle ne va pas aussi loin sur aucun des deux aspects (type somme et produit) et est un peu plus verbeuse dans son utilisation type somme, mais elle a le mérite de traiter les deux à l'aide d'un seul concept. Enfin, en bonus, elle résout aussi la question de la réflection et ses applications intéressantes (en particulier en sérialisation) dont on ne profite ni en Rust, ni en OCaml ou Haskell.

                • [^] # Re: Raisons d'essayer Rust

                  Posté par  . Évalué à 1 (+0/-1). Dernière modification le 16/11/20 à 18:26.

                  Non, pas vraiment : en Go les interfaces sont des valeurs à part entière assimilables à un couple (type, valeur) dont on peut inspecter chaque composant à l'exécution.

                  C'est l'implémentation de la fonctionnalité qui veut cela, mais ça n'a rien à voir avec la notion générale de type somme. La valeur (du couple) est un dictionnaire, c'est-à-dire un type produit. Quand vous utilisez une interface, vous affirmez l'existence unique d'un certain dictionnaire pour le type qui vous intéresse. Alors, effectivement, un énoncé qui quantifie existentiellement et de manière unique sur les types est équivalent à une disjonction exclusive infinie, mais c'est là une vision tordue de ce que sont réellement les types sommes.

                  Quand on dit qu'il existe un unique entier satisfaisant une propriété P, c'est tout comme si l'on disait cette disjonction exclusive infinie : P0 ou P1 ou P2 ou P3… Avec vos interfaces vous fait la même chose mais en quantifiant sur les types, puis vous examiner au runtime lequel des types implémente le dictionnaire. Mais c'est là un choix d'implémentation du mécanisme (c'est bien plus simple que de tout gérer à la compilation) qui dans le principe n'est rien d'autre que celui des arguments implicites de type produit. Vous auriez pu faire la même chose en vous contentant d'avoir une représentation du type au runtime et sans interface.

                  elle résout aussi la question de la réflection et ses applications intéressantes (en particulier en sérialisation) dont on ne profite ni en Rust, ni en OCaml ou Haskell

                  C'est tout à fait possible en OCaml avec les types GADT extensibles, mais je dois dire que je n'en ai jamais bien compris l'intérêt (c'est quoi ces fameuses applications intéressantes ?). À la différence qu'il faut se taper à la main ce que le compilateur Go fait automatiquement pour vous. Un GADT extensible permet de définir une somme illimitée de couple (type, valeur), ce qui est l'implémentation retenue pour les interfaces.

                  Exemples avec mon interface showable :

                  type _ ty = ..
                  
                  type _ ty +=
                    | Int : int ty
                    | Float : float ty
                    | List : 'a ty -> 'a list ty
                  
                  let couple = (Int, {show = string_of_int});;
                  val couple : int ty * int showable = (Int, {show = <fun>})
                  
                  (* on peut étendre le GADT *)
                  type _ ty += String : string ty
                  
                  let couple2 = String, {show = fun s -> s};;
                  val couple2 : string ty * string showable = (String, {show = <fun>})

                  Voilà, ma valeur couple est une paire constituée d'une représentation au runtime du type int ainsi que d'un dictionnaire implémentant l'interface showable pour lui. Mais avec les type sommes extensibles, on perd la vérifications de l'exhaustivité lorsque l'on s'en sert. ;-)

                  Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                  • [^] # Re: Raisons d'essayer Rust

                    Posté par  (site Web personnel) . Évalué à 4 (+2/-0). Dernière modification le 16/11/20 à 21:25.

                    C'est l'implémentation de la fonctionnalité qui veut cela, mais ça n'a rien à voir avec la notion générale de type somme. La valeur (du couple) est un dictionnaire, c'est-à-dire un type produit. Quand vous utilisez une interface, vous affirmez l'existence unique d'un certain dictionnaire pour le type qui vous intéresse. Alors, effectivement, un énoncé qui quantifie existentiellement et de manière unique sur les types est équivalent à une disjonction exclusive infinie, mais c'est là une vision tordue de ce que sont réellement les types sommes.

                    C'est peut-être une vision tordue (pour le théoricien du typage), mais, dans les faits, c'est équivalent (au côté infini près) et simple à comprendre, même si ce n'est pas une implémentation exclusive des types somme directement calquée sur un papier de recherche. Et ce n'est pas une question d'implémentation : le langage définit explicitement ces actions pour les interfaces, donc ça fait partie du langage (beaucoup de code en dépend).

                    C'est tout à fait possible en OCaml avec les types GADT extensibles, mais je dois dire que je n'en ai jamais bien compris l'intérêt (c'est quoi ces fameuses applications intéressantes ?).

                    Par exemple, en Go on peut faire sans plus d'histoires un marshall et unmarshall (à l'aide du module encoding/gob) de presque n'importe quel type de façon type safe (en OCaml le module équivalent n'est pas type safe). Autre exemple : printf.

                    À la différence qu'il faut se taper à la main ce que le compilateur Go fait automatiquement pour vous.

                    Ce n'est pas une petite différence ! Surtout que le « à la main » passe par le concept de GADT, qui n'est pas le plus accessible, et je ne vois pas comment tu pourrais définir une fonction qui marche pour tout type (dont ceux définis ultérieurement par un utilisateur).

                    Mais avec les type sommes extensibles, on perd la vérifications de l'exhaustivité lorsque l'on s'en sert. ;-)

                    Note que, bien que pratique, la vérification d'exhaustivité a un inconvénient : elle ne se prête pas aux modifications et réparations graduelles du code. Si tu ajoutes un habitant à ton type somme, tu casses toutes les utilisations. Inversement, si on essaie de changer les utilisations d'abord, l'exhaustivité empêche de compiler également. C'est pas forcément toujours important, mais ça se heurte à certaines façons de faire : par exemple, faire son refactoring à base de petits commits, sans casser le code entre-temps.

                    • [^] # Re: Raisons d'essayer Rust

                      Posté par  . Évalué à 1 (+0/-1). Dernière modification le 17/11/20 à 01:42.

                      C'est peut-être une vision tordue (pour le théoricien du typage), mais, dans les faits, c'est équivalent (au côté infini près) et simple à comprendre, même si ce n'est pas une implémentation exclusive des types somme directement calquée sur un papier de recherche.

                      Pas besoin d'un papier de recherche pour cela, la notion de type somme c'est du niveau licence en mathématiques : c'est la somme disjointe d'ensemble (définie par une disjonction exclusive) qui est la notion jumelle du produit cartésien (type produit défini par une conjonction). Il n'y a rien de hautement sophistiqué là-dedans. C'est bien pour cela que je m'étonne de leur absence.

                      D'ailleurs, outre cette représentation dynamique des types, vous avez d'autres types sommes dans le langage. Si j'ai bien compris, la valeur nil peut être affectée à n'importe quel type, ce qui fait qu'en réalité vous ne manipulez que des types options, qui est un type somme : c'est le type 1 + A pour tout type A. Ce qui fait que lorsque vous avez des fonctions qui peuvent retourner une erreur, en renvoyant une paire, vous retournez un type produit de la forme suivante : (1 + A) * (1 + B) = 1 + A + B + A*B, où l'on voit les 4 cas à gérer que déplorait Colin Pitrat dans son premier commentaire. Là où, avec un type somme de résultat, il n'y a que les deux cas A + B.

                      Et ce n'est pas équivalent, même dans les faits. Cela l'est tout autant que Platon définissant l'homme comme un bipède sans plumes, et Diogène le cynique de le rayer en se promenant avec un poulet déplumé tout en haranguant la foule d'un « Voici un homme ! ».

                      Il y a d'autres types sommes infinis que l'on ne peut définir, en tant que tel, avec Go : celui des listes par exemples. Un type somme récursif, comme celui des listes, se développe en une somme infinie : 1 + A + A^2 + A^3 + ..., c'est-à-dire la liste vide, ou les listes à un élément, ou les listes à deux éléments, ou les listes à trois éléments…

                      Tout ce que vous avez, c'est le type somme extensible que j'ai défini précédemment, qui est géré par le compilo et accolé dans une paire à toute valeur utilisant une interface. Puis vous faites du pattern matching dessus. Mais les seuls cas d'usage que j'ai vu, c'est avec l'interface vide pour palier le manque de polymorphisme dans le langage. L'exemple que tu m'as donné pour les arbres ne semble pas faire de d'analyse de cas sur la représentation dynamique des types, mais définir des arbres dans un langage qui n'a pas de type somme (directement définissables et accessibles au programmeur) mais que des types produits (ou struct ou enregistrements…) comme on peut le faire en C, C++, Java, Python, etc.

                      Ce n'est pas une petite différence ! Surtout que le « à la main » passe par le concept de GADT, qui n'est pas le plus accessible

                      Ce que je veux dire c'est que l'outillage est là pour avoir une représentation dynamique des types, et que ce que j'ai fait à la main le compilo pourrait le faire automatiquement. Ce n'est pas la partie la plus dure, seulement je ne crois pas que quelqu'un y ait vu un quelconque intérêt à l'implémenter.

                      je ne vois pas comment tu pourrais définir une fonction qui marche pour tout type (dont ceux définis ultérieurement par un utilisateur).

                      Je ne vois pas comment tu pourrais le faire aussi, simplement en bénéficiant d'un représentation dynamique des types. Soit la fonction est polymorphiquement paramétrique et là c'est très usuel en OCaml, soit le poylmorphisme est ad hoc et c'est le système des type classes (ou interface pour Go, mais le switch sur type ne sert à rien). Où a-t-on besoin d'une représentation dynamique des types ?

                      Par exemple, en Go on peut faire sans plus d'histoires un marshall et unmarshall (à l'aide du module encoding/gob) de presque n'importe quel type de façon type safe (en OCaml le module équivalent n'est pas type safe).

                      On peut faire de la sérialisation/déserialisation de manière type safe en OCaml, mais c'est ad hoc, il faut choisir son format. Les modules de la lib standard font cela de manière générique, ce qui ne peut être type safe. Même si l'on avait une représenation dynamique des types, je ne suis pas certain que l'on puisse réellement faire cela de manière générique et type safe.

                      Autre exemple : printf.

                      Pour faire un printf type safe il me semble qu'il faut les types dépendants, et en OCaml c'est implémenté via un GADT.

                      Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                      • [^] # Re: Raisons d'essayer Rust

                        Posté par  . Évalué à 5 (+2/-0).

                        En Go, les valeurs ne prennent pas nil, seulement les références.

                        "La première sécurité est la liberté"

                      • [^] # Re: Raisons d'essayer Rust

                        Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

                        C'est bien pour cela que je m'étonne de leur absence.

                        C'est pas étonnant qu'ils soient absents, puisque la pratique de programmation qu'ils couvrent est déjà couverte par le concept des interfaces (bien que ce ne soient pas des type somme implémentés de façon scolaire). Je donne un exemple plus bas pour les listes simplement chaînées.

                        Si j'ai bien compris, la valeur nil peut être affectée à n'importe quel type

                        Uniquement à un type pointeur *A (équivalent à option A), et il n'y a pas une seule valeur nil, mais une différente pour chaque type pointeur (A(nil) est différent de B(nil)). Et c'est en effet un type somme qui couvre le cas courant du type option.

                        l'on voit les 4 cas à gérer que déplorait Colin Pitrat dans son premier commentaire. Là où, avec un type somme de résultat, il n'y a que les deux cas A + B.

                        Sauf que les conventions font qu'il n'y a pas 4 cas en pratique, mais bien 2 sauf 3 dans de rares cas comme Read qui peut renvoyer une erreur EOF qu'on peut vouloir traiter de façon particulière (cas non gérable par un simple type somme A + B de toutes façons).

                        L'exemple que tu m'as donné pour les arbres ne semble pas faire de d'analyse de cas sur la représentation dynamique des types, mais définir des arbres dans un langage qui n'a pas de type somme (directement définissables et accessibles au programmeur) mais que des types produits (ou struct ou enregistrements…) comme on peut le faire en C, C++, Java, Python, etc.

                        Eh bien si, mais le fichier que j'ai donné contient uniquement la définition, pour les utilisations il faut regarder ailleurs comme ici qui implémente une marche sur l'ast en faisant un switch sur le type de l'interface qui sert à représenter un nœud.

                        Si on peut définir un ast, on peut définir en particulier une liste simplement chaînée :

                        package main
                        
                        import "fmt"
                        
                        type LinkedList interface {
                            ImplementsLinkedList()
                        }
                        
                        type Nil struct{}
                        
                        func (e Nil) ImplementsLinkedList() {}
                        
                        type IntList struct {
                            Head int
                            Tail LinkedList
                        }
                        
                        func (l IntList) ImplementsLinkedList() {}
                        
                        func main() {
                            mylist := IntList{Head: 1, Tail: IntList{Head: 2, Tail: Nil{}}}
                            fmt.Printf("%+v\n", mylist)
                            // affiche: {Head:1 Tail:{Head:2 Tail:{}}}
                            switch LinkedList(mylist).(type) {
                            case Nil:
                                fmt.Printf("%+v\n", Nil{}) // n'est pas exécuté
                            case IntList:
                                fmt.Printf("Tête: %v, Queue: %+v\n", mylist.Head, mylist.Tail)
                            }
                            // affiche: Tête: 1, Queue: {Head:2 Tail:{}}
                        }

                        Ici j'ai défini un type pour les listes d'entiers : jusqu'à ce qu'on ait des types génériques en Go (dans un an ou deux), si on veut plus générique il faut utiliser interface{} à la place de int, ce qui donne des listes non limitées à un seul type, comme dans Python, Perl ou Lisp.

                        En pratique je sais pas si grand monde fait ceci, vu que les listes simplement chaînées n'apportent que rarement quelque chose par rapport à un tableau dynamique.

                        Même si l'on avait une représenation dynamique des types, je ne suis pas certain que l'on puisse réellement faire cela de manière générique et type safe.

                        Ah si, en Go c'est bien type safe : lorsque la valeur marshallée n'est plus compatible (parce que le code a évolué, par exemple, ou qu'on essaye de la récupérer dans le mauvais type), on obtient proprement une erreur.

                        Les types switchs sur les interfaces combinés au package reflect permettent d'inspecter les types (par exemples récupérer une liste des champs d'un struct). On peut donc définir printf récursivement sur une interface : les feuilles correspondent aux types de bases du langage (int, float64, etc.). Un exemple accessible est la définition récursive de Printf pour des types quelconques : ici on a un switch sur les types simples et un traitement différent pour les types plus complexes de type reflect.Value dans une fonction différent ici (en particulier la gestion récursives des structs, maps et tableaux).

                        Pour faire un printf type safe il me semble qu'il faut les types dépendants, et en OCaml c'est implémenté via un GADT.

                        Pour Printf en Go ce que je veux dire c'est qu'il peut afficher n'importe quel type de façon générique (le type est passé sous la forme interface{} pour pouvoir l'inspecter), même ceux définis par un utilisateur : pratique pour debugger par exemple. En OCaml il faut définir un affichage pour chaque type. En Haskell et Rust c'est pareil (aux derive près qui rendent la chose un peu plus facile, mais cela reste très manuel).

                        • [^] # Re: Raisons d'essayer Rust

                          Posté par  (site Web personnel) . Évalué à 2 (+0/-0). Dernière modification le 17/11/20 à 11:03.

                          Petit détail : à la place de

                          switch LinkedList(mylist).(type) {

                          j'aurais du écrire :

                          switch l := LinkedList(mylist).(type) {

                          puis utiliser l et pas mylist dans le Printf (ça change rien au résultat ici, mais sinon je n'illustre pas vraiment un type switch sur une interface dont on ne connaîtrait pas le type concret).

                        • [^] # Re: Raisons d'essayer Rust

                          Posté par  . Évalué à 2 (+1/-1). Dernière modification le 17/11/20 à 16:32.

                          C'est pas étonnant qu'ils soient absents, puisque la pratique de programmation qu'ils couvrent est déjà couverte par le concept des interfaces

                          Absolument pas, et ton exemple sur les listes chaînées me le prouvent une fois de plus. Mais là on tourne en rond, et tu ne sembles vraiment pas saisir à quoi servent les type sommes (étonnant pour une personne ayant programmé en Rust, Haskell, OCaml et Coq).

                          Sauf que les conventions font qu'il n'y a pas 4 cas en pratique, mais bien 2 sauf 3 dans de rares cas comme Read qui peut renvoyer une erreur EOF qu'on peut vouloir traiter de façon particulière (cas non gérable par un simple type somme A + B de toutes façons).

                          Avec les types sommes ce serait plus qu'une convention, elle serait vérifiée statiquement par le type checker. Outre l'API étrange de renvoyer une erreur et une résultat en même temps, cela se fait très bien avec un type somme, même de la forme A + B. L'existence du troisième cas fait que la valeur de retour est de la forme A + B + A * B, ce qui, par de l'algèbre niveau collège, se ramène à une somme de deux termes A + B * (1 + A)

                          Pour le reste c'est lié à la réflection et au typage dynamique du langage, ce qui peut aussi se faire statiquement à base de prépocesseur comme en Haskell, Rust… ou OCaml. Et donc toute cette discussion est hors sujet par rapport à l'existence ou l'absence des types sommes.

                          Soit dit en passant, si l'on oublie de traiter un cas dans un type somme, le code compile et le compilateur émet un warning et non une erreur :

                          function [] -> 0;;
                          Line 1, characters 0-16:
                          Warning 8: this pattern-matching is not exhaustive.
                          Here is an example of a case that is not matched:
                          _::_
                          - : 'a list -> int = <fun>

                          Autrement dit cela fait exactement ce que tu attends (cf. une de tes réponses à Nicolas Boulay)

                          C'est très sympa, mais je pense personnellement que ce serait mieux sous la forme d'un warning ou d'une analyse statique : ça permet de trouver tous les endroits à modifier tout en permettant les modifications graduelles. Ça donnerait les avantages sans les inconvénients.

                          C'est exactement le comportement du compilateur face aux types sommes : warning et analyse statique.

                          Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                          • [^] # Re: Raisons d'essayer Rust

                            Posté par  (site Web personnel) . Évalué à 2 (+0/-0).

                            Absolument pas, et ton exemple sur les listes chaînées me le prouvent une fois de plus. Mais là on tourne en rond, et tu ne sembles vraiment pas saisir à quoi servent les type sommes (étonnant pour une personne ayant programmé en Rust, Haskell, OCaml et Coq).

                            Mon impression, c'est que tu ne sembles pas saisir les subtilités des interfaces Go partant de préjugés sur le sens du mot « interface » et de comment sont implémentées certaines choses (comme la réflection) dans d'autres langages : bien que servant pour le dispatch dynamique de méthode (typage produit), ainsi que pour la réflection, elles sont des valeurs typés statiquement et les type switchs qui permettent d'accéder au type concret sont typés 100% statiquement également. Même concernant le check d'exhaustivité, rien n'empêcherait de le faire non plus (comme le prouve l'existence d'une analyse statique dont j'ai donné le lien).

                            Comme tu le dis, ayant programmé dans ces quatre langages, je pense savoir ce qui est possible ou non avec un type somme et, en dehors des limites du fait de l'absence de généricité en Go, je peux représenter tout type somme comme la disjonction des types satisfaisant une interface.

                            Pour le reste c'est lié à la réflection et au typage dynamique du langage, ce qui peut aussi se faire statiquement à base de prépocesseur comme en Haskell, Rust… ou OCaml.

                            Lors d'un type switch il n'y a pas de typage dynamique : toutes les variables sont typés statiquement dans chaque branche, comme lors d'un filtrage par motif d'un type somme. En Go, il n'y a une sorte de typage dynamique uniquement lors des assertions de type explicites, mais pour le coup c'est vraiment une question orthogonale (même si elles partagent une partie d'implémentation et concepts par simple économie d'ingénierie).

                            Et « le reste » c'était pour répondre à ta question annexe des applications intéressantes de la réflection et choses qu'elle peut faire et que Rust, Haskell ou OCaml ne peuvent pas faire (le préprocesseur sert uniquement à réduire la quantité de boilerplate, mais ne résout pas le problème de fond). L'utilisation de Printf dans l'exemple que j'ai donné l'illustre bien : rien n'a été fait pour permettre l'affichage des nouveaux types.

                            le code compile et le compilateur émet un warning et non une erreur

                            Pour le coup, my bad, ça fait un moment que je fais pas d'OCaml, j'ai dû confondre avec Rust où cela produit bien une erreur et non un warning.

                            • [^] # Re: Raisons d'essayer Rust

                              Posté par  . Évalué à 2 (+0/-0). Dernière modification le 17/11/20 à 23:59.

                              Mon impression, c'est que tu ne sembles pas saisir les subtilités des interfaces Go partant de préjugés sur le sens du mot « interface »

                              C'est pas un préjugé sur le mot « interface », c'est ainsi que le concept est présenté dans A Tour of Go.

                              Et je dois dire que j'ai toujours du mal à voir en quoi, avec tous les exemples que tu me donnes, cela peut être considérer comme fournissant une fonctionnalité équivalente aux types sommes. Si c'est le cas tu dois bien pouvoir me définir simplement la somme de deux types, disons int et string, de telles sortes que le type ainsi défini ne puisse contenir que des valeurs de l'un ou de l'autre (union ensembliste) et rien que celles-là.

                              Autrement dit, tu dois pouvoir me définir cela (je l'espère sans encodage tricky) :

                              type int_or_string = A of int | B of string

                              À partir de ton type de liste de int, tu devrais pouvoir définir trivialement cette version de la fonction last calculant le dernier élément de la liste :

                              let rec last = function
                                | [] -> None
                                | [x] | [_; x] | [_; _; x] -> Some x
                                | _ :: _ :: _ :: l -> last l

                              Quand je dis cette version, c'est celle avec 5 switchs. C'est équivalent au déroulement d'une boucle. Partant du type list A = 1 + A + A^2 + A^3 + A^4 +..., j'ai un switch pour les puissances 0, 1, 2 et 3 puis un dernier pour toute puissance ≥ 4.

                              je peux représenter tout type somme comme la disjonction des types satisfaisant une interface.

                              Comme peux tu limiter, à priori, les membres de cette disjonction ? Qui te dit qu'un utilisateur, sur lequel tu n'as aucun contrôle, ne créera pas un type satisfaisant l'interface en question ?

                              Tant que je n'aurais pas une solution, claire et nette, à ces problèmes, je ne considérerais pas les interfaces comme un substitut aux types sommes. Même pas de loin, en tant que version du pauvre.

                              Lors d'un type switch il n'y a pas de typage dynamique : toutes les variables sont typés statiquement dans chaque branche, comme lors d'un filtrage par motif d'un type somme.

                              Quand je parlais de typage dynamique, je faisais allusion au type interface{}. C'est le type top (celui qui contient tout valeur, qui est le plus grand de tous les types par la relation de sous-typage), et un type switch sur une telle valeur ressemble fortement à du rafinement de type dynamique (même si le type est connu statiquement dans chaque branche). Un langage à typage dynamique, comme Python, est un langage avec un seul type statique à savoir top. C'était pour parler de votre gestion du Printf, qui est appelé sur cette interface.

                              Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                              • [^] # Re: Raisons d'essayer Rust

                                Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

                                C'est pas un préjugé sur le mot « interface », c'est ainsi que le concept est présenté dans A Tour of Go.

                                Oui, enfin, c'est un tutoriel. Notre échange ne porte pas, j'espère, sur l'application la plus commune des interfaces Go (auquel cas on serait d'accord que les types somme n'est pas cette application), mais sur leur expressivité d'un point de vue typage.

                                Autrement dit, tu dois pouvoir me définir cela (je l'espère sans encodage tricky) :

                                C'est possible d'exprimer cela à l'aide du système de types. Tu peux considérer cela tricky, si tu veux, je ne vais pas débattre sur ce genre de considérations : l'encodage demande simplement l'écriture d'une méthode vide pour affirmer que le type satisfait l'interface, donc deux fois plus verbeux qu'avec un type somme dédié.

                                package main
                                
                                import "fmt"
                                
                                type IntOrString interface {
                                    ImplementsIntOrString()
                                }
                                
                                type A int
                                type B string
                                
                                func (a A) ImplementsIntOrString() {}
                                func (b B) ImplementsIntOrString() {}
                                
                                func main() {
                                    x := IntOrString(A(3))
                                    fmt.Printf("%+v\n", x) // affiche 3
                                    switch y := x.(type) { // donner le nom y à la version destructurée
                                    case A:
                                        fmt.Printf("%d\n", y) // y statiquement de type A dans cette branche
                                        // affiche 3
                                    case B:
                                        fmt.Printf("%s\n", y) // y statiquement de type B
                                    }
                                }

                                Quand je dis cette version, c'est celle avec 5 switchs. C'est équivalent au déroulement d'une boucle. Partant du type list A = 1 + A + A2 + A3 + A4 +…, j'ai un switch pour les puissances 0, 1, 2 et 3 puis un dernier pour toute puissance ≥ 4.

                                Là tu parles d'autre chose : tu parles de l'expressivité de la construction de filtrage par motif et de sa destructuration en profondeur qui est plus du domaine de la syntaxe (le genre de trucs qui s'implémente avec des macros en lisp ou racket). En Go, tu n'as pas de syntaxe facilitant cela. Comprenons-nous bien, c'est bien une fonctionnalité syntaxique liée aux type sommes, mais ce que tu semblais débattre c'était la fonctionnalité de typage, la possibilité même de représenter les types somme à l'aide du système de types !

                                Comme peux tu limiter, à priori, les membres de cette disjonction ? Qui te dit qu'un utilisateur, sur lequel tu n'as aucun contrôle, ne créera pas un type satisfaisant l'interface en question ?

                                C'est une question valable : en Go tu pourrais rajouter des habitants en code utilisateur, mais ça n'affecterait que le code de l'utilisateur pour une éventuelle analyse statique d'exhaustivité, ça ne va rien casser; une façon d'éviter cela pour un utilisateur, c'est de ne pas exporter la méthode publiquement; en pratique, l'utilisateur ne va pas implémenter une méthode pour satisfaire l'interface de toutes façons.

                                Tant que je n'aurais pas une solution, claire et nette, à ces problèmes, je ne considérerais pas les interfaces comme un substitut aux types sommes. Même pas de loin, en tant que version du pauvre.

                                Je ne vais pas débattre de la notion de version du pauvre : perso je parle uniquement sur ce qu'il est possible ou non d'exprimer avec le système de types de façon pas trop compliquée.

                                Un langage à typage dynamique, comme Python, est un langage avec un seul type statique à savoir top.

                                Personnellement, ma définition de langage dynamique est différente (à ce titre Perl n'est pas à typage dynamique, car sans type top unique), d'où ma confusion, mais admettons. Un type top semble nécessaire pour définir un Printf avec de la réflexion (que ce soit avec du typage statique ou dynamique).

                                • [^] # Re: Raisons d'essayer Rust

                                  Posté par  . Évalué à 2 (+0/-0).

                                  Je vais plutôt donner un réponse générale à ton commentaire qui, malheureusement, confirme ma position initiale : le système des interfaces n'est pas une alternative viable aux types sommes.

                                  Premièrement, ta solution d'interface IntOrString ne répond pas pleinement au problème. La somme est ouverte et non fermée, j'ai pu y rajouter un float64 avec ce code :

                                  type C float64
                                  func (c C) ImplementsIntOrString() {}

                                  Le point positif, si on passe un valeur de type C à une fonction qui attend uniquement un A ou un B, est que tout se passe bien. J'ai testé avec une fonction d'affichage (qui alors n'affiche rien), mais si elle devait retourner une valeur je suppose qu'elle retournerait la valeur par défaut de son type de retour (il me semble que chaque type a une valeur par défaut).

                                  Néanmoins, le filtrage par motif n'est pas qu'une affaire de syntaxe : c'est un élément crucial dans l'utilisation des types sommes, sans cela ils perdent tout de leur intérêt. Mon point n'était pas tant de savoir si l'on pouvait encoder des types sommes avec les interfaces, mais de savoir s'ils satisfont cette requête :

                                  C'est pas étonnant qu'ils soient absents, puisque la pratique de programmation qu'ils couvrent est déjà couverte par le concept des interfaces (bien que ce ne soient pas des type somme implémentés de façon scolaire).

                                  J'ai cité un extrait d'un de tes commentaires précédents. Sans le filtrage par motifs, ils ne couvrent absolument par la pratique de programmation des types sommes. En revanche, sous le capot, ils sont bien implémentés sous la forme d'un type somme extensible, comme celui que j'avais utilisé pour la réflexion. C'est là un choix d'implémentation pour la fonctionnalité du polymorphisme ad hoc.

                                  Il y a eu deux propositions pour ajouter cette fonctionnalité à OCaml : une basée sur une implémentation à la Go utilisant le type pour la réflexion et une autre basée sur le système de modules et foncteurs à la type classes de Haskell. C'est la dernière qui a était retenue et qui est en cours d'élaboration, mais qui demande encore des travaux de recherches pour faire cela proprement au niveau du système de types.

                                  Quoi qu'il en soit, merci pour cette échange qui m'a permis de comprendre un peu mieux la manière dont les interfaces sont implémentées dans ce langage.

                                  Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                                  • [^] # Re: Raisons d'essayer Rust

                                    Posté par  (site Web personnel) . Évalué à 4 (+2/-0).

                                    La somme est ouverte et non fermée, j'ai pu y rajouter un float64 avec ce code

                                    Oui, tout à fait.

                                    Le point positif, si on passe un valeur de type C à une fonction qui attend uniquement un A ou un B, est que tout se passe bien. J'ai testé avec une fonction d'affichage (qui alors n'affiche rien), mais si elle devait retourner une valeur je suppose qu'elle retournerait la valeur par défaut de son type de retour (il me semble que chaque type a une valeur par défaut).

                                    Oui.

                                    Néanmoins, le filtrage par motif n'est pas qu'une affaire de syntaxe : c'est un élément crucial dans l'utilisation des types sommes, sans cela ils perdent tout de leur intérêt.

                                    Il n'y a pas de filtrage par motif avec patterns qui vont au-delà d'un niveau de profondeur de destructuration (l'équivalent du type switch), donc il faut imbriquer des type switchs au besoin pour plus de profondeur.

                                    Au final, c'est de mon point de vue bien une différence syntaxique, même si récursive (implémentable par macro récursive, mais macro quand même). De là à dire qu'ils perdent tout de leur intérêt et que c'est non viable, je suis un peu surpris. D'après mon expérience, les destructurations à juste un niveau de profondeur sont les plus fréquentes. La plupart des filtrages par motifs se traduisent en un seul type switch, parfois une imbrication dans certaines branches, ce qui reste dans le domaine du viable à mon avis. Je me demande quels types d'applications tu as en tête, pour que le côté récursif en profondeur de la destructuration te semble à ce point indispensable.

                                    Quoi qu'il en soit, merci pour cette échange qui m'a permis de comprendre un peu mieux la manière dont les interfaces sont implémentées dans ce langage.

                                    Merci aussi, c'était intéressant d'avoir le point de vue de quelqu'un qui découvre Go avec un état d'esprit venant de OCaml. Ça m'a permis de me mettre à jour sur certaines nouveautés en OCaml !

                                    • [^] # Re: Raisons d'essayer Rust

                                      Posté par  . Évalué à 2 (+1/-1). Dernière modification le 18/11/20 à 17:46.

                                      Je me demande quels types d'applications tu as en tête, pour que le côté récursif en profondeur de la destructuration te semble à ce point indispensable.

                                      Regarde par exemple ce fil de discussion ouvert par Gasche sur le forum OCaml : Musings on extended pattern-matching syntaxes. Tu verras à quel point le pattern matching est essentiel dans l'utilisation poussée des types sommes. Quand on a des types sommes un peu partout, on déstructure à tous les niveaux et tout le temps.

                                      Je t'accorde que la différence est syntaxique, et ne concerne pas le système de types, mais elle est essentielle pour l'usage convenable de types sommes. Déjà qu'ils sont une plaie à définir en Go, qu'ils sont ouverts et non fermés, si en plus on n'a aucune construction syntaxique aidant à leur usage, ce qui est clair et net c'est que ce n'est absolument pas une alternative viable aux types sommes.

                                      Quand je t'ai montré comment faire de la réflexion avec des GADT en OCaml, tu m'as répondu cela :

                                      Ce n'est pas une petite différence ! Surtout que le « à la main » passe par le concept de GADT, qui n'est pas le plus accessible

                                      Et après tu me présentes les interfaces comme alternatives crédibles aux types sommes, via ton encodage du dessus. Tu es à la limite de la mauvaise foi sur le coup.

                                      Je veux bien t'accorder que le concept de GADT, si on veut en explorer tout le potentiel, est difficile d'accès. Mais le cas d'usage que je proposais est utilisable par n'importe qui, sans avoir besoin de saisir les subtilités qu'il y a derrière. Je redonne sa définition :

                                      type _ ty = ..
                                      
                                      type _ ty +=
                                        | Int : int ty
                                        | Float : float ty
                                        | List : 'a ty -> 'a list ty

                                      Sans rien comprendre à ce qui passe derrière, lorsque l'on définit un nouveau type, disons celui-ci :

                                      type moule = {pseudo : string; karma : int}

                                      il suffit de faire avec :

                                      type _ ty += Moule : moule ty

                                      c'est-à-dire rajouter un constructeur constant qui à le nom du type puis lui donné le bon type. Ce n'est certainement pas plus compliqué que ton encodage de types somme ouverts. Et au fond, votre type interface {} ce n'est rien d'autre que celui-ci emballé dans un type existentiel :

                                      type top = Top : 'a ty * 'a -> top

                                      Exemple de code qui prend une interface {} et fait un switch sur type :

                                      let f (Top (t, v)) = match t with
                                        | Int -> Format.printf "%d@." v
                                        | Float -> Format.printf "%f@." v
                                        | _ -> ()
                                      
                                      (* et à l'usage *)
                                      f (Top(Int, 1));;
                                      1
                                      - : unit = ()
                                      
                                      f (Top(Float, 2.5));;
                                      2.500000
                                      - : unit = ()

                                      Ce qui revient à ce que je disais depuis le début : vous avez un type somme extensible gérer par le compilateur pour chaque interface, qu'il enveloppe dans une type existentiel. Comme je l'ai fait : j'ai emballé le GADT extensible dans un couple (type, valeur) puis je fait du pattern matching sur des constantes : ce à quoi se résume ton encodage des types sommes en Go.

                                      Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                                      • [^] # Re: Raisons d'essayer Rust

                                        Posté par  (site Web personnel) . Évalué à 4 (+2/-0).

                                        Regarde par exemple ce fil de discussion ouvert par Gasche sur le forum OCaml : Musings on extended pattern-matching syntaxes. Tu verras à quel point le pattern matching est essentiel dans l'utilisation poussée des types sommes. Quand on a des types sommes un peu partout, on déstructure à tous les niveaux et tout le temps.

                                        J'ai lu le fil de discussion, et je suis honnêtement un peu dans la même situation que schonfeder à la fin : je suis assez perplexe quand au problème pratique que résolvent ces considérations syntaxiques, il me manque un contexte. Là, on a affaire à une discussion à propos d'une syntaxe qui n'existe que dans une extension expérimentale, sans indications du problème qu'elles rendraient significativement plus simple en pratique : c'est une réponse à cela que je cherchais avec ma question.

                                        Peut-être suis-je biaisé dans mon expérience en OCaml et Coq. C'est surtout Compcert et lecture de quelques parties du compilateur OCaml : l'essentiel des filtrages par motif qu'on y trouve ne fait pas appel à des fonctionnalités avancées ni à des destructuration vraiment profondes, c'est du code plutôt simple, assez verbeux et accessible (peut-être la partie (f)lambda plus nouvelle fait-elle un usage plus avancé du filtrage par motif ?).

                                        Je veux bien t'accorder que le concept de GADT, si on veut en explorer tout le potentiel, est difficile d'accès. Mais le cas d'usage que je proposais est utilisable par n'importe qui, sans avoir besoin de saisir les subtilités qu'il y a derrière.

                                        Je t'accorde cela. Note cependant que l'idée qu'il est possible pour un programmeur OCaml d'écrire du code inacessible à d'autres programmeurs OCaml ne passe pas bien dans toutes les communautés. Go est un peu une réaction en ce sens : l'idée que tout programmeur doit être capable de maîtriser le langage grosso-modo dans son ensemble, pour faciliter les contributions externes ici et là. C'est pour cela que j'ai écrit :

                                        Ce n'est pas une petite différence ! Surtout que le « à la main » passe par le concept de GADT, qui n'est pas le plus accessible

                                        Car la possibilité de faire accepter une feature comme les GADTs dans la communauté Go est nulle, même si une de leurs applications est relativement accessible.

                                        Et puis tu compares un différence de verbosité (encodage verbeux des types somme en Go), voire d'expressivité du typage (distinction somme ouverte ou fermée conduisant à des warnings utiles), à une différence d'expressivité du langage (définir une fonction d'affichage qui donnera un affichage par défaut pour les types créés par n'importe quel utilisateur). Qu'est-ce qui est plus ou moins important restera subjectif, mais il s'agit de considérations de nature différentes quand même.

                                        • [^] # Re: Raisons d'essayer Rust

                                          Posté par  (site Web personnel) . Évalué à 4 (+2/-0).

                                          J'ai lu le fil de discussion, et je suis honnêtement un peu dans la même situation que schonfeder à la fin

                                          J'ai lu un peu les pages suivantes : l'exemple de trefis est intéressant et montre une différence plus significative (un seul raise au lieu de trois), au prix d'une formule un peu longue.

                                          Ceci dit la version proposée utilisant les types somme actuels :

                                          match f () with
                                          | Some y ->
                                            begin match g y with
                                            | A x ->
                                              begin match h x with
                                              | Ok result -> result
                                              | Error _ -> raise Not_found
                                              end
                                            | _ -> raise Not_found
                                            end
                                          | None -> raise Not_found

                                          ressemblerait beaucoup à la version Go naturelle :

                                          y, err := f()
                                          if err != nil {
                                             return err
                                          }
                                          switch x := g(y).(type) {
                                          case A:
                                             result, err := h(x)
                                             if err != nil {
                                                 return err
                                             }
                                             return result 
                                          }
                                          return errors.New("not found")

                                          qui, dans ce cas particulier pourrait être raccourcie (mais c'est pas idiomatique) :

                                          y, err := f()
                                          if err == nil {
                                             switch x := g(y).(type) {
                                             case A:
                                                result, err := h(x)
                                                if err == nil {
                                                   return result
                                                }
                                          }
                                          return errors.New("not found")

                                          dans le même style que la solution proposée avec la nouvelle syntaxe qui fonce direct sur la seul cas intéressant :

                                          let foo () =
                                            match f () with
                                            | Some y and (g y with A x and (h x with Ok result)) -> result
                                            | _ -> raise Not_found

                                          Mais, est-ce une situation fréquente que de se retrouver avec seulement un cas qui correspond à un résultat, et tout plein de cas qui correspondent exactement à la même erreur ? Et si on veut ajouter un contexte à l'erreur au lieu d'un simple Not_found (est-ce que c'est h ou g qui n'a pas trouvé de résultat ?), la nouvelle syntaxe proposée n'est plus utilisable.

                                          • [^] # Re: Raisons d'essayer Rust

                                            Posté par  . Évalué à 3 (+0/-0).

                                            Que pensez-vous de la futur généricité dans Go ?

                                            cf https://blog.golang.org/generics-next-step

                                            Est-ce que cela se rapproche des modules paramétriques OCaml ou pas du tout ?

                                            "La première sécurité est la liberté"

                                            • [^] # Re: Raisons d'essayer Rust

                                              Posté par  (site Web personnel) . Évalué à 4 (+2/-0).

                                              C'est beaucoup moins puissant, mais aussi beaucoup moins compliqué (tant pour l'utilisateur que l'implémentation). Mais faut dire que les modules paramétriques OCaml sont vraiment puissants par rapport à ce qu'on trouve ailleurs.

                                              Dans les différences concrètes que je peux voir se produire en pratique, il n'est par exemple pas possible d'utiliser une fonction générique autrement que pour l'instantier ou l'appeler (donc, si j'ai bien compris le draft, tu ne pourras pas faire une fonction qui renvoie une fonction générique, par exemple). On peut aussi uniquement paramétrer par des types et non des valeurs. Pas de généricité dans les paramètres de méthode non plus (la question de savoir si une méthode permet de satisfaire une interface deviendrait floue).

                                              J'aimais pas trop certains drafts précédents, mais le dernier je le trouve pas mal : faudra voir ce que ça donne et couvre statistiquement en pratique.

                                              • [^] # Re: Raisons d'essayer Rust

                                                Posté par  . Évalué à 3 (+0/-0).

                                                En C++, l'explosion de code provenait de la création d'une nouvelle fonction à chaque usage d'une fonction générique. Les compilo essaient tant bien que mal de factoriser tout ça,mais ce n'était qu'une optimisation.

                                                J'aimais bien le concept de créer un type réel à partir d'un type générique en Ocaml, cela permet justement d'éviter de créer "sous le tapis", une nouvelle fonction.

                                                Je ne sais pas comment Go va gérer ça.

                                                "La première sécurité est la liberté"

                                                • [^] # Re: Raisons d'essayer Rust

                                                  Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

                                                  A priori les fonctions génériques seront compilées une seule fois :

                                                  Generic functions, rather than generic types, can probably be compiled using an interface-based approach. That will optimize compile time, in that the function is only compiled once, but there will be some run time cost.

                                                  J'imagine que définir les contraintes pour les types génériques à l'aide d'interfaces rend cette approche assez naturelle. Rien dans le draft n'impose vraiment une approche ou l'autre, ceci dit.

                                                • [^] # Re: Raisons d'essayer Rust

                                                  Posté par  . Évalué à 3 (+1/-0).

                                                  En C++, l'explosion de code provenait de la création d'une nouvelle fonction à chaque usage d'une fonction générique.

                                                  Ça c'est parce que C++ pratique la monomorphisation afin de spécifier chaque instance de template et avoir un code plus efficace. Cela se fait au prix d'une multiplication du nombre de fonctions pour chaque instance d'une template. C'est un choix de stratégie de compilation : les template sont instanciés à la compilation, là où un foncteur OCaml ne génère qu'une fonction et est instancié à l'exécution (une fois compilé un module n'est rien d'autre qu'un enregistrement comme les autres, tous les types étant effacés à la compilation, et un foncteur un fonction générique qui crée un enregistrement à partir d'un autre enregistrement).

                                                  Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                                                  • [^] # Re: Raisons d'essayer Rust

                                                    Posté par  . Évalué à 3 (+0/-0).

                                                    Quand tu as 10 fois totot() dans le code, dupliqué 10 fois le code n'est plus rapide que dans un microbenchmark. L'inflation de la taille de code ne permet pas d'utiliser le cache Instruction correctement. Il est plus efficace d'avoir une fois le code et gérer l'inline au besoin (selon que toto<>(a) est petit ou gros, ou si "a" est une constante).

                                                    "La première sécurité est la liberté"

                                        • [^] # Re: Raisons d'essayer Rust

                                          Posté par  . Évalué à 1 (+0/-1). Dernière modification le 19/11/20 à 16:20.

                                          Je peux comprendre que tu ne perçoives pas l'intérêt de la proposition expérimentale de gasche, mais le but du lien était de te montrer que les power users des types somme ressentent le besoin de travailler sur les patterns et motifs de destruction, que cela fait partie intégrante de l'usage des types sommes. Un type somme ne peut s'utiliser autrement que par déstructuration de son motif.

                                          Si tu veux un exemple moins alambiqué et bien plus courant, regarde celui de la fonction simplify_first_col dans ce journal de gasche. Ou celui-ci :

                                          type A | B
                                          
                                          match v with
                                          |Ok A -> ...
                                          |Ok B -> ...
                                          |Error _ -> ...
                                          
                                          (* comparé à *)
                                          
                                          match v with
                                          | Ok v -> match v with A -> ... | B -> ...
                                          | Error _ -> ...

                                          Quand on fait une somme de somme, extrêmement courant dans un langage disposant nativement et naturellement de types somme, on aime bien aplatir la somme dans la destruction de motif.

                                          Et puis tu compares un différence de verbosité (encodage verbeux des types somme en Go), voire d'expressivité du typage (distinction somme ouverte ou fermée conduisant à des warnings utiles), à une différence d'expressivité du langage (définir une fonction d'affichage qui donnera un affichage par défaut pour les types créés par n'importe quel utilisateur).

                                          Alors premièrement la distinction somme ouverte ou fermée est de la plus haute importance ! Cela va bien au-delà de l'existence de warnings utiles, il s'agit de savoir de quoi l'on parle. Une somme ouverte est plus ou moins improprement appelé une somme. Elle ne désigne pas un seul type précis mais une famille illimitée de types : faire passer l'un pour l'autre s'est se moquer du monde. Je me souviens d'avoir vu reprocher en ces lieux que les philosophes ne définissaient pas ou mal leurs concepts, ce qui m'avait fait sourire, mais à côté des programmeurs ils sont la rigueur incarnée en comparaison.

                                          Une somme de deux types c'est le plus petit des majorants (à isomorphisme près) par la relation de sous-typage, une somme ouverte les contenant c'est un majorant quelconque ! Je te donne deux entiers, disons 2 et 3, et si je te dis que 36 est leur plus petit commun multiple tu ne tiques pas ? C'est exactement ce que tu fais en voulant éluder la distinction entre somme ouverte et fermée. Le produit étant la version duale c'est à dire les plus grand des minorants, ou le pgcd pour l'analogue chez les entiers.

                                          Donc non, c'est clair, vous n'avez pas la somme de deux types en Go, jusqu'à preuve du contraire. Ou si vous l'aviez, il faudra m'expliquer la raison étrange d'avoir choisi le produit, et non la somme, comme convention pour vos fonctions pouvant retourner une erreur (ce qui est le point de départ de toute cette discussion).

                                          Je le répète et j'insiste profondément : tu n'as pas encodé la somme de deux types. Ce point là, je ne te l'accorderais jamais. Par comparaison, on mimique en OCaml avec des types fantomes ce qui en réalité relève des types dépendants : le concept de fichier en lecture seul est un type dépendant. Et pourtant aucun développeur OCaml n'irait soutenir que l'on a des types dépendants en OCaml (voir cette interview de Leo White).

                                          Ensuite mon illustration sur le type top et la fonction qui en fait usage avait pour but de t'expliquer, avec du code, ce que gasche t'avait déjà expliqué en 2017. Et pour ceux de ta communauté (voir la PR sur les type somme) qui craignent un problème d'interaction entre type somme et interface {}, ils ne se passe rien, tout va bien :

                                          Ok (Top(Int, 1));;
                                          - : (top, 'a) result = Ok (Top (Int, <poly>))
                                          

                                          Pour ce qui est de la fonction d'affichage générique, c'est une question de goût : je n'en ai rien à faire. Pour être franc et honnête, le système de types de Go est pour moi de l'ordre du jouer playskool que l'on donne aux enfants, je n'irais pas utiliser une telle chose juste pour disposer d'une telle fonction : j'ai des ppx pour cela, elles dérivent un formateur pour les types définis par l'utilisateur et utilisables avec la chaîne de formatage "%a" (l'équivalent de ton "+%v").

                                          Format.printf "%a" Format.pp_print_int 1;;
                                          1- : unit = ()

                                          Les extensions de syntaxes ppx dérivent un pretty printer (pp) à utiliser comme celui fournit par défaut pour les int. Ce qui revient tout à fait au même que votre printf en Go, mais avec un système de types un peu plus sérieux.

                                          Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                                          • [^] # Re: Raisons d'essayer Rust

                                            Posté par  (site Web personnel) . Évalué à 4 (+2/-0). Dernière modification le 19/11/20 à 17:27.

                                            Donc non, c'est clair, vous n'avez pas la somme de deux types en Go, jusqu'à preuve du contraire. Ou si vous l'aviez, il faudra m'expliquer la raison étrange d'avoir choisi le produit, et non la somme, comme convention pour vos fonctions pouvant retourner une erreur (ce qui est le point de départ de toute cette discussion).

                                            Parce c'est plus simple pour le flot de contrôle et se gère plus naturellement et légèrement avec les structures de contrôle impératives classiques déjà présentes. Go n'est pas un langage où les gens veulent une étude continue de nouvelles fonctionnalités syntaxiques.

                                            Les extensions de syntaxes ppx dérivent un pretty printer (pp) à utiliser comme celui fournit par défaut pour les int. Ce qui revient tout à fait au même que votre printf en Go, mais avec un système de types un peu plus sérieux.

                                            La notion de système de types sérieux ou non n'est pas constructive : perso, je comprends que c'est une façon de transmettre ton ressenti et, connaissant bien OCaml, je peux prendre ça avec recul. Mais si je ne connaissais pas OCaml, je pourrais naïvement croire que le système de types de Go est clairement mauvais et croire aux allégations mal informées d'ignorance académique de ses créateurs ou autre : oui, ils ne sont pas des chercheurs en systèmes de types avancés, mais un langage de programmation ne se limite pas à ça.

                                            Le but d'un langage de programmation, ce n'est pas son système de typage, c'est d'écrire des programmes qui répondent à des besoins. Et concernant le typage, tout n'est pas question de puissance, mais aussi d'accessibilité en pratique : c'est pas pour rien que les modules en OCaml sont sous-utilisés et considérés un concept pour non-débutants par rapport aux interfaces Go, accessibles aux débutants, par exemple. Le but des types somme pour les errerus, c'est de bien gérer ces erreurs : d'autres approches comme celle de Go permettent statistiquement de bien gérer les erreurs aussi (voire mieux que les exceptions OCaml), sont-elles moins sérieuses ? La réflection permet grâce au marshalling de sauver facilement un état (par exemple sauvegarde pour un jeu) sans avoir à écrire de code, c'est une application pratique, peu importe le sérieux de l'approche par rapport à une autre approche qui ne propose pas encore d'équivalent simple.

                                            • [^] # Re: Raisons d'essayer Rust

                                              Posté par  . Évalué à 2 (+0/-0).

                                              Mais si je ne connaissais pas OCaml, je pourrais naïvement croire que le système de types de Go est clairement mauvais et croire aux allégations mal informées d'ignorance académique de ses créateurs ou autre : oui, ils ne sont pas des chercheurs en systèmes de types avancés, mais un langage de programmation ne se limite pas à ça.

                                              Je n'ai pas dit que leur système de types est mauvais (ce que tu développes et défend, je l'avais fait à l'époque du journal de gasche que j'ai cité plus haut) mais qu'il n'était pas assez sérieux pour ce que j'aime faire. Tu ne peux pas dire objectivement que les deux systèmes de types de OCaml et de Go sont du même niveau. J'exprime donc une opinion personnelle relative à mes besoins.

                                              Quand tu répètes en boucle que tu apprécies la réflexion pour ses capacités de marshalling, je ne nie pas la chose, ni interdit à toi ou d'autres développeurs d'avoir ce besoin. En revanche, je me permets de répondre que le fait que ce soit intégré de base dans le langage ne m'intéresse pas. J'ai pas le droit d'avoir des goûts distincts des tiens ?

                                              Par contre lorsque tu affirmes que vous avez des types somme, je me permet de le nier objectivement. Que tu veuilles entendre ou non raison m'indiffère, mais la chose n'en reste pas moins vraie. Tu peux tout à fait reconnaître que tu n'en vois pas l'intérêt, et que cela ne te manque pas plus que cela, mais je n'appellerais jamais type somme ce dont vous disposez, tout comme je n'appellerai pas types dépendants ce que l'on fait en OCaml avec des types fantômes.

                                              Pour revenir sur les goûts personnels, je reconnais volontiers qu'un langage ne se limite pas à un système de types, mais je ne suis pas programmeur, et la seule chose qui m'intéresse dans un langage c'est de jouer avec ses types. Avec Go je m'ennuierai vite, avec OCaml je m'amuse : c'est tout ce que je voulais dire par système de types plus sérieux. Tout comme j'adore son système de modules et me moque totalement qu'il soit ou non facilement accessible : je suis mathématicien et logicien de formation et le discours mathématiques est structuré selon le système de modules OCaml depuis Euclide jusqu'à nos jours, je navigue là dedans comme un poisson dans l'eau. Par exemples les concepts de bases de l'algèbre linéaire sont des types de modules :

                                              module type Groupe = sig
                                               type t (* le type du support du groupe *)
                                               val zero : t (*l'élément neutre *)
                                               val add : t -> t -> t (* l'opération binaire sur t *)
                                               val neg : t -> t (* tout élément a un oppposé *)
                                              end
                                              
                                              (* un corps est un groupe muni d'une opération inversible
                                                 et distributive sur l'addition *)
                                              module type Corps = sig
                                                include Groupe
                                                val one : t (* élément neutre pour la multiplication *)
                                                val mul : t -> t -> t (* la multiplication *)
                                                val inv : t -> t (* inverse pour la multiplication *)
                                              end
                                              
                                              (* Enfin un espace vectoriel est la donné d'un groupe, dont les
                                                 éléments sont appelés vecteurs, puis d'un corps dont les éléments
                                                 sont appelé scalaires, ainsi que d'un produit extérieur des scalaires
                                                 sur les vecteurs, aussi appelé `scale` en anglais *)
                                              module Espace_vectoriel = sig
                                                module V : Groupe
                                                module K : Corps
                                                type vector = V.t
                                                type scalar = K.t
                                                val scale : scalar -> vector -> vector
                                              end

                                              La seule chose que je regrette étant que le système ne soit pas assez intégré dans le cœur du langage (c'est un langage à part, OCaml c'est deux langages en un), de telle sorte que l'on ne peut pas manipuler vraiment les modules comme des objets de première classe, mais cela évolue dans le bon sens. Avec le systèmes des interfaces, je resentirai un manque dans ma capacité d'expression. D'ailleurs le système est plus puissant que le système des types classes de Haskell, et lorsque Simon Peyton Jones (principal contributeur au compilateur GHC de Haskell) a soutenu le contraire, je me suis permis de le corriger. Ce n'est donc pas ton insistance à affirmer que Go a des types somme qui m'arrêtera.

                                              Enfin concernant l'ignorance académique sur les systèmes de types, j'aimerai me tromper mais c'est vraiment ce que j'ai ressenti à la lecture en diagonale de la PR sur les types somme dont tu as donné le lien. Tu pensais, je te cite, que « mon impression, c'est que tu ne sembles pas saisir les subtilités des interfaces Go » alors qu'il m'a fallu moins de dix minutes de réflexion pour en comprendre la formalisation à partir de ta description. Ce fût plus long pour te convaincre que tel était le cas : relis bien tous mes commentaires.

                                              Pour conclure sur la notion d'abstraction :

                                              On n'emploie pas toujours correctement en logique le terme : abstraction. Nous ne devons pas dire : abstraire quelque chose (abstrahere aliquid), mais abstraire de quelque chose (abstrahere ab aliquo). Si par exemple dans un drap écarlate je pense uniquement au rouge, je fais abstraction du drap; si je fais en outre abstraction de ce dernier en mettant à penser l'écarlate comme une substance matérielle en général, je fais abstraction d'encore plus de déterminations, et mon concept est devenu par là encore plus abstrait. Car plus on écarte d'un concept de caractères distinctifs des choses, c'est-à-dire plus on en abstrait de déterminations, plus le concept est abstrait. C'est donc abstrayants (conceptus abstrahentes) qu'on devrait nommer les concepts abstraits, c'est-à-dire ceux dans lesquels d'avantage d'abstractions ont eu lieu. Ainsi par exemple, le concept de corps n'est pas à proprement parler un concept abstrait; car du corps lui-même je ne puis faire abstraction puisque dans ce cas je n'en aurais pas le concept. Mais il faut bien que je fasse abstraction de la taille, de la couleur, de la dureté ou de la fluidité, bref de tous les déterminations spéciales des corps particuliers — Le concept le plus abstrait est celui qui n'a rien de commun avec ce qui diffèrent de lui. C'est le concept de quelque chose; car le concept qui s'en distingue est celui de rien et il n'a donc rien de commun avec le quelque chose.

                                              Kant, Logique.

                                              Votre type interface {}, c'est cela, le concept le plus abstrait, celui de quelque chose : il n'y a plus rien à abstraire, l'ensemble des méthodes étant vide. Raison pour laquelle tu as dis que si tu l'avais choisi au lieu des int tu aurais eu des listes hétérogènes de choses à la python. Comme cela, une liste de choses :

                                              [Top(Int, 1); Top(List Int, [0; 1; 2]); Top(Float, 3.5)];;
                                              - : top list = [Top (Int, <poly>); Top (List Int, <poly>); Top (Float, <poly>)]

                                              Ce qui montre que le type top pourrait s'appeler anything (enfin ce n'est pas tout à fait celui-ci le type anything en OCaml mais type any = Any : 'a -> any). Et les concepts c'est la base de la pensée : plus je peux en définir, plus je peux exprimer convenablement ma pensée. Mais un type, en informatique, n'étant rien d'autre qu'un concept… Ou l'on voit d'ailleurs que, déjà en français, la notion de listes s'exprime sous la forme d'un type paramétrique (généricité quand tu nous tiens ;-) : le complément du nom dans les expressions liste d'entiers, liste de course, liste de noms ou liste de choses est lui même un concept, c'est-à-dire un type. ;-)

                                              Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                                              • [^] # Re: Raisons d'essayer Rust

                                                Posté par  (site Web personnel) . Évalué à 2 (+0/-0).

                                                Je n'ai pas dit que leur système de types est mauvais (ce que tu développes et défend, je l'avais fait à l'époque du journal de gasche que j'ai cité plus haut) mais qu'il n'était pas assez sérieux pour ce que j'aime faire.

                                                Pour te rassurer, c'est comme cela que je l'ai pris, mais ce n'est pas comme cela que tu l'as écrit, il y avait un point après « sérieux » : facile à interpréter plus génériquement :-)

                                                Quand tu répètes en boucle que tu apprécies la réflexion pour ses capacités de marshalling, je ne nie pas la chose, ni interdit à toi ou d'autres développeurs d'avoir ce besoin. En revanche, je me permets de répondre que le fait que ce soit intégré de base dans le langage ne m'intéresse pas. J'ai pas le droit d'avoir des goûts distincts des tiens ?

                                                Oui, mais l'histoire de la réflexion est sortie suite à une question initiale sur les applications intéressantes (au sens général a priori) de celles-ci : à aucun moment je n'ai essayé de te convaincre que ça serait intéressant pour toi en particulier, mais de t'informer à ta demande pourquoi ça l'était pour d'autres. C'est pour ça qu'on se répète, parce que j'ai répondu à cela et tu n'as, par la suite, qu'uniquement insisté sur le fait que tu n'en avais rien à faire et que c'était question de goût, ce qui était HS et donne l'impression que tu te fiches de l'intérêt de la chose en général, même si ce n'est pas ton intention.

                                                Pour revenir sur les goûts personnels, je reconnais volontiers qu'un langage ne se limite pas à un système de types, mais je ne suis pas programmeur, et la seule chose qui m'intéresse dans un langage c'est de jouer avec ses types. Avec Go je m'ennuierai vite, avec OCaml je m'amuse : c'est tout ce que je voulais dire par système de types plus sérieux.

                                                Encore une fois, c'est effectivement ce que j'ai imaginé de ta part. Mais ce n'est pas évident dans tes messages. Lorsque tu expliques que pour toi le système de types de Go est un « jouet playskool que l'on donne aux enfants », tu ne dis pas que c'est en comparaison du super jouet bien plus fun qu'est le système de types OCaml. Du coup, quelqu'un qui n'a pas une bonne connaissance de l'historique de tes messages va prendre cela dans le sens que le système de types de Go est un jouet à côté de celui de OCaml : c'est l'interprétation la plus naturelle pour un programmeur, c'est-à-dire les premiers utilisateurs de langages de programmation qui n'imaginent pas forcément que pour d'autres, un langage de programmation, c'est un terrain d'expérimentation pour jouer avec les types. Pour le coup, ils risquent de ne pas trouver ça sérieux, tu es en train de jouer après tout, même si c'est avec un système de types sérieux ;-)

                                                Ce n'est donc pas ton insistance à affirmer que Go a des types somme qui m'arrêtera.

                                                Si tu relis mes messages, tu verras que depuis le début je n'ai pas vraiment affirmé cela (le débat de théorie des typages n'est pas inintéressant pour moi, mais secondaire). Plutôt, j'ai affirmé que je pouvais traiter (ou encoder) avec des types Go les cas d'utilisation (pratique de programmation) des types sommes. Je n'affirme même pas que mon énoncé a un sens propre en théorie des types. Et j'ai fait l'effort de donner des exemples à l'appui pour chaque chose que tu as demandé (en commençant par les listes chaînées), avec l'idée que tu t'intéressais au langage et pas uniquement à faire rentrer son système de types dans ton jeu sur les systèmes de types. Quand j'ai dit utilisation ou pratique de programmation, c'est dans un sens pratique, pas au sens d'un énoncé de typage précis : les cas d'utilisation que j'ai pu rencontrer personnellement, que ce soit dans CompCert en Coq ou dans OCaml (ce que j'ai précisé ensuite). J'ai bien reconnu d'emblée que ce soit plus verbeux, ou l'absence de gestion de la distinction ouverte ou fermée : distinction peut-être capitale pour qui utilise le langage comme plateforme de jeu sur les types, mais distinction beaucoup plus secondaire autrement.

                                                Pour en revenir sur les goûts : si tu relis mes messages sur ce fil, tu verras que je ne parle pas vraiment de mes goûts (je l'ai déjà fait dans le journal), mais de pourquoi, à mon avis, les choses sont ainsi en Go. Concernant mes goûts, vu le journal, c'est assez clair qu'ils sont variés et, suivant ce que je veux faire (jouer avec les types, les tableaux multi-dimensionnels ou développer un logiciel) j'utilise quelque chose de différent. Le système de types de Go n'est pour moi ni meilleur ni pire dans l'absolu que celui d'OCaml : il répond simplement à des besoins et un public différents. Je n'affirme pas par là que leur champ d'application est sans intersection en pratique (ce n'est pas le cas), mais que, globalement, Go est plus un langage dont le design vise à répondre aux besoins des développeurs, alors qu'OCaml vise avant tout à répondre aux besoins des chercheurs en théorie des types.

                                                • [^] # Re: Raisons d'essayer Rust

                                                  Posté par  . Évalué à 1 (+0/-1).

                                                  Lorsque tu expliques que pour toi le système de types de Go est un « jouet playskool que l'on donne aux enfants », tu ne dis pas que c'est en comparaison du super jouet bien plus fun qu'est le système de types OCaml. Du coup, quelqu'un qui n'a pas une bonne connaissance de l'historique de tes messages va prendre cela dans le sens que le système de types de Go est un jouet à côté de celui de OCaml

                                                  Il y a une chose que je ne suis pas dans ce passage de ton commentaire : l'interprétation qu'une personne sans connaissance de l'historique de mes messages est tout à fait correct. Ce système de types est un jouet pour enfant à côté de celui d'OCaml. Je comprends que cela puisse être désagréable à lire, mais c'est bien ce que je pense. Là, c'est le logicien en moi qui s'exprime.

                                                  Là logique est une science dont l'objet d'étude est, comme son nom l'indique, le langage. Mais elle ne l'étudie pas à la manière de la linguistique, c'est-à-dire qu'elle ne s'intéresse pas à telle ou telle langues données pour les analyser comparativement dans leurs structures. Si je peut m'exprimer ainsi, ce qu'elle cherche à travers la diversité des langues c'est les invariants par variations. Elle cherche les lois nécessaires de toute pensée, dont le langage n'est que le moyen d'expression. En conséquence, elle n'a pas une approche descriptive comme la linguistique, mais une approche prescriptive et, comme le disait Kant dans le traité qu'il lui a consacré :

                                                  La logique est une science rationnelle non seulement selon la forme, mais selon la matière; une science a priori des lois nécessaires de la pensée, non pas relativement à des objets particuliers, mais bien relativement à tous les objets en général; c'est donc une science du droit usage de l'entendement et de la raison en général, non pas de façon subjective, c'est-à-dire selon des principes empiriques (psychologique) : comment l'entendement pense — mais de façon objective, c'est-à-dire selon des principes a priori : comment il doit penser.

                                                  Kant, logique.

                                                  L'aspect prescriptif se trouve à la fin de la définition qu'il donne de cette science : « comment il doit penser ».

                                                  Ensuite, ce que la logique a à apporter à la programmation et à ses langages, ce n'est pas ce calcul assez simple sur les booléens, mais la théorie des types. Les notions centrales de la logique sont celles de concepts, jugement et raisonnement. Par miroir, ces notions en informatique sont celle de types, jugements de typage et dérivation de typage.

                                                  Résultat, quand je regarde le système de type de Go, j'ai une logique tellement amputée de certains de ses concepts et principes, que l'ensemble m'apparaît réellement comme un jouet : la logique sous-jacente est simpliste en comparaison de ce que contient la logique. Ce qui n'est absolument pas le cas avec le système de types d'OCaml (ou Haskell). Ce qui est amusant, au passage, c'est qu'il y a une implémentation de ce systèmes en Go : le cœur du langage c'est le système F et celui-ci constitue le langage de configuration Dhall.

                                                  Par exemple, ce qui me manque, en premier, c'est l'absence de jugement disjonctif : la totalité de ce dont nous débattons depuis le début (et même avant que j'intervienne sur ce fil de discussion). La disjonction n'est pas une opération qui prend deux booléens puis en retourne un troisième, mais une opération qui prend deux jugements puis en produit un troisième. La première opération apparait lorsque l'on interprète ces jugements selon leur valeur de vérité, et cette notion n'est pas primitive mais dérivée.

                                                  Les jugements disjonctifs donne naissance aux types somme (A ou B), là où les jugements conjonctifs donnent naissance aux types produit comme les struct (A et B). Les premiers sont fait pour être utiliser ainsi :

                                                  Le caractère propre de tous les jugements disjonctifs, qui détermine, au point de vue de la relation, leur différence spécifique relativement aux autres et particulièrement aux jugements catégoriques, consiste en ce que les membres de la disjonction sont en totalité des jugements problématiques, dont ne peut rien penser d'autre que ceci : étant les parties de la sphère d'une connaissance, chacun complétant l'autre pour former le tout (complementum ad totum), pris ensemble, ils équivalent à la sphère du tout. Il s'ensuit que la vérité doit être contenue dans l'un des jugements problématiques, ou, ce qui revient au même, que l'un d'eux doit avoir valeur assertotique, puisqu'en dehors d'eux, la sphère de la connaissance n'englobe rien sous les conditions données et qu'ils sont opposés les uns aux autres; par suite il n'est possible ni qu'il y ait en dehors un autre jugement qui soit vrai, ni qu'il y en ait plus d'un parmi eux.

                                                  Kant, logique.

                                                  Afin d'avoir ce qu'exige l'usage logique des jugements disjonctifs (« il n'est possible ni qu'il y ait en dehors un autre jugement qui soit vrai, ni qu'il y en ait plus d'un parmi eux »), il est nécessaire que le langage, via son système de types, fournisse de manière primitive les sommes fermées, puis qu'il vérifie l'exhaustivité de leur usage.

                                                  Dans le reste, on peut aussi lui reprocher de ne pas pouvoir faire abstraction des jugements, c'est-à-dire pas de variables de types ou généricité. Il n'y a pas de sous-typage, le cœur de la syllogistique aristotélicienne : tous les animaux sont mortels, tous les hommes sont des animaux, donc tous les hommes sont mortels.1 L'usage des raisonnements hypothétique (si A alors B) est bien présent au niveau des booléens (if then else) mais ne semble pas être mis si en avant que cela, et moins évident à utiliser qu'OCaml, au niveau des types (ici c'est le type des fonction, comme f : int -> float).

                                                  Je devrais sans doute pouvoir trouver d'autres choses qui me manquent en creusant un peu le langage, mais cela fait déjà beaucoup trop à mon goût. Autant d'absences qui peuvent difficilement me le faire considérer autrement que comme un jouet, relativement à ce que je chercherais à faire avec, quand bien même cette expression pourrait paraître déplaisante à ces utilisateurs.


                                                  1. c'est la combinaison des type sommes avec sous-typage qui nous permettent d'encoder, ce qui relèvent des types dépendants, les droits de lecture et écriture sur un fichier. 

                                                  Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                                                  • [^] # Re: Raisons d'essayer Rust

                                                    Posté par  . Évalué à 2 (+0/-0).

                                                    Là logique est une science dont l'objet d'étude est, comme son nom l'indique, le langage. Mais elle ne l'étudie pas à la manière de la linguistique, c'est-à-dire qu'elle ne s'intéresse pas à telle ou telle langues données pour les analyser comparativement dans leurs structures. Si je peut m'exprimer ainsi, ce qu'elle cherche à travers la diversité des langues c'est les invariants par variations. Elle cherche les lois nécessaires de toute pensée, dont le langage n'est que le moyen d'expression.

                                                    De ce qu'il me semble la logique s'intéresse uniquement aux langues formelles et à l'aspect objectif. Ce n'est qu'une petite partie du langage.

                                                    Le fait d'être prescriptif c'est une branche de la logique, mais je pense que ça capture bien ce que je trouve profondément gênant avec tes messages. Ça et l'appel continuel aux philosophes, ça ne donne pas vraiment l'impression d'une discussion, mais un étalage de ta science.

                                                    Voila c'était plus pour te donner un ressenti que pour en discuter. Bonne soirée

                                                    • [^] # Re: Raisons d'essayer Rust

                                                      Posté par  . Évalué à 2 (+0/-0). Dernière modification le 29/11/20 à 22:24.

                                                      De ce qu'il me semble la logique s'intéresse uniquement aux langues formelles et à l'aspect objectif. Ce n'est qu'une petite partie du langage.

                                                      La logique ne s'intéresse pas, et à aucun moment, uniquement aux langues formelles. Elle s'intéresse à ce qu'il y a de formel dans toute langue (que celle-ci soit naturelle ou artificielle), ce qui constitue bien un aspect objectif du langage. Que ce ne soit qu'une partie, voire même une toute petite partie, de ce qui constitue un langage, je ne l'ai jamais nié et ne le nierai jamais. Ce n'est pas pour autant que c'est une partie à négliger.

                                                      Pour ce qui est de la nature de l'analyse logique et des langages dont elles s'occupent, tu pourras, par exemple, te reporter à mon analyse grammaticale du groupe adverbiale dans la langue française. Cela à commencer dans un journal sur la manière d'automatiser la punition consistant à recopier 100 fois la phrase « je ne dois pas jeter d'avion en papier en classe ». La précision « 100 fois » constituant le groupe adverbial (lui-même constitué d'un noyau et d'un complément), c'est-à-dire une précision, ou une spécification, de ce que le verbe « copier » signifie dans une telle phrase. Le début de l'analyse se trouve ici dans le journal à l'origine de la discussion, puis est développée plus en profondeur dans cette dépêche. Tout ce que j'ai écrit à l'époque relève de l'analyse logique du langage, bien que le support sur lequel elle s'exerce soit du français on ne peut plus usuel.

                                                      C'est justement parce que l'analyse logique s'applique à tout langage existant qu'elle se permet de prescrire des règles à ceux qui prétendent en inventer de toutes pièces. Libre à chacun de faire fie de ce qu'une science vielle de plus de 2500 ans affirme, mais il faut, dans ce cas, s'attendre à des retours de bâtons ;-)

                                                      Le fait d'être prescriptif c'est une branche de la logique, mais je pense que ça capture bien ce que je trouve profondément gênant avec tes messages.

                                                      Le fait d'être prescriptif n'est nullement une branche mais l'essence même de la logique, je ne vois pas à quoi elle pourrait servir d'autre. J'entends bien que tu trouves cela gênant dans mes messages, mais je te rétorque la question : pourquoi ? Dans une autre discussion, tu en es venu à parler d'Einstein et de sa théorie de la gravitation, en expliquant, à bon droit, que ce n'est pas parce qu'une personne avait observé toute sa vie des objets tombés qu'elles comprenait la théorie de la relativité générale. Ceci étant, considérerait-on comme acceptable qu'une personne voulant construire un système de GPS se permette d'ignorer cette théorie et donc l'influence du champ de gravitation sur la marche des horloges (comment synchroniser l'horloge du satellite et celle du récepteur resté sur terre ?) ?

                                                      À titre personnel, ce que je trouve gênant est qu'il existe une science vielle de plus de 2500 ans dont certains programmeurs (en particulier certains concepteurs de langages) semblent se moquer. J'entends par « se moquer » non se rire d'elle, mais être indifférent à ce qu'elle prescrit. Et j'ai beau retourner la question dans tous les sens dans ma tête, je n'en comprends pas la raison. Mon appel aux philosophes (je ne vois pas en quoi il a été continuel) n'étant là que pour rappeler cet état de fait : les questions ne datent pas d'aujourd'hui mais ont des siècles de réflexions humaines derrière elles.

                                                      À la base tout est partie d'une question, on ne peut plus naturelle, à savoir : comme, de deux chose l'une, un appel de fonction peut réussir ou échouer, pourquoi ne pas répondre par une alternative comme le fait Rust ? Sur cela, comme il s'agit de faire usage d'un type somme1, j'ai généralisé la question : pourquoi Go n'a pas de type somme ? Sur ces entrefaits les réponses qui m'ont été apporté ne m'ont pas convaincues, si ce n'est que les utilisateurs ou responsables du langages (cf la PR mise en lien par anaseto) ne maîtrisaient pas les notions dont il était question.

                                                      Je n'ai jamais eu, et n'aurais jamais, la volonté d'étaler ma science en place publique. C'est là une attitude que je trouve soit indécente, soit puérile. En revanche, quand je demande à ce que l'on me montre un chat, puis que l'on me montre un animal qui certes, est un quadrupède, à une queue, des poils… mais qui, quand que je joue avec, se met à aboyer, je réponds que j'ai affaire à un chien et non à un chat.


                                                      1. la disjonction qui se trouve dans l'expression « soit cela réussie, soit cela échoue » est appelée somme en raison du comportement identique de la disjonction et de la conjonction par rapport à l'addition et la multiplication. Lorsque l'on commande un menu au restaurant, l'alternative «(plat et entrée) ou (plat et dessert) » est équivalente à celle-ci « plat et (entrée ou dessert) ». 

                                                      Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                                                      • [^] # Re: Raisons d'essayer Rust

                                                        Posté par  . Évalué à 2 (+0/-0).

                                                        De ce qu'il me semble la logique s'intéresse uniquement aux langues formelles et à l'aspect objectif. Ce n'est qu'une petite partie du langage.

                                                        La logique ne s'intéresse pas, et à aucun moment, uniquement aux langues formelles. Elle s'intéresse à ce qu'il y a de formel dans toute langue (que celle-ci soit naturelle ou artificielle), ce qui constitue bien un aspect objectif du langage. Que ce ne soit qu'une partie, voire même une toute petite partie, de ce qui constitue un langage, je ne l'ai jamais nié et ne le nierai jamais. Ce n'est pas pour autant que c'est une partie à négliger.

                                                        Alors essayer d'en déduire les formalisme tout en étant préscriptiviste. C'est vouloir en faire des langues formelles.

                                                        À titre personnel, ce que je trouve gênant est qu'il existe une science vielle de plus de 2500 ans dont certains programmeurs (en particulier certains concepteurs de langages) semblent se moquer. J'entends par « se moquer » non se rire d'elle, mais être indifférent à ce qu'elle prescrit. Et j'ai beau retourner la question dans tous les sens dans ma tête, je n'en comprends pas la raison.

                                                        La première chose à faire, si on cherche à comprendre c'est de se débarrasser de ses apriori1, accepter que ce que l'on prend pour acquis peut être remis en cause. Si ta question c'est "Pourquoi est-ce qu'ils se trompent ?", tu ne trouvera pas grand monde pour t'aider à trouver une réponse. De temps en temps des gens viendront essayer de dialoguer, mais ça va vite tourner en rond.

                                                        Mon appel aux philosophes (je ne vois pas en quoi il a été continuel)

                                                        Il est systématique dans chacun de tous tes commentaires. Quand tu ne cite pas nommément quelqu'un c'est 2500 ans d'histoires qui sont là pour appuyer ce que tu dis.


                                                        1. c'est très facile à dire et très compliqué à faire, on est d'accord 

                                                        • [^] # Re: Raisons d'essayer Rust

                                                          Posté par  . Évalué à 2 (+0/-0). Dernière modification le 30/11/20 à 01:28.

                                                          Alors essayer d'en déduire les formalisme tout en étant préscriptiviste. C'est vouloir en faire des langues formelles.

                                                          Mais ce n'est pas du tout ce que je prône, ni ce qu'à jamais prôné la logique. Je ne vois pas où tu veux en venir, ni ce qui pu laisser entendre cela dans mes commentaires. Quoi qu'il y a bien, dans le passé (voire le présent), des logiciens avec une telle ambition mais ils étaient anti-kantien affichés. Donc, je ne vois pas comment je peux être associer à de telles personnes.

                                                          La première chose à faire, si on cherche à comprendre c'est de se débarrasser de ses apriori, accepter que ce que l'on prend pour acquis peut être remis en cause. Si ta question c'est "Pourquoi est-ce qu'ils se trompent ?", tu ne trouvera pas grand monde pour t'aider à trouver une réponse. De temps en temps des gens viendront essayer de dialoguer, mais ça va vite tourner en rond.

                                                          Ma question, sur le cas présent, n'était pas spécialement « pourquoi ils se trompent ? », mais pourquoi alors que Colin Pitrat se pose une question que tout homme peut se poser, on lui répond que sa question a une réponse qui n'en est pas une. Et le fait que la réponse n'en est pas une, il l'explicite on ne peut plus clairement dans son commentaire. La seule chose que que j'ai fait, c'est creuser un peu plus la question.

                                                          Enfin, je suis loin d'être le dernier à accepter que ce que je prends pour acquis puisse être remis en cause : c'est bien la base de la science après tout. Mais, enfin, quand on se place sur ce terrain là, il vaut mieux avoir du répondant, sinon on se retrouve sur facebook, tweeter ou youtube à avoir des gens qui t'expliquent que le coronavirus est un complot et que le masque est inutile. Et là, effectivement, toute discussion ou dispute (lorsqu'il y a débat) est vouée à l'échec. Mais j'ai bien trop de respect tant pour anaseto que pour toi, depuis le temps que je fréquente ces lieux, pour vous ranger dans ces cases là.

                                                          Après tout, il se peut que je me sois emporté parce que c'est un sujet (sur le plan scientifique1, et donc de la rigueur intellectuelle) qui me tient particulièrement à cœur, mais je ne vois pas où j'ai dérapé. Si tu pouvais me l'indiquer (toi ou anaseto), je vous en serais reconnaissant.

                                                          Il est systématique dans chacun de tous tes commentaires. Quand tu ne cite pas nommément quelqu'un c'est 2500 ans d'histoires qui sont là pour appuyer ce que tu dis.

                                                          Mais parce que la science ne s'est pas faite en un jour. On ne peut pas aborder la savoir en partant du principe que rien ne s'est fait avant nous. Rien que ce qui a donné, par attaque et défense successive, l'informatique prend une partie de ses racines dans la philosophie kantienne et son désaccord avec Leibnitz sur certains points. Voir par exemple cet article de 1905 d'Henri Poicaré, là où il doute de l'espoir de Hilbert qui sera, justement, réduit à néant par Turing 30 ans plus tard. Comment veux-tu que je fasse comme si tout ceci n'avait pas exister ? Dois-je considérer que l'ordinateur est tombé du ciel du jour au lendemain ?


                                                          1. chose assez étrange pour moi, parce que je ne suis ni un scientifique, ni un chercheur, mais il y a certains principes qui me semblent couler comme de l'eau de source (bien plus que certains principes fondamentaux de la physique). 

                                                          Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                                                          • [^] # Re: Raisons d'essayer Rust

                                                            Posté par  (site Web personnel) . Évalué à 2 (+0/-0). Dernière modification le 30/11/20 à 10:30.

                                                            Après tout, il se peut que je me sois emporté parce que c'est un sujet (sur le plan scientifique1, et donc de la rigueur intellectuelle) qui me tient particulièrement à cœur, mais je ne vois pas où j'ai dérapé. Si tu pouvais me l'indiquer (toi ou anaseto), je vous en serais reconnaissant.

                                                            J'ai répondu un peu plus bas à des choses proches, mais vraiment, la source de notre différence de perspective est que tu ne considères que l'aspect théorie logique/théorie des types dans tes réponses, alors que nous autres nous intéressons plutôt aux interactions de l'humain avec le système de types et le langage en général.

                                                            La question de savoir si l'utilisation des interfaces à la place d'un type somme est équivalente d'un point de vue logique, ou la question d'utiliser un produit pour la gestion d'erreurs plutôt qu'un type somme qui semblerait plus naturel à un théoricien du typage, sont des questions dont les réponses ne fournissent pas d'impact pratique clair. Un choix optimal d'un point de vue langage n'a a priori aucune raison de devoir être compatible avec un raisonnement de logique, puisqu'il s'agit d'une science avant tout humaine : un langage de programmation est une interface entre l'humain et la machine.

                                                            Chercher à appliquer des idées de théorie des types est intéressant, mais sans statistiques comparative ni même une intuition des conséquences sur le nombre de bugs ou la productivité dans des contextes réels d'une idée ou l'autre, ce n'est vraiment pas quelque chose qu'on peut espérer pouvoir faire sans faire de concessions aux considérations humaines.

                                                            Je t'ai bien donné des réponses diverses à la question d'origine (accessibilité, simplicité du flot de contrôle et annotation facile des erreurs sans introduire de nouvelles syntaxes, analyses statiques qui évitent les soucis en pratique, etc.) pour ces choix et tu résumes le tout à « une réponse qui n'en est pas une » juste parce qu'il s'agit de réponses qui n'ont rien à voir avec la science de la logique.

                                                      • [^] # Re: Raisons d'essayer Rust

                                                        Posté par  (site Web personnel) . Évalué à 2 (+0/-0).

                                                        À la base tout est partie d'une question, on ne peut plus naturelle, à savoir : comme, de deux chose l'une, un appel de fonction peut réussir ou échouer, pourquoi ne pas répondre par une alternative comme le fait Rust ? Sur cela, comme il s'agit de faire usage d'un type somme1, j'ai généralisé la question : pourquoi Go n'a pas de type somme ? Sur ces entrefaits les réponses qui m'ont été apporté ne m'ont pas convaincues, si ce n'est que les utilisateurs ou responsables du langages (cf la PR mise en lien par anaseto) ne maîtrisaient pas les notions dont il était question.

                                                        Je crois que ce que tu ne vois pas depuis le début, c'est qu'un langage de programmation est normalement utilisé par des humains pour développer des logiciels. Sa création doit donc être basée non sur une science prescriptive comme la logique, mais sur une science sociale et expérimentale et encore plus vieille, basée sur l'observation des besoins des gens et, en particulier ici, des développeurs. Un système de types puissant n'est pas forcément un système de types qui conduit en pratique à une meilleure productivité ni à moins de bugs. Enfiler une armure de plates avant d'aller couper des oignons pour le repas est contre-productif et n'apporte aucune sécurité supplémentaire à moins de décider d'utiliser une épée plutôt qu'un simple couteau bon marché.

                                                        De ce point de vue là, je trouve personnellement que le système de types de Go a fait un meilleur travail que celui d'OCaml. Si j'étais rapide dans mes conclusions comme toi, je pourrais donc dire que les responsables OCaml ne maîtrisaient pas les notions de cette science sociale et expérimentale. Étant plus prudent, je pense plutôt qu'ils avaient des objectifs primaires différents avec ce langage (jouer avec les types et se faire plaisir) et que les besoins des développeurs étaient, à l'époque, beaucoup moins bien connus qu'aujourd'hui.

                            • [^] # Re: Raisons d'essayer Rust

                              Posté par  . Évalué à 1 (+0/-1). Dernière modification le 18/11/20 à 00:30.

                              L'utilisation de Printf dans l'exemple que j'ai donné l'illustre bien : rien n'a été fait pour permettre l'affichage des nouveaux types.

                              Je viens de faire un test avec votre version de Printf. Cela doit être une question de point de vue, mais pour moi elle est moisie.

                              fmt.Printf("%d", 1) // Imprime bien 1
                              
                              fmt.Printf("%d", "hello") // Imprime "%!d(string=hello)"

                              Le deuxième exemple ne devrait pas compiler! Si je dois abandonner la sécurité du typage statique parce qu'il y a des programmeurs pas capable de définir leur formateur pour les types qu'ils définissent, je préfère rester avec ce que j'ai. ;-)

                              Format.printf "%i" 1;;
                              1- : unit = ()
                              
                              Format.printf "%i" "hello";;
                              Line 1, characters 19-26:
                              Error: This expression has type string but an expression was expected of type
                                       int

                              Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                              • [^] # Re: Raisons d'essayer Rust

                                Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

                                Je viens de faire un test avec votre version de Printf. Cela doit être une question de point de vue, mais pour moi elle est moisie.

                                Tu compares à la version d'OCaml qui n'est pas comparable, car utilise un type format dédié et pas une chaîne de caractères (le fait que l'on écrive ça sous forme de chaîne est du sucre syntaxique). Il n'est pas possible de passer une chaîne de caractères non connue à la compilation en tant que format, par exemple. Ensuite, OCaml utilise des concepts de typage autrement plus avancés que les types sommes (les GADTs) pour réussir à faire une analyse statique de l'input de type format.

                                Ce n'est pas possible de faire cela en Go de façon générale, puisque Go accepte des chaînes non connues à la compilation en tant que format : il existe des analyses statiques pour chercher des erreurs dans les chaînes de format lorsque ce sont des littéraux.

                                Par contre, en Go, on a les verbes "%+v" et "%v" qui permettent d'afficher un type quelconque sans avoir à s'inquiéter de leur type, alors qu'en OCaml on n'a pas ça. C'est une question de compromis, difficile de tout avoir.

                              • [^] # Re: Raisons d'essayer Rust

                                Posté par  (site Web personnel) . Évalué à 2 (+0/-0).

                                Si je dois abandonner la sécurité du typage statique

                                Je n'avais pas réagit sur ça, mais ici il n'est pas question de sécurité du typage statique, mais de sémantique et d'expressivité du typage : en Go Printf est type safe, c'est sa sémantique qui est différente (affichage spécial en cas de verbe de formatage incorrect). Le cas du printf OCaml est, d'ailleurs, assez unique.

                                On met toujours quelque part la limite pour l'expressivité du typage : c'est un effet Pareto (20% de complexité pour 80% d'avantages), sauf qu'on ne place pas tous cette limite au même endroit (Go, OCaml ou Coq ?). La vérification absolue du format du printf, c'est sympa, mais c'est de la complexité (GADTs), pour quelque chose qui évite des erreurs pas plus fréquentes (et moins graves) qu'une erreur de signe, un accès tableau mal indexé, et bien d'autres erreurs logiques qui ne sont pas traitées en OCaml.

                                D'autant plus, qu'en l'occurrence c'est de la complexité pour quelque chose où une analyse statique traite la plupart des cas courants : lance go vet sur ton exemple et tu auras un warning. C'est une commande de base lancée par défaut normalement à chaque fois qu'on exécute les tests avec go test et qui lance un certain nombre d'analyses statiques.

                    • [^] # Re: Raisons d'essayer Rust

                      Posté par  . Évalué à 4 (+1/-0).

                      "Si tu ajoutes un habitant à ton type somme, tu casses toutes les utilisations. Inversement, si on essaie de changer les utilisations d'abord, l'exhaustivité empêche de compiler également."

                      C'est quand même génial d'avoir une fonctionnalité qui te donne précisément tous les endroits du code à modifier. Si tu utilises des pseudo AST, c'est super utile.

                      "La première sécurité est la liberté"

                      • [^] # Re: Raisons d'essayer Rust

                        Posté par  (site Web personnel) . Évalué à 3 (+1/-0). Dernière modification le 17/11/20 à 10:24.

                        C'est très sympa, mais je pense personnellement que ce serait mieux sous la forme d'un warning ou d'une analyse statique : ça permet de trouver tous les endroits à modifier tout en permettant les modifications graduelles. Ça donnerait les avantages sans les inconvénients.

                        • [^] # Re: Raisons d'essayer Rust

                          Posté par  . Évalué à 3 (+0/-0).

                          Le problème pour ton changement graduel, c'est que même si le code compile, il est faux.

                          "La première sécurité est la liberté"

                          • [^] # Re: Raisons d'essayer Rust

                            Posté par  (site Web personnel) . Évalué à 2 (+0/-0).

                            Pas forcément, lorsque tu ajoutes un nouvel habitant au type somme, cet habitant n'est encore jamais produit par le programme. Tu fais un commit. Tu continues petit à petit à ajouter les cas traitant ce nouvel habitant avec des commits régulièrement. Quand tous les cas ont été gérés (plus de warnings), on peut enfin ajouter du code qui produit effectivement cet habitant.

              • [^] # Re: Raisons d'essayer Rust

                Posté par  . Évalué à 2 (+1/-0).

                Dans le détail, les let (et ands) monadiques d'OCaml sont un peu plus que du sucre syntaxique: ils sont encore explicitement présent dans l'AST après le typage, afin d'avoir de meilleur messages d'erreurs. Typiquement, ils sont élaborés en une forme plus primitive dans la même passe qui transforme le filtrage de motif en une imbrication de tests et sauts.

  • # La pizza métal

    Posté par  (site Web personnel) . Évalué à 9 (+7/-0).

    Ce journal est une invitation à venir critiquer en disant : "mais non t'as rien compris, ce langage est beaucoup plus riche que ce que tu as pu tester, la preuve …" mais comme certains le feront mieux que moi, je m'en vais relire la petite BD de Boulet pendant ce temps.

    Sinon qu'est-ce qui t'attire dans le fait de tester différents langages comme ça ? Je suppose qu'en prenant le temps de te familiariser avec l'environnement, installer une chaîne de compilation, découvrir les librairies,, voir comment tout ça s'articule ça doit te demander pas mal de temps ?

    Ton journal m'a donné envie de découvrir J, je sais pas j'ai comme une sorte de fascination pour APL, il faudra un jour que je m'y mette.

    Et sinon, pour troller un peu, de mon côté j'aime beaucoup OCaml. Le langage me permet de mettre tellement de contraintes dans mon code, qu'au moment où il compile enfin j'ai l'impression d'avoir traversé l'Amazonie avec mon briquet et mon couteau.

    • [^] # Re: La pizza métal

      Posté par  . Évalué à 4 (+2/-0). Dernière modification le 14/11/20 à 08:39.

      la petite BD de Boulet

      ah putain qu'est-ce que j'ai ri :)

      sinon pour revenir aux coming out des langages, je me mets au Python : bin j'aime vraiment bcp en fait. je suis en train d'arriver dans la phase où je trouve le langage de plus en plus simple parce que je comprends comment ça marche derrière ("bin évidemment qu'on peut pas indexer des listes par des objets mutables, vu qu'il y accède par des hash").

      En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

    • [^] # Re: La pizza métal

      Posté par  (site Web personnel) . Évalué à 6 (+4/-0).

      Ce journal est une invitation à venir critiquer en disant : "mais non t'as rien compris, ce langage est beaucoup plus riche que ce que tu as pu tester, la preuve …" mais comme certains le feront mieux que moi, je m'en vais relire la petite BD de Boulet pendant ce temps.

      Héhé, peut-être bien :-) Le côté que j'aime bien, c'est qu'on découvre que certaines features sont super importantes pour certains, secondaires pour d'autres, voire pas rentables. Parfois, ça donne des pistes sur le type d'utilisation qui est donnée au langage, ou le background du programmeur. D'autres fois, ça nous montre juste à quel point le ressenti est individuel et l'importance des goûts et sentiments en programmation : se sentir à l'aise et épris d'affection avec un langage, indépendamment de l'objectivité statistique du sentiment, c'est important si on va passer beaucoup d'heures avec.

      Sinon qu'est-ce qui t'attire dans le fait de tester différents langages comme ça ? Je suppose qu'en prenant le temps de te familiariser avec l'environnement, installer une chaîne de compilation, découvrir les librairies,, voir comment tout ça s'articule ça doit te demander pas mal de temps ?

      Les langages de programmation, c'était un de mes passe-temps pendant quelques années. Tout comme toi avec APL, j'avais une sorte de fascination sur les différentes idées qui pouvaient exister pour représenter un programme. Et puis j'aimais tester suffisamment pour ressentir personnellement le truc, découvrir l'effort cognitif que le langage me demande, l'impact des différentes idées sur ma façon de programmer, découvrir la communauté, les outils, tout en jouant le jeu : essayer d'écrire du code idiomatique, ne pas fuir les exceptions ou les modules/foncteurs en OCaml, ni les monades/type class/foncteurs en Haskell, profiter des effets de bord et de la syntaxe flexible en Perl, programmer de façon fonctionnelle et tacite en J, ne pas fuir les macros en Tcl ou Lisp, ne pas essayer d'implémenter les monades en Go, ce genre de choses.

      Ton journal m'a donné envie de découvrir J, je sais pas j'ai comme une sorte de fascination pour APL, il faudra un jour que je m'y mette.

      Si tu aimes APL pour ses primitives (et que tu n'étais pas attaché à l'utilisation de symboles Unicode), tu devrais trouver effectivement ça intéressant : beaucoup de primitives sont des généralisations de celles d'APL, et le langage est, en général, plus fonctionnel (de quoi plaire à un OCamliste !), avec pas mal de fonctions d'ordre supérieur (qu'ils appellent adverbes et conjonctions) et la possibilité de définir les siennes facilement.

      Et sinon, pour troller un peu, de mon côté j'aime beaucoup OCaml. Le langage me permet de mettre tellement de contraintes dans mon code, qu'au moment où il compile enfin j'ai l'impression d'avoir traversé l'Amazonie avec mon briquet et mon couteau.

      C'est un bon exemple de ce que je dis plus haut : bien qu'on n'évalue pas précisément le temps que nous sauve la richesse extra des types en OCaml par rapport, par exemple, à Go, en fonction de comment on ressent le fait de devoir débugger un nil raté de temps en temps ou un switch non exhaustif, on va soit trouver ça génial (j'étais comme toi ici à une époque), soit être assez indifférent (je suis proche d'ici maintenant), voire trouver ça pas rentable (par exemple quelqu'un pour qui réussir à compiler du OCaml est pénible, à cause des messages d'erreur un peu difficiles).

      D'ailleurs, je sais pas trop pourquoi mon ressenti sur nil ou le check d'exhaustivité des filtrages par motif a évolué. Peut-être que je fais moins d'erreurs maintenant ou que je programme des trucs différents qu'avant ?

  • # Zig

    Posté par  . Évalué à 7 (+4/-0).

    Je jette un œil sur Zig aussi de temps en temps. La documentation de la bibliothèque standard n'est pas très facile à lire, et cette même bibliothèque standard est un peu un gros foutoir. Mais en dehors de ça, je trouve que ce langage a plein de trucs très intelligents qui manquent cruellement au C. Je trouve aussi que la compatibilité avec le C est super bien foutue : pouvoir importer un header C et l'utiliser directement en Zig, chapeau. Ça donne envie d'essayer des choses.

  • # termes de programmation

    Posté par  (site Web personnel) . Évalué à 7 (+7/-0).

    Ce journal fait référence à de nombreux termes avec lesquels je ne suis pas toujours à l'aise (voir que je ne connais pas du tout). Pour exemple: Typage expressif, traits, fonctions non récursives terminales, langages fonctionnels …

    Je sais pondre du code, mais j'aimerais améliorer mes connaissances en programmation, et j'ai la sensation que mieux maîtriser ses différents termes, et donc les concepts qui vont avec, m'aiderai à y arriver.

    Est ce que vous connaîtriez des ressources sur le web ou des livres qui abordent ces aspects et surtout qui seraient assez exhaustif ?

    • [^] # Re: termes de programmation

      Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

      C'est une bonne question. Je dois dire que je ne pense pas avoir appris la plupart de ces termes dans un livre ou ressource unique. La plupart du temps, c'est en lisant des articles de blog de la communauté d'un langage spécifique qu'on découvre les termes (en sachant que, parfois, d'un langage à un autre, les termes peuvent avoir des sens un peu différents, comme pour les traits ou interfaces). Souvent l'article n'explique pas le terme, donc on cherche sur wikipédia, ou on tombe sur stackoverflow ou ailleurs.

      Le wikipédia anglais est vraiment pas mal pour ce genre de choses, par exemple, pour le typage on va facilement trouver des liens vers tous les termes typiques (typage dépendant, graduel, dynamique ou statique, fort ou faible, inféré etc.). Par exemple, un système de typage est dit expressif s'il permet de décrire des pratiques, contraintes et structures complexes ; c'est un terme un peu vague. En particulier, un typage expressif permet d'écrire plus de programmes de façon type safe, ce qui veut dire en pratique en détectant plus d'erreurs à la compilation.

      • [^] # Re: termes de programmation

        Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

        De mon côté, venant du monde Java et de la programmation objet j'ai commencé en m'intéressant aux design patterns. Je voulais savoir ce qui faisait qu'une manière de coder était reconnue comme un patron de conception, ou savoir si les designs pattern étaient uniquement disponibles dans la POO ou est-ce que d'autres langages proposent des choses radicalement différentes (ça ressemble à quoi un design pattern fonctionnel, et en programmation logique ?)

        Je suis tombé sur le typeclassopedia d'Haskell, que j'ai lu et qui m'a donné un mal de tête comme j'en avait jamais connu jusqu'alors. Et en même temps, j'avais l'impression de découvrir quelque chose de complètement différent et que j'avais la une clef pour mettre en relation plein de concepts différents.

        C'était aussi l'époque où Scala venait prenait son envol, et il y avait plein d'articles de blog qui présentaient des concepts relativement communs pour qqn faisant de la programmation fonctionnelle, mais accessible pour qqn venant de java. (un article que j'ai retrouvé de mémoire et qui m'avait aussi marqué à l'époque : The Essence of the Iterator Pattern ) Je pense que maintenant ça doit continuer sur d'autres supports d'autres sources, à chercher…

        Bref, un peu de curiosité, suivre les tendances du moments en fonction des langages qui sortent et faut essayer de choper le train en marche, de toute façon tout se recycle en continue : les concepts ont été posés il y a maintenant plus de 40 ans, ils ont juste muris entre temps !

        • [^] # Re: termes de programmation

          Posté par  . Évalué à 5 (+3/-0).

          Salut,

          Moi, ce qui m'énerve le plus, c'est les mots où on peut mettre tout et n'importe quoi, comme framework.

          J'ai toujours une hésitation quand on me demande si j'ai utilisé un "framework" : on parle technique (librairie ?), organisationnel (méthodologie ?), ou est-ce une question pour placer un mot qui n'a pas de sens mais qui fait cool ?

          ;)

  • # Common Lisp: corrections

    Posté par  . Évalué à 4 (+2/-0).

    Salut,

    Common Lisp et Racket sont des langages fonctionnels, par défaut au typage dynamique

    Common Lisp est un langage multi-paradigme, pas purement fonctionnel donc, compilé et fortement typé, qui permet du typage graduel, tout en utilisant son excellent REPL (devrait-on dire, image-based development). Mais il ne fait pas autant d'inférences de typage que les langages modernes (Haskell et cie). Pour cela des librairies émergent… (https://github.com/stylewarning/coalton/)

    À noter que comme on code avec le REPL à côté, on compile notre code fonction par fonction avec un raccourci clavier… c'est terriblement efficace.

    Il existe aussi typed-racket.

    compilent vers du code assez efficace

    Carrément. Les articles de blog qui montrent que Common Lisp peut être autant ou plus efficace que du C affluent (oui oui, on croule dessous :p ). Car Common Lisp permet de déclarer ses types un peu partout, quand on veut. On peut vraiment partir à la chasse à l'optimisation.

    [macros] mais non hygiéniques

    Je ne suis pas dans ces débats, donc perso je m'en fiche. Pour se protéger, on utilise le système des gensym pour déclarer une nouvelle variable dans une macro, qui aura donc un nom unique, et voilà.

    Un peu plus fonctionnel, en particulier la construction extrêmement flexible loop

    Je suppose que tu voulais dire "un peu plus impératif" ! Exemples de loop, qu'il faut apprendre par l'exemple: https://lispcookbook.github.io/cl-cookbook/iteration.html

    le gestionnaire de paquets lui-même, bien que fonctionnel, est considéré bêta depuis très très longtemps.

    Oui, Quicklisp, comme d'autres librairies, est marqué "beta software" mais il est pleinement fonctionnel. Il n'est marqué beta que parce la communauté Common Lisp joue le jeu de la stabilité à fond. Un programme stable va durer des décennies. Il n'est pas rare qu'un programme écrit dans les année 90 (soit juste après la standardisation ANSI du language) tourne toujours sans modification. Personnellement j'ai vu des warnings de dépréciation rester pendant 16 ans ! Compare cela aux langages d'aujourd'hui, comme Python…

    Par ailleurs, Quicklisp est un peu différent des autres gestionnaires de librairies. Il fonctionne en "releases" mensuelles, un peu comme apt. Il garantit que les librairies d'une même release peuvent s'installer ensemble.

    Notez que des alternatives avec un autre modèle émergent: Ultralisp, CLPM…

    • [^] # Re: Common Lisp: corrections

      Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

      Common Lisp est un langage multi-paradigme, pas purement fonctionnel donc, compilé et fortement typé

      Oui, ils ne sont pas purement fonctionnels (OCaml non plus, ni même vraiment Haskell). Par contre même si le typage est fort, ils sont, de base, typés très dynamiquement (il est possible d'écrire des expressions, autres que des casts, qui vont produire une erreur de typage à l'exécution, ou changer impérativement le type d'une variable, par exemple). La notion de typage fort n'est d'ailleurs pas bien définie, j'imagine qu'ici ce que tu veux dire c'est l'absence de conversions implicites et autres caractéristiques de typage laxistes (qu'on peut retrouver dans C qui lui est statiquement typé, mais faiblement), ou peut-être la possibilité d'écrire certaines déclarations optionnelles de types (mais qui, en général, ne sont pas toujours validées statiquement).

      Il existe aussi typed-racket.

      C'est pour cela que j'avais précisé « par défaut », car, effectivement, à l'aide des macros on peut construire, a posteriori, du typage statique (comme pour typed-racket). Mais je suis content que tu le mentionnes, car construire un système de typage avec des macros, c'est quand même une chose impressionnante et qui mérite d'être connue !

      Car Common Lisp permet de déclarer ses types un peu partout, quand on veut. On peut vraiment partir à la chasse à l'optimisation.

      Oui, sauf que pour le coup, même si SBCL fait un peu d'inférence, elle n'est pas complète ni garantie, ce qui veut dire qu'il faut choisir un degré de safety pour les déclarations (safety 0) si on veut optimiser agressivement : on risque alors de ne pas être type safe. À utiliser avec modération :-)

      Je suppose que tu voulais dire "un peu plus impératif" !

      Oups, oui, bien sûr !

      Concernant Quicklisp et les bibliothèques, je suis allé un peu vite et mon impression est peut-être faussée, ça fait longtemps que j'ai pas plongé dedans.

  • # Null safety

    Posté par  . Évalué à 4 (+3/-0).

    Une des choses que j'ai découvert assez récemment et qui est un gros progrès c'est le principe de null safety. C'est a dire la différenciation entre un type null et non-null. Et l'obligation de gérer la valeur null quand elle est possible. Les erreurs "null pointer exception" sont parmi les plus fréquentes. Ce qui permet un code bien plus robuste aux erreurs. Un peu comme le typage statique par rapport au dynamique.
    J'ai beaucoup aimé remplacer le Java par du Kotlin grâce à ça. Mon code est bien plus robuste depuis

  • # Suggestion pour tes prochains tests

    Posté par  (site Web personnel) . Évalué à 4 (+1/-0).

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n’en sommes pas responsables.