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.
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:intty|Float:floatty|List:'aty->'alisttyletcouple=(Int,{show=string_of_int});;valcouple:intty*intshowable=(Int,{show=<fun>})(* on peut étendre le GADT *)type_ty+=String:stringtyletcouple2=String,{show=funs->s};;valcouple2:stringty*stringshowable=(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.
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'ashowable={show:'a->string}(* puis l'utiliser ainsi *)letprintdictvalue=print_endline(dict.showvalue);;valprint:'ashowable->'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 :
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.
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.
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).
Oui, on peut dire pleins de choses sur la non élégance de cet algo, mais c'est vraiment un truc comme ça que j'ai appris.
C'est le bon algo, avec néanmoins une comparaison inutile entre les deux nombres. Le principe est celui-ci :
prendre deux nombres a et b
calculer le reste r de la division de a par b
si r est nul, b est le pgcd recherché
sinon recommencer en 1 avec b et r
et c'est ce que fait ma boucle en un one liner. Il n'est pas nécessaire de chercher à savoir lequel des deux nombres est le plus grand. Si b est plus grand que a, le reste est a, et le point 4 remet tout dans le bon ordre au premier tour. Ensuite, le reste, qui servira de diviseur, sera toujours plus petit que le dividende.
De ce que je lis 40% des élèves n'y arrivaient pas.
J'ai eu beau cherché dans ce document, je ne vois pas d'où tu tires ce nombre. De mémoire, on était bien moins de 60% à y arriver. J'ai raconté cette anecdote pour montrer que le changement de base était accessible à certain élèves de primaires (les plus doués en maths d'entre eux) et qu'il devait donc être une formalité pour des étudiants dans le supérieur (ceux qui font des études d'info). Ceci étant, je me demande la part d'enfants qui appliquent sans comprendre et ceux qui comprennent réellement : la raison leur est enseignée (voir par exemple la chaîne lumni lancée pendant le confinement) mais combien la retienne et la comprenne réellement, je ne le sais.
Ceci étant, je maintiens que, comme pour le pgcg, l'alogrithme est hautement récursif et fonctionnel :
Ici les nombres sont représentés par une liste de chifres en mode "petit boutiste". Le code lui même reproduit parfaitement les différentes étapes d'apprentissage d'un enfant, et les sources d'erreurs potentielles où il faut prendre garde.
L'on apprend d'abord à ajouter des chiffres de petites tailles. Ma nièce en CP est à ce stade là, elle n'a pas encore à gérer le cas de la retenue dans la fonction ajouter_chiffre. Ensuite, lorsque la somme dépasse la dizaine, on leur enseigne que l'on crée un paquet de dix (ou dizaine) et que l'on garde le reste : deuxième branche du if then else de la fonction ajouter_chiffre. C'est là que se situe l'origine et la raison d'être de la retenue.
Ensuite, lorsque l'on pose l'addition, on a deux listes de chiffres puis l'on a va en construire une autre (sans modifier les deux en entrées) en appliquant itérativement (c'est-à-dire récursivement) l'addition de chiffre sur nos deux listes jusqu'à épuisement complet.
De plus, les différentes branches montre bien deux sources d'erreurs ou de confusion qui arrive au cours de l'apprentissage :
la première étant qu'un enfant peut ne pas savoir quoi faire lorsqu'il a épuisé une liste mais pas l'autre (deuxième branche du code)
la seconde étant qu'au début ils leur arrivent souvent d'oublier de redescendre la retenue s'il en reste une à la fin (le if then else de la dernière branche).
Enfin, la méthode est facilement généralisable pour un programmeur (ou un enfant dont le niveau d'abstraction est suffisamment développé). Ici la base est codée en dur dans l'addition de chiffres : il suffit d'en faire un paramètre. ;-)
(* le tilde ~ devant le paramètre est là pour avoir un paramètre nommé *)letajoute_chiffre~basec1c2=letres=c1+c2inifres<basethen(res,0)else(res-base,1)letadditionner~basel1l2=letrecloop(res,retenue)mn=matchm,nwith|c1::reste1,c2::reste2->letc,retenue=ajoute_chiffre~basec1(c2+retenue)inloop(c::res,retenue)reste1reste2|[],c::reste|c::reste,[]->letc,retenue=ajoute_chiffrec~base(0+retenue)inloop(c::res,retenue)[]reste|[],[]->List.rev(ifretenue=0thenreselseretenue::res)inloop([],0)l1l2(* exemples *)(* 31 + 35 = 66 en base 10 *)additionner~base:10[1;3][5;3];;-:intlist=[6;6](* la même en base 16 *)additionner~base:16[15;1][3;2];;-:intlist=[2;4](* puis en base 13 *)additionner~base:13[5;2][9;2];;-:intlist=[1;5]
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Je ne vais reprendre qu'une partie car il me semble que c'est le nœud de notre désaccord.
Il y a bien là un point de désaccord, tu sembles, à mes yeux, sous estimer grandement les capacités intellectuelles et de compréhension d'un enfant.
Je vais d'abord parler d'un personne que je connais bien : moi-même. J'ai effectué mon école primaire dans les années 80 au sein d'une école privée. Pour l'accès au collège, il y avait un examen d'entrée en 6ème. Parmi les exercices de calculs, il y en avait un qui consistait à effectuer une addition et une multiplication en base 10… puis en base 5. Il fallait ensuite convertir les résultats d'une base à l'autre pour bien vérifier que le résultat était indépendant de la base choisie.
Oui : on demandait cela à des élèves en fin de CM2. Je t'accorde que tous n'y arrivaient pas, mais j'étais loin d'être le seul à y arriver, et nous comprenions ce que nous faisions. On ne se contentait pas d'appliquer bêtement une recette sans savoir pourquoi elle fonctionne. Je suppose que le choix des bases était fait à partir du nombre de doigts que l'on possède sur nos mains. Mais dans le fond, pas de grande différence, sur le plan algorithmique, avec une base 2, 8 ou 16.
Ensuite, pour les enfants d'aujourd'hui, je n'ai comme exemple proche que mes nièces, n'ayant pas d'enfants. Elles sont en 5ème, CM1 et CP. Les deux grandes comprennent leurs algorithmes, à commencer par la raison d'être de la retenue, quand à la dernière je lui ai déjà appris les petites additions à deux chiffres sans retenue. Et non, elles n'appliquent pas bêtement une recette sans la comprendre.
surtout que ton exemple est généralement présenté comme itératif et non fonctionnel
Euh, une récursion fonctionnelle c'est itératif. Elle est où ta distinction ? Un procédé récursif ou itératif, c'est bonnet blanc et blanc bonnet. Quand une personne se dit que pour une poule, il faut un œuf, mais que pour un œuf, il faut aussi une poule, puis qu'elle réitère (note bien le choix du verbe), elle fait de la récursion. Le problème ici étant : quelle est la condition d'arrêt ?
Mais rassure moi, pour l'algorithme d'Euclide, on ne vous le présentait pas autrement qu'avec ce one liner ?
letrecpgcdab=match(amodb)with0->b|r->pgcdbr(* exemple *)pgcd2115;;-:int=3
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Le télétravail a eu pour effet de considérablement diminuer le volume de questions idiotes par jour, et c'est dommage car j'aurais du en noter quelques unes et faire un livre de blagues :)
Un peu comme les perles du bac, ça aurait pu être marrant. :-)
Trèves de plaisanterie … je ne connais pas Ocaml mais ton explication donne envie de s'y attarder un peu pour en connaître un peu plus
J'ai jeté un œil du côté de elixir (mentionné dans un autre commentaire par un adepte de python), et ça peut être intéressant à regarder pour appréhender l'approche fonctionnelle de la programmation. Venant de python ce sera peut être moins perturbant : pas de typage statique et une syntaxe plus proche.
ajouter=&(&1+&2)# ils considérent cela comme du sucre syntaxique pourajouter=fn(x,y)->x+y
Utiliser la notation de De Bruijn dans un langage généraliste, je dois dire que je n'avais jamais vu cela. :-D
Leur présentation de la notion de clôture (closure) me semble un peu étrange par rapport à ce qu'elle est réellement, mais à part cela ça peut être amusant de regarder de ce côté.
Sinon, pour ce qui est de python vs OCaml, il y a le développeur d'un gestionnaire de paquet multi-platforme (Linux, MacOS et Windows) zero install qui a fait la migration du code de python vers OCaml en 2013. Il a détaillé son expériences sur son blog dans une longue série d'articles. Au bilan on se retrouve 6 ans plus tard avec une personne qui pense que le projet est mort parce qu'il y a peu voire pas d'activité sur le dépôt de code, et l'auteur de lui répondre :
Well, this is the problem with OCaml. When 0install was written in Python, I did frequent bug-fix releases. Now it’s in OCaml, there’s no need to do that. e.g. there were 8 Python releases in 2012 vs 1 OCaml release last year. I prefer it that way, but it’s bad for the project’s metrics.
Je ne sais pas quoi répondre. On est pas dans le même monde.
Sans doute pas dans le même, effectivement. ;-)
Il ne faut pas s'arrêter au vocabulaire que j'ai utilisé : ici je m'adresse au lectorat de linuxfr, où je partais d'une réponse à quelqu'un qui mentionnait des « ingénieurs au sens large » et des « programmeurs lambda ». Je m'attends à ce que cette population ne soit pas choquée et comprenne la notion de boucle de rétroaction.
Ceci étant mon monde n'est pas celui de l'informatique mais de la musique, qui est loin d'être composé de techniciens et d'ingénieurs, et je te garantie qu'ils comprennent ce qu'est une boucle de rétroaction : quand il y a un larsen, ils savent d'où cela vient et comment régler le problème. ;-)
Tu verrais la difficulté pour les prof de collèges pour expliquer la récursivité aux élèves…
Je ne sais pas la difficulté que rencontre les profs de collèges sur ce point mais, pour ma part, les premiers algorithmes que j'ai appris étant enfant sont hautement récursifs et totalement fonctionnels dans leurs principes, et il en est de même de tous les écoliers de France depuis des décennies. Je veux parler des algorithmes d'addition et de multiplication avec retenue, ainsi que celui de la division euclidienne (celui où l'on pose la division). Tu as peut être oublié ce que tu faisais étant enfant à l'école primaire.
Si je veux être pompeux et utiliser un concept sophistiqué, je pourrais employer, par exemple, celui de monade d'état et pourtant c'est au cœur des API REST. Si j'en crois la présentation de wikipédia :
La communication client–serveur s'effectue sans conservation de l'état de la session de communication sur le serveur entre deux requêtes successives. L'état de la session est conservé par le client et transmis à chaque nouvelle requête. Les requêtes du client contiennent donc toute l'information nécessaire pour que le serveur puisse y répondre. La visibilité des interactions entre les composants s'en retrouve améliorée puisque les requêtes sont complètes. La tolérance aux échecs est également plus grande. De plus, le fait de ne pas avoir à maintenir une connexion permanente entre le client et le serveur permet au serveur de répondre à d'autres requêtes venant d'autres clients sans saturer l'ensemble de ses ports de communication, ce qui améliore l'extensibilité du système.
Moi quand je lis ça, je me dis : tiens, ils travaillent dans une monade d'état.
Peut être que l'on ne comprends pas le mot simple de la même façon. Je parle de facilité d'accès et pas de simplicité algorithmique. La programmation fonctionnel permet d'obtenir des
Je voudrais bien te répondre, mais il semble que ton clavier se soit blo
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Mais il est bien plus intuitif de décrire un programme de manière impérative, il est aussi bien plus confortable d'avoir un accès direct aux effets de bords.
Parle pour toi, l'impératif rempli d'effets de bords je n'y comprends strictement rien dès que le programme devient trop gros. C'est un paradigme qui m'est totalement incompréhensible.
Autre chose manipuler des listes ne demande pas de notions particulières, là où la programmation fonctionnelle va te demander de comprendre des notions comme la reduction, fold, zip,… C'est élégant, mais c'est des notions plus sophistiquées.
Qu'est ce qu'il y a de compliqué là-dedans ? Prenons le reduce (ou fold) : c'est un peu comme une boucle de rétroaction. J'ai un système qui prend deux entrées et renvoie une sortie, je mets ma liste sur une des entrées qui sera consommée un par un, et je branche la sortie sur l'autre entrée : le système s'arrête quand il n'y plus rien à consommer dans liste.
C'est le schéma du dessus le reduce : je branche ma liste dans l'entrée du bas et je renvoie la sortie dans l'entrée du haut. Pour que ça marche il faut également donner une valeur initiale à l'entrée du haut, pour le premier tour, avant qu'elle ne soit alimentée par la sortie. Coder ce composant est on ne peut plus simple :
Le composant est constitué d'un opérateur à deux entrées (le paramètre f, qui était l'addition dans mon schéma), d'un accumulateur acc qui jouera le rôle de l'entrée avec rétroaction et d'une liste l à envoyer sur la deuxième entrée.
Comment fonctionne-t-il ? C'est simple : il décompose la liste par un pattern matching. Soit la liste est vide est, dans ce cas, on renvoie ce qui a été accumulé sur la premier entrée. Soit elle ne l'est pas et, dans ce cas, elle est constitué d'une tête (son premier élément) et d'une queue (le reste de la liste). À ce moment, on donne les deux entrées acc et hd à la fonction dans notre boite (le terme f hd acc) puis on renvoie la sortie dans la première entrée et le reste de la liste tl dans la seconde entrée.
Exemple d'usage : calculer la somme d'une liste d'entiers. Pour cela, il faut mettre l'addition comme fonction dans la boîte et partir de 0, ce qui donne :
letsommeliste=reduce(+)0listesomme[1;2;3](* qui retournera 6 *)
Ce qui demande du temps, et de l'expérience, c'est de comprendre toute la puissance du reduce et à quel point on peut faire de choses avec un tel composant, pourtant en apparence si simple.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Oui tu as bien compris le code, mais en OCaml c'est du même ordre si on vire toutes mes explications autour :
letajouterxy=x+yajouter12
la définition de ajouter c'est juste du sucre syntaxique pour cette définition :
letajouter=funxy->x+y(*qui est elle même du sucre syntaxique pour celle-ci *)letajouter=funx->funy->x+y
mais cela je peux aussi l'écrire en python :
ajouter=lambdax,y:x+y
D'ailleurs j'ai toujours trouver l'utilisation du mot clef lambda quelque peu pédant, un peu comme Haskell qui utilise \ parce que ça ressemble à la graphie du lambda dans l'alphabet grec.
Ce que je voulais signaler c'est que la programmation fonctionnelle n'a rien d'étrange ou de si compliquée : c'est fondamentalement ce que nous faisons quand on fait des phrases à trous.
Il y a deux modèles de calcul qui servent de base aux langages de programmations. Celui de Turing avec sa machine et ses instructions pour en modifier l'état qui sert de base aux paradigme impératif. Celui de son directeur de thèse, Alonzo Church, à savoir le lambda-calcul avec son système d'expression et de réécriture d'expression qui sert de base au paradigme fonctionnel.
Ce que je disais c'est que les phrases à trous sont du lambda-calcul, tout comme le calcul tel qu'on l'enseigne dans les cours de mathématiques élémentaires.
Pour bien illustrer cet aspect où tout est expression en programmation fonctionnel (c'est vrai de OCaml mais aussi de lisp, ses dériviées, Haskell, elixir…), prenons cet exemple :
letpairex=xmod2=0
cette fonction teste la parité de son paramètre, elle compare à 0 le reste de la division euclidienne par 2 (x mod 2).
Avec la fonction ajouter de ci-dessus, on peut l'appeler ainsi :
ajouter(ifpaire2then1else2)2
ce qui vaut toujours 3. Ici if paire 2 then 1 else 2 est une expression comme un autre, que je peux donner en argument à ajouter car elle à le bon type. De la même façon que l'expression composée le chat de ma tante peut être passer à la phrase à trous "je regarde qqchose" car elle a la bonne catégorie grammaticale.
motifs / abstrais / beta reduction => les termes ne sont pas assez "basique" pour certains et trop théoriques.
Mon discours n'était pas destiné à des débutants, je m'interrogeais seulement sur ce qu'il y a de si compliqué à comprendre dans le paradigme fonctionnel alors que l'on fait la même chose à l'entrée du collège en cours de français (j'ai eu l'idée en faisant faire ses devoirs à une de mes nièces qui était en 6ème).
Ce qui est vrai avec OCaml, ce n'est pas que la langage soit compliqué, mais qu'une grande partie de ses utilisateurs (le milieu académique faisant de la recherche en langage de programmation) fait des choses compliquées avec : pour leur santé mentale, il n'essaieraient même pas de les faire en Python ou Go. ;-)
Si tu veux voir un programme simple, mais un peu plus évolué que les one liner de ces commentaires, tu peux regarder le journal sur taptempo en OCaml.
Pour finir, il y a un point qui m'a échappé dans ta réponse :
(* j'abstrais le patrons suivants *) => ici tu en as perdu encore … (et dans certaines entreprises les délégués syndicaux te regardent d'un sale oeil … )
Ici tu considères que j'ai en face de moi un public de développeurs ? Ils ont eu peur de quoi ? du mot abstraction ? Si c'est le cas, effectivement les délégués syndicaux ne vont pas m'aimer : là c'est direction les ressources humaines, tu prends tes affaires et pas la peine de revenir demain. :-D
C'est le métier du programmeur que de créer des abstractions. S'ils ont peur d'un mot qui constitue le cœur de leur activité, que font-ils ici ?
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
C'est l'anti-thèse de Ocaml, très beau sur le plan théorique mais en pratique, c'est trop compliqué pour le développeur lambda, pas assez performant, trop peu utilisé etc.
Il n'a jamais fait de phrase à trou dans son enfance en cours de français le développeur lambda ? Parce qu'écrire un programme en OCaml c'est le même principe. Elle est où la difficulté ?
Exemples:
Je mange du pain
Je bois de l'eau
de ces deux phrases, on peut abstraire un motif sous la forme :
Je verbe de qqchose
ce motif, on peut l'appliquer soit au couple manger - pain, soit au couple boire - eau, afin d'obtenir les phrases précédentes.
Après, certes, on peut commencer à construire des phrases plus complexes, en partant par exemple de ce patron :
je regarde qqchose
ce qui peut donner :
je regarde la télé
je regarde le chien du voisin
Dans le deuxième exemple, le complément d'objet est lui-même un composé dont on peut abstraire un motif. Disons, celui-ci :
l'animal de qqu'un
qui peut être instancier sous la forme :
le chien du voisin
le chat de ma tante
le poisson rouge de Nicolas
et ainsi de suite. Ensuite, en composant les patrons de phrases, on construit des phrases de plus en plus complexes (ce qui ne veut pas dire compliquée ;-).
Tu t'y prends autrement pour exprimer ta pensée de manière écrite? Le développeur lamabda, aussi, a une autre façon de procéder? Si c'est le cas, j'aimerais bien savoir laquelle.
En programmation fonctionnelle, abstraire un mot pour former un patron c'est ce qui se nomme la lambda abstraction (le mot clef fun de OCaml), puis appliquer un patron à des cas particuliers cela s'appelle l'application de fonction, et la phase qui remplace le paramètre formelle par le paramètre effectif se nomme la beta reduction (c'est-à-dire l'exécution du code).
(* j'ai les motifs suivants *)1+22+3(* j'abstrais le patrons suivants *)funxy->x+y(* je l'utilise pour retrouver les exemples du dessus *)(funxy->x+y)12(* et là le runtime fait des substitutions successives ou beta reduction *)(* on commence par susbstituer le premier paramètre x *)(funy->1+y)2(*on fait pareil avec y *)1+2(* on calcule *)3
Après le langage va rajouter des catégories grammaticales ou types pour bien vérifier que l'on utilise les patrons avec des mots ou construction conforme au règle de la grammaire, comme tout langage à typage statique. Autrement dit, dans le patron "je verbe de qqchose", il n'est pas correcte de vouloir utiliser le mot "bouchon" à la place du verbe car il n'appartient pas à la bonne catégorie grammaticale.
Qui y a-t-il de compliquer à comprendre dans tout cela ?
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
La pour le coup, le calcul me semble pas si simple. Ce cargo a un énorme avantage: il ne consomme (quasiment) pas de carburant.
Sur ce point, je dirais quand même que l'expérience reste en faveur du pétrole. Le turbovoile d'Anton Flettner fut construit au début des années 1920 et, de fait, cette technologie n'a pas du tout prise. Je crains qu'il ne faille pas compter sur cet apparent avantage économique pour espérer la voir fleurir.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Il serait pas awesome ce produit ? Genre revolutionary ?
Je crois qu'il y a méprise : ce n'est pas revolutionary mais It’s gonna be legen… wait for it… dary ! Legendary! L'awesomeness c'est totalement Stinson-proof. Par contre cela risque de ne pas être très inclusive, ce qui est à la mode en ce moment. :-)
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Après, je ne comprends pas ce mépris sous-jacent pour les techniciens.
Je ne crois pas qu'il y ait un quelconque mépris envers les techniciens. Il me semble que l'idée générale serait plutôt que la difficulté technique d'exécution d'une œuvre ne détermine nullement sa qualité musicale. Cela se rapproche de ce qu'exprimer Duke Ellington avec son It don't mean a thing (if it ain't got that swing).
Pour rester dans l'instrument de Van Halen, Bibi (aka Biréli Lagrène), un des virtuoses de la guitare, aurait pu mal tourner à la fin des années 80, début 90, s'il avait suivit ce courant. Heureusement, par la suite, il est revenu à la raison (petit clin d'œil à Beethoven sur la fin ;-). Ceci étant il lui arrive encore de composer des pièces qui me laisse de marbre, comme son mouvements : la partie du sax soprano se prête très bien à la tessiture et au timbre de cet instrument, en revanche je trouve qu'il gâche la pièce avec sa guitare.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Le type 'null' est toujours là. Les types de base ne sont pas des objets.
Tout ça, OK (et à mon sens le null est peut-être le plus gros problème de Java). Il y avait un projet pour se débarasser des types de base, mais ça fait longtemps que je n’en ai plus entendu parler.
C'est étrange comme projet, j'aurais plutôt eu l'idée contraire : virer les objets et n'avoir que des types comme ceux de base. :-)
Les définitions mathématiques ne peuvent jamais être fausses. En effet, comme le concept est d’abord donné par la définition, il ne contient exactement que ce que la définition veut que l’on pense par ce concept. Mais, s’il ne peut rien s’y trouver de faux quant au contenu, il peut y avoir parfois, mais rarement, quelque défaut dans la forme (dans l’expression), je veux dire du côté de la précision. Ainsi cette définition ordinaire de la ligne circulaire, qu’elle est une ligne courbe dont tous les points sont également éloignés d’un point unique (du centre), a le défaut d’introduire sans nécessité la détermination courbe. En effet il doit y avoir un théorème particulier qui est dérivé de la définition, et qui peut être aisément démontré, à savoir que toute ligne dont tous les points sont également éloignés d’un point unique est courbe (qu’aucune partie n’en est droite).
Kant, Critique de la raison pure.
Un type c'est un concept et l'interface d'une classe la définition d'un concept (celui mal fait d'algèbre sur un concept). Celui qui définit ses concepts comme des classes s'y prend aussi mal que ce qui est reproché dans le texte ci-dessus. Si au lieu de ce théorème, on prend le fait qu'un cercle a une circonférence dont la valeur est le diamètre multiplié par une constante (Pi) et que l'on met cela dans une méthode d'une classe, alors on définit le cercle comme une quiche sur patte. ;-)
Le problème étant que, si l'on a que des classes, ce défaut qui touche à la forme des définitions, au lieu d'être rare, se retrouve être permanent.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
La commutativité et l'associativité sont des points super importants en calcul sur des gros volumes. Que ce soit en C++ ou en n'importe quoi.
Associativité: tu peux séparer en morceaux indépendants
Commutativité: l'ordre n'est pas important, ainsi tu peux traiter les morceaux quand ils arrivent et ne pas dépendre de la latence réseau par exemple.
Ce n'est pas moi qui dirait le contraire : ces propriétés permettent d'effectuer les calculs dans n'importe quel ordre, i.e. quelque soit la permutation effectuée sur la séquence d'opérations on aboutit toujours au même résultat. Ainsi, on est moins tributaire du temps d'accès aux données (peu importe l'ordre dans lesquelles elles arrivent) que ce soit du à de la latence réseau, ou à des échanges répétés entre le cache et la ram. ;-)
;)
Il y a une série d'articles de blogs (compilé en un livre) sur le thème théorie des catégories pour les programmeurs. L'auteur y donne des illustrations de code en Haskell et C++ : c'est dingue à quel point la syntaxe du C++ est tordue et peu « parlante ». Si l'on prend la notion élémentaire de monoïde (cf chapitre 3), en Haskell cela se définit ainsi :
classMonoidmwheremempty::mmappend::m->m->m
autrement dit un monoïde sur un type m est la donnée d'un élément neutre et d'une opération associative (important l'associativité, même si le contrat est implicite entre les programmeurs ;-). Là où en C++, avec les concepts, on se retrouve à le définir ainsi :
c'est-à-dire la donnée conjointe d'un type (ou concept, ou ensemble…), d'une loi de composition interne associative et d'un élément neutre pour cette loi. La différence avec Haskell étant que le type fait partie du dictionnaire, là où en Haskell le type support du monoïde paramétrise le dictionnaire qui n'a que deux éléménts. C'est similaire à ce type produit :
type'mmonoid={mempty:'m;mappend:'m->'m->'m}
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Oui c'est cela, mais plus proche des packages génériques : c'est tout une section de code qui peut être paramétrée, pas seulement une fonction. Mais les concepts n'ajoutent rien de nouveau là-dessus, c'est ce qu'on toujours fait les template. Les concepts c'est juste un système de types pour les template. Le langage des template est interprété par le compilateur et, avant, lorsqu'il y avait une problème lors de l'instantiation d'une template, il y a avait l'équivalent d'une stack trace en guise de message d'erreur, qui pouvait faire 3km de long si le code utilisé était très générique. Là, ils auront des messages plus clairs du au système de type.
Peut-on faire des trucs encore plus forts avec les concepts que juste spécifier les opérations nécessaires ?
Je ne crois pas, mais on peut les utiliser en dehors des templates. Par exemple, pour contraindre le mécanisme d'inférence de type :
Sortableautox2=f(y);
ne compilera que si la fonction f retourne une valeur que l'on peut trier.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Mais je pense amicalement que tu me provoques un peu sachant que tu sais cela très bien.
Ce n'était pas de la provocation, j'étais bien sérieux avec ma question. Je me doutais bien que cela devait être affirmé sans preuve, mais j'ai préféré avoir confirmation. Là où j'étais un peu railleur, c'est en te demandant la syntaxe choisie pour exprimer cela (de manière générale je me demandes quels genres de psychotropes prennent les responsables du standard C++, mais ils devraient réduire la dose, j'ai rarement vu une syntaxe aussi laide).
C'est juste un problème d'interface ou de contrat que tu passes avec ton développeur et la libraire. Si on reprend l'exemple de la commutativité, si en tant que développeur je sais que le type générique (e.g. template) que j'utilise est commutatif pour mon opération, alors je peux me permettre certaines choses. On peut me mentir, mais au moins j'aurais prévenu. Inversement, en demandant que le type en entrée soit commutatif, je préviens l'utilisateur et il ne peut pas rater la contrainte que je lui demande.
Ça se tient. C++ étant orienté performance, avoir la commutativité permet de choisir un parcours de données plus efficace pour optimiser l'usage du cache du CPU, par exemple.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Avec un concept tu pourra aussi demander à ce que + soit commutatif.
C'est fascinant ça. Mais comment on prouve au type checker que l'opérateur + que l'on définit est bien commutatif ? On se contente de l'affirmer sans preuve ? Dans les faits, elle ressemble à quoi la syntaxe pour ce genre de propriété ?
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
T'as le message d'erreur d'un type checker au lieu d'avoir une stack trace à la python. En gros, le système de template c'était du lamba-calcul non typé, ils y ont rajouté un système de types. Le langage est interprété par le compilateur : en l'absence de typage statique, tu as une stack trace lors de l'appel, avec un système de types tu as tous les avantages du typage statique.
Par contre le choix du nom pour la fonctionnalité n'est pas très judicieux : un concept c'est un type (les deux mots sont synonymes), donc des concepts il y en avait déjà depuis le début en C++. Là, cela revient à paramétrer du code par un type muni de certaines opérations : ça s'appelle une algèbre. ;-)
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Personnellement, l'apprentissage de la théorie de la relativité m'a surtout fait comprendre a quel point j'étais ignorant, et je ne suis d'ailleurs toujours pas très sur de savoir ce que ce que c'est que la gravité.
Il me semble que tu es un ancien marin, alors pour approfondir cette question, sans avoir besoin d'un lourd formalisme géométrique, je peux te proposer de retourner à la source et de lire le Dialogue sur les deux grands systèmes du monde de Galilée. Si tu as lu le livre d'Einstein sur la rélativité, son personnage enfermé dans sa cabine est un lointain cousin d'un personnage de Galilée : un marin enfermé dans sa cabine et qui se demande si avec les expériences qu'il y fait, il peut déterminer si le bateau est en mouvement ou non. ;-)
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Pour ma part, j'utilise un masque maison en coton à chaque sortie depuis le début du confinement.
Au niveau de l'entretien, je ne le passe pas à la machine, mais je le fait tremper dans l'eau chauffée à la bouilloire électrique (entre 80°C et 90°C) avec de la javel [1] pendant 30 minutes, puis je finis par un coup de fer à repasser. Avec une telle température de lavage, il finit par rétrécir et j'en refais un autre : c'est assez rapide à faire si on a du stock de tissu ou de vieux draps, et pour le patron je suis maintenant celui fournit par l'AFNOR.
[1]: je mets aussi de la lessive, puis un rinçage rapide avec un adoucissant pour que le tissu ne deviennent pas trop rêche.
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 kantien . En réponse au journal Retour d'expérience sur les langages de programmation. Évalué à 1. Dernière modification le 17 novembre 2020 à 01:42.
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 type1 + A
pour tout typeA
. 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 casA + 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 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 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 ?
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.
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 kantien . En réponse au journal Retour d'expérience sur les langages de programmation. Évalué à 1. Dernière modification le 16 novembre 2020 à 18:26.
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.
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
:Voilà, ma valeur
couple
est une paire constituée d'une représentation au runtime du typeint
ainsi que d'un dictionnaire implémentant l'interfaceshowable
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 kantien . En réponse au journal Retour d'expérience sur les langages de programmation. Évalué à 2.
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 :
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'écrireprint 1
au lieu deprint {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.
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 lado
notation de Haskell. Le>>=
c'est juste une généralisation du pipe du shell qui est lebind
de la monade identité.Le code avec les exceptions j'aurais pu l'écrire ainsi si j'avais utilisé le pipe :
ou avec la type classe de la monade identité :
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.
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 kantien . En réponse au journal Retour d'expérience sur les langages de programmation. Évalué à 2.
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 leand
? 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.
Si on prend une fonction de conversion qui renvoie une option au lieu d'une exception cela devient beaucoup plus lourd :
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).
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.
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: Pour alimenter la discussion ...
Posté par kantien . En réponse à la dépêche Python dépasse Java en popularité selon l’indice TIOBE de novembre. Évalué à 2.
C'est le bon algo, avec néanmoins une comparaison inutile entre les deux nombres. Le principe est celui-ci :
et c'est ce que fait ma boucle en un one liner. Il n'est pas nécessaire de chercher à savoir lequel des deux nombres est le plus grand. Si b est plus grand que a, le reste est a, et le point 4 remet tout dans le bon ordre au premier tour. Ensuite, le reste, qui servira de diviseur, sera toujours plus petit que le dividende.
J'ai eu beau cherché dans ce document, je ne vois pas d'où tu tires ce nombre. De mémoire, on était bien moins de 60% à y arriver. J'ai raconté cette anecdote pour montrer que le changement de base était accessible à certain élèves de primaires (les plus doués en maths d'entre eux) et qu'il devait donc être une formalité pour des étudiants dans le supérieur (ceux qui font des études d'info). Ceci étant, je me demande la part d'enfants qui appliquent sans comprendre et ceux qui comprennent réellement : la raison leur est enseignée (voir par exemple la chaîne lumni lancée pendant le confinement) mais combien la retienne et la comprenne réellement, je ne le sais.
Ceci étant, je maintiens que, comme pour le pgcg, l'alogrithme est hautement récursif et fonctionnel :
Ici les nombres sont représentés par une liste de chifres en mode "petit boutiste". Le code lui même reproduit parfaitement les différentes étapes d'apprentissage d'un enfant, et les sources d'erreurs potentielles où il faut prendre garde.
L'on apprend d'abord à ajouter des chiffres de petites tailles. Ma nièce en CP est à ce stade là, elle n'a pas encore à gérer le cas de la retenue dans la fonction
ajouter_chiffre
. Ensuite, lorsque la somme dépasse la dizaine, on leur enseigne que l'on crée un paquet de dix (ou dizaine) et que l'on garde le reste : deuxième branche duif then else
de la fonctionajouter_chiffre
. C'est là que se situe l'origine et la raison d'être de la retenue.Ensuite, lorsque l'on pose l'addition, on a deux listes de chiffres puis l'on a va en construire une autre (sans modifier les deux en entrées) en appliquant itérativement (c'est-à-dire récursivement) l'addition de chiffre sur nos deux listes jusqu'à épuisement complet.
De plus, les différentes branches montre bien deux sources d'erreurs ou de confusion qui arrive au cours de l'apprentissage :
if then else
de la dernière branche).Enfin, la méthode est facilement généralisable pour un programmeur (ou un enfant dont le niveau d'abstraction est suffisamment développé). Ici la base est codée en dur dans l'addition de chiffres : il suffit d'en faire un paramètre. ;-)
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Pour alimenter la discussion ...
Posté par kantien . En réponse à la dépêche Python dépasse Java en popularité selon l’indice TIOBE de novembre. Évalué à 3. Dernière modification le 12 novembre 2020 à 17:49.
Il y a bien là un point de désaccord, tu sembles, à mes yeux, sous estimer grandement les capacités intellectuelles et de compréhension d'un enfant.
Je vais d'abord parler d'un personne que je connais bien : moi-même. J'ai effectué mon école primaire dans les années 80 au sein d'une école privée. Pour l'accès au collège, il y avait un examen d'entrée en 6ème. Parmi les exercices de calculs, il y en avait un qui consistait à effectuer une addition et une multiplication en base 10… puis en base 5. Il fallait ensuite convertir les résultats d'une base à l'autre pour bien vérifier que le résultat était indépendant de la base choisie.
Oui : on demandait cela à des élèves en fin de CM2. Je t'accorde que tous n'y arrivaient pas, mais j'étais loin d'être le seul à y arriver, et nous comprenions ce que nous faisions. On ne se contentait pas d'appliquer bêtement une recette sans savoir pourquoi elle fonctionne. Je suppose que le choix des bases était fait à partir du nombre de doigts que l'on possède sur nos mains. Mais dans le fond, pas de grande différence, sur le plan algorithmique, avec une base 2, 8 ou 16.
Ensuite, pour les enfants d'aujourd'hui, je n'ai comme exemple proche que mes nièces, n'ayant pas d'enfants. Elles sont en 5ème, CM1 et CP. Les deux grandes comprennent leurs algorithmes, à commencer par la raison d'être de la retenue, quand à la dernière je lui ai déjà appris les petites additions à deux chiffres sans retenue. Et non, elles n'appliquent pas bêtement une recette sans la comprendre.
Euh, une récursion fonctionnelle c'est itératif. Elle est où ta distinction ? Un procédé récursif ou itératif, c'est bonnet blanc et blanc bonnet. Quand une personne se dit que pour une poule, il faut un œuf, mais que pour un œuf, il faut aussi une poule, puis qu'elle réitère (note bien le choix du verbe), elle fait de la récursion. Le problème ici étant : quelle est la condition d'arrêt ?
Mais rassure moi, pour l'algorithme d'Euclide, on ne vous le présentait pas autrement qu'avec ce one liner ?
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Pour alimenter la discussion ...
Posté par kantien . En réponse à la dépêche Python dépasse Java en popularité selon l’indice TIOBE de novembre. Évalué à 4.
Un peu comme les perles du bac, ça aurait pu être marrant. :-)
J'ai jeté un œil du côté de elixir (mentionné dans un autre commentaire par un adepte de python), et ça peut être intéressant à regarder pour appréhender l'approche fonctionnelle de la programmation. Venant de python ce sera peut être moins perturbant : pas de typage statique et une syntaxe plus proche.
Néanmoins, sur leur syntaxe, ils m'ont bien fait rire sur un point (cf. chapitre sur les modules et fonctions) :
Utiliser la notation de De Bruijn dans un langage généraliste, je dois dire que je n'avais jamais vu cela. :-D
Leur présentation de la notion de clôture (closure) me semble un peu étrange par rapport à ce qu'elle est réellement, mais à part cela ça peut être amusant de regarder de ce côté.
Sinon, pour ce qui est de python vs OCaml, il y a le développeur d'un gestionnaire de paquet multi-platforme (Linux, MacOS et Windows) zero install qui a fait la migration du code de python vers OCaml en 2013. Il a détaillé son expériences sur son blog dans une longue série d'articles. Au bilan on se retrouve 6 ans plus tard avec une personne qui pense que le projet est mort parce qu'il y a peu voire pas d'activité sur le dépôt de code, et l'auteur de lui répondre :
:-)
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Pour alimenter la discussion ...
Posté par kantien . En réponse à la dépêche Python dépasse Java en popularité selon l’indice TIOBE de novembre. Évalué à 3. Dernière modification le 11 novembre 2020 à 16:10.
Sans doute pas dans le même, effectivement. ;-)
Il ne faut pas s'arrêter au vocabulaire que j'ai utilisé : ici je m'adresse au lectorat de linuxfr, où je partais d'une réponse à quelqu'un qui mentionnait des « ingénieurs au sens large » et des « programmeurs lambda ». Je m'attends à ce que cette population ne soit pas choquée et comprenne la notion de boucle de rétroaction.
Ceci étant mon monde n'est pas celui de l'informatique mais de la musique, qui est loin d'être composé de techniciens et d'ingénieurs, et je te garantie qu'ils comprennent ce qu'est une boucle de rétroaction : quand il y a un larsen, ils savent d'où cela vient et comment régler le problème. ;-)
Je ne sais pas la difficulté que rencontre les profs de collèges sur ce point mais, pour ma part, les premiers algorithmes que j'ai appris étant enfant sont hautement récursifs et totalement fonctionnels dans leurs principes, et il en est de même de tous les écoliers de France depuis des décennies. Je veux parler des algorithmes d'addition et de multiplication avec retenue, ainsi que celui de la division euclidienne (celui où l'on pose la division). Tu as peut être oublié ce que tu faisais étant enfant à l'école primaire.
Si je veux être pompeux et utiliser un concept sophistiqué, je pourrais employer, par exemple, celui de monade d'état et pourtant c'est au cœur des API REST. Si j'en crois la présentation de wikipédia :
Moi quand je lis ça, je me dis : tiens, ils travaillent dans une monade d'état.
Je voudrais bien te répondre, mais il semble que ton clavier se soit blo
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Pour alimenter la discussion ...
Posté par kantien . En réponse à la dépêche Python dépasse Java en popularité selon l’indice TIOBE de novembre. Évalué à 2.
Parle pour toi, l'impératif rempli d'effets de bords je n'y comprends strictement rien dès que le programme devient trop gros. C'est un paradigme qui m'est totalement incompréhensible.
Qu'est ce qu'il y a de compliqué là-dedans ? Prenons le
reduce
(oufold
) : c'est un peu comme une boucle de rétroaction. J'ai un système qui prend deux entrées et renvoie une sortie, je mets ma liste sur une des entrées qui sera consommée un par un, et je branche la sortie sur l'autre entrée : le système s'arrête quand il n'y plus rien à consommer dans liste.C'est le schéma du dessus le
reduce
: je branche ma liste dans l'entrée du bas et je renvoie la sortie dans l'entrée du haut. Pour que ça marche il faut également donner une valeur initiale à l'entrée du haut, pour le premier tour, avant qu'elle ne soit alimentée par la sortie. Coder ce composant est on ne peut plus simple :Le composant est constitué d'un opérateur à deux entrées (le paramètre
f
, qui était l'addition dans mon schéma), d'un accumulateuracc
qui jouera le rôle de l'entrée avec rétroaction et d'une listel
à envoyer sur la deuxième entrée.Comment fonctionne-t-il ? C'est simple : il décompose la liste par un pattern matching. Soit la liste est vide est, dans ce cas, on renvoie ce qui a été accumulé sur la premier entrée. Soit elle ne l'est pas et, dans ce cas, elle est constitué d'une tête (son premier élément) et d'une queue (le reste de la liste). À ce moment, on donne les deux entrées
acc
ethd
à la fonction dans notre boite (le termef hd acc
) puis on renvoie la sortie dans la première entrée et le reste de la listetl
dans la seconde entrée.Exemple d'usage : calculer la somme d'une liste d'entiers. Pour cela, il faut mettre l'addition comme fonction dans la boîte et partir de
0
, ce qui donne :Ce qui demande du temps, et de l'expérience, c'est de comprendre toute la puissance du reduce et à quel point on peut faire de choses avec un tel composant, pourtant en apparence si simple.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Pour alimenter la discussion ...
Posté par kantien . En réponse à la dépêche Python dépasse Java en popularité selon l’indice TIOBE de novembre. Évalué à 3.
Oui tu as bien compris le code, mais en OCaml c'est du même ordre si on vire toutes mes explications autour :
la définition de
ajouter
c'est juste du sucre syntaxique pour cette définition :mais cela je peux aussi l'écrire en python :
D'ailleurs j'ai toujours trouver l'utilisation du mot clef
lambda
quelque peu pédant, un peu comme Haskell qui utilise\
parce que ça ressemble à la graphie du lambda dans l'alphabet grec.Ce que je voulais signaler c'est que la programmation fonctionnelle n'a rien d'étrange ou de si compliquée : c'est fondamentalement ce que nous faisons quand on fait des phrases à trous.
Il y a deux modèles de calcul qui servent de base aux langages de programmations. Celui de Turing avec sa machine et ses instructions pour en modifier l'état qui sert de base aux paradigme impératif. Celui de son directeur de thèse, Alonzo Church, à savoir le lambda-calcul avec son système d'expression et de réécriture d'expression qui sert de base au paradigme fonctionnel.
Ce que je disais c'est que les phrases à trous sont du lambda-calcul, tout comme le calcul tel qu'on l'enseigne dans les cours de mathématiques élémentaires.
Pour bien illustrer cet aspect où tout est expression en programmation fonctionnel (c'est vrai de OCaml mais aussi de lisp, ses dériviées, Haskell, elixir…), prenons cet exemple :
cette fonction teste la parité de son paramètre, elle compare à 0 le reste de la division euclidienne par 2 (
x mod 2
).Avec la fonction
ajouter
de ci-dessus, on peut l'appeler ainsi :ce qui vaut toujours
3
. Iciif paire 2 then 1 else 2
est une expression comme un autre, que je peux donner en argument àajouter
car elle à le bon type. De la même façon que l'expression composéele chat de ma tante
peut être passer à la phrase à trous "je regardeqqchose
" car elle a la bonne catégorie grammaticale.Mon discours n'était pas destiné à des débutants, je m'interrogeais seulement sur ce qu'il y a de si compliqué à comprendre dans le paradigme fonctionnel alors que l'on fait la même chose à l'entrée du collège en cours de français (j'ai eu l'idée en faisant faire ses devoirs à une de mes nièces qui était en 6ème).
Ce qui est vrai avec OCaml, ce n'est pas que la langage soit compliqué, mais qu'une grande partie de ses utilisateurs (le milieu académique faisant de la recherche en langage de programmation) fait des choses compliquées avec : pour leur santé mentale, il n'essaieraient même pas de les faire en Python ou Go. ;-)
Si tu veux voir un programme simple, mais un peu plus évolué que les one liner de ces commentaires, tu peux regarder le journal sur taptempo en OCaml.
Pour finir, il y a un point qui m'a échappé dans ta réponse :
Ici tu considères que j'ai en face de moi un public de développeurs ? Ils ont eu peur de quoi ? du mot abstraction ? Si c'est le cas, effectivement les délégués syndicaux ne vont pas m'aimer : là c'est direction les ressources humaines, tu prends tes affaires et pas la peine de revenir demain. :-D
C'est le métier du programmeur que de créer des abstractions. S'ils ont peur d'un mot qui constitue le cœur de leur activité, que font-ils ici ?
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Pour alimenter la discussion ...
Posté par kantien . En réponse à la dépêche Python dépasse Java en popularité selon l’indice TIOBE de novembre. Évalué à 3.
Il n'a jamais fait de phrase à trou dans son enfance en cours de français le développeur lambda ? Parce qu'écrire un programme en OCaml c'est le même principe. Elle est où la difficulté ?
Exemples:
de ces deux phrases, on peut abstraire un motif sous la forme :
verbe
deqqchose
ce motif, on peut l'appliquer soit au couple manger - pain, soit au couple boire - eau, afin d'obtenir les phrases précédentes.
Après, certes, on peut commencer à construire des phrases plus complexes, en partant par exemple de ce patron :
qqchose
ce qui peut donner :
Dans le deuxième exemple, le complément d'objet est lui-même un composé dont on peut abstraire un motif. Disons, celui-ci :
animal
deqqu'un
qui peut être instancier sous la forme :
et ainsi de suite. Ensuite, en composant les patrons de phrases, on construit des phrases de plus en plus complexes (ce qui ne veut pas dire compliquée ;-).
Tu t'y prends autrement pour exprimer ta pensée de manière écrite? Le développeur lamabda, aussi, a une autre façon de procéder? Si c'est le cas, j'aimerais bien savoir laquelle.
En programmation fonctionnelle, abstraire un mot pour former un patron c'est ce qui se nomme la lambda abstraction (le mot clef
fun
de OCaml), puis appliquer un patron à des cas particuliers cela s'appelle l'application de fonction, et la phase qui remplace le paramètre formelle par le paramètre effectif se nomme la beta reduction (c'est-à-dire l'exécution du code).Après le langage va rajouter des catégories grammaticales ou types pour bien vérifier que l'on utilise les patrons avec des mots ou construction conforme au règle de la grammaire, comme tout langage à typage statique. Autrement dit, dans le patron "je
verbe
deqqchose
", il n'est pas correcte de vouloir utiliser le mot "bouchon" à la place du verbe car il n'appartient pas à la bonne catégorie grammaticale.Qui y a-t-il de compliquer à comprendre dans tout cela ?
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Attention aux tartes à la crème technologique
Posté par kantien . En réponse au lien Transports - Oceanbird, cargo transatlantique 100% éolien. Évalué à 4.
Sur ce point, je dirais quand même que l'expérience reste en faveur du pétrole. Le turbovoile d'Anton Flettner fut construit au début des années 1920 et, de fait, cette technologie n'a pas du tout prise. Je crains qu'il ne faille pas compter sur cet apparent avantage économique pour espérer la voir fleurir.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: CMS headless et baseless ?
Posté par kantien . En réponse au journal Docker vs Podman sur fedora 32 et headless CMS. Évalué à 1.
Je crois qu'il y a méprise : ce n'est pas revolutionary mais It’s gonna be legen… wait for it… dary ! Legendary! L'awesomeness c'est totalement Stinson-proof. Par contre cela risque de ne pas être très inclusive, ce qui est à la mode en ce moment. :-)
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Ses solos ont mal vieillis (ou j'ai mal vielli)
Posté par kantien . En réponse au journal [Bonsonnisation] La Cathedral est morte ! Vive le bazar !. Évalué à 4. Dernière modification le 08 octobre 2020 à 22:21.
Je ne crois pas qu'il y ait un quelconque mépris envers les techniciens. Il me semble que l'idée générale serait plutôt que la difficulté technique d'exécution d'une œuvre ne détermine nullement sa qualité musicale. Cela se rapproche de ce qu'exprimer Duke Ellington avec son It don't mean a thing (if it ain't got that swing).
Pour rester dans l'instrument de Van Halen, Bibi (aka Biréli Lagrène), un des virtuoses de la guitare, aurait pu mal tourner à la fin des années 80, début 90, s'il avait suivit ce courant. Heureusement, par la suite, il est revenu à la raison (petit clin d'œil à Beethoven sur la fin ;-). Ceci étant il lui arrive encore de composer des pièces qui me laisse de marbre, comme son mouvements : la partie du sax soprano se prête très bien à la tessiture et au timbre de cet instrument, en revanche je trouve qu'il gâche la pièce avec sa guitare.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: C'est pas pour casser l'ambiance
Posté par kantien . En réponse à la dépêche Java 15 est sorti. Évalué à 1.
C'est étrange comme projet, j'aurais plutôt eu l'idée contraire : virer les objets et n'avoir que des types comme ceux de base. :-)
Un type c'est un concept et l'interface d'une classe la définition d'un concept (celui mal fait d'algèbre sur un concept). Celui qui définit ses concepts comme des classes s'y prend aussi mal que ce qui est reproché dans le texte ci-dessus. Si au lieu de ce théorème, on prend le fait qu'un cercle a une circonférence dont la valeur est le diamètre multiplié par une constante (Pi) et que l'on met cela dans une méthode d'une classe, alors on définit le cercle comme une quiche sur patte. ;-)
Le problème étant que, si l'on a que des classes, ce défaut qui touche à la forme des définitions, au lieu d'être rare, se retrouve être permanent.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Typage structurel
Posté par kantien . En réponse au journal C++ Hell/Heaven et les concepts. Évalué à 3.
Ce n'est pas moi qui dirait le contraire : ces propriétés permettent d'effectuer les calculs dans n'importe quel ordre, i.e. quelque soit la permutation effectuée sur la séquence d'opérations on aboutit toujours au même résultat. Ainsi, on est moins tributaire du temps d'accès aux données (peu importe l'ordre dans lesquelles elles arrivent) que ce soit du à de la latence réseau, ou à des échanges répétés entre le cache et la ram. ;-)
Il y a une série d'articles de blogs (compilé en un livre) sur le thème théorie des catégories pour les programmeurs. L'auteur y donne des illustrations de code en Haskell et C++ : c'est dingue à quel point la syntaxe du C++ est tordue et peu « parlante ». Si l'on prend la notion élémentaire de monoïde (cf chapitre 3), en Haskell cela se définit ainsi :
autrement dit un monoïde sur un type
m
est la donnée d'un élément neutre et d'une opération associative (important l'associativité, même si le contrat est implicite entre les programmeurs ;-). Là où en C++, avec les concepts, on se retrouve à le définir ainsi :Non, mais what the fuck !? :-o
Par comparaison, en ML, c'est proche du Haskell mais plus conforme à la définition formelle des mathématiciens :
c'est-à-dire la donnée conjointe d'un type (ou concept, ou ensemble…), d'une loi de composition interne associative et d'un élément neutre pour cette loi. La différence avec Haskell étant que le type fait partie du dictionnaire, là où en Haskell le type support du monoïde paramétrise le dictionnaire qui n'a que deux éléménts. C'est similaire à ce type produit :
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Typage structurel
Posté par kantien . En réponse au journal C++ Hell/Heaven et les concepts. Évalué à 3.
Oui c'est cela, mais plus proche des packages génériques : c'est tout une section de code qui peut être paramétrée, pas seulement une fonction. Mais les concepts n'ajoutent rien de nouveau là-dessus, c'est ce qu'on toujours fait les template. Les concepts c'est juste un système de types pour les template. Le langage des template est interprété par le compilateur et, avant, lorsqu'il y avait une problème lors de l'instantiation d'une template, il y a avait l'équivalent d'une stack trace en guise de message d'erreur, qui pouvait faire 3km de long si le code utilisé était très générique. Là, ils auront des messages plus clairs du au système de type.
Je ne crois pas, mais on peut les utiliser en dehors des templates. Par exemple, pour contraindre le mécanisme d'inférence de type :
ne compilera que si la fonction
f
retourne une valeur que l'on peut trier.Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Typage structurel
Posté par kantien . En réponse au journal C++ Hell/Heaven et les concepts. Évalué à 3.
Ce n'était pas de la provocation, j'étais bien sérieux avec ma question. Je me doutais bien que cela devait être affirmé sans preuve, mais j'ai préféré avoir confirmation. Là où j'étais un peu railleur, c'est en te demandant la syntaxe choisie pour exprimer cela (de manière générale je me demandes quels genres de psychotropes prennent les responsables du standard C++, mais ils devraient réduire la dose, j'ai rarement vu une syntaxe aussi laide).
Ça se tient. C++ étant orienté performance, avoir la commutativité permet de choisir un parcours de données plus efficace pour optimiser l'usage du cache du CPU, par exemple.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Typage structurel
Posté par kantien . En réponse au journal C++ Hell/Heaven et les concepts. Évalué à 3.
C'est fascinant ça. Mais comment on prouve au type checker que l'opérateur
+
que l'on définit est bien commutatif ? On se contente de l'affirmer sans preuve ? Dans les faits, elle ressemble à quoi la syntaxe pour ce genre de propriété ?Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Typage structurel
Posté par kantien . En réponse au journal C++ Hell/Heaven et les concepts. Évalué à 5. Dernière modification le 18 septembre 2020 à 01:19.
T'as le message d'erreur d'un type checker au lieu d'avoir une stack trace à la python. En gros, le système de template c'était du lamba-calcul non typé, ils y ont rajouté un système de types. Le langage est interprété par le compilateur : en l'absence de typage statique, tu as une stack trace lors de l'appel, avec un système de types tu as tous les avantages du typage statique.
Par contre le choix du nom pour la fonctionnalité n'est pas très judicieux : un concept c'est un type (les deux mots sont synonymes), donc des concepts il y en avait déjà depuis le début en C++. Là, cela revient à paramétrer du code par un type muni de certaines opérations : ça s'appelle une algèbre. ;-)
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Comment j'ai appris à ne plus m'en faire et à aimer les bombes
Posté par kantien . En réponse au journal Où vivre dans 100 ans ?. Évalué à 6. Dernière modification le 08 septembre 2020 à 23:28.
Demande à eingousef ! :D
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Mauvaise idée…
Posté par kantien . En réponse au journal vers un sciencefr.org ?. Évalué à 4.
Il me semble que tu es un ancien marin, alors pour approfondir cette question, sans avoir besoin d'un lourd formalisme géométrique, je peux te proposer de retourner à la source et de lire le Dialogue sur les deux grands systèmes du monde de Galilée. Si tu as lu le livre d'Einstein sur la rélativité, son personnage enfermé dans sa cabine est un lointain cousin d'un personnage de Galilée : un marin enfermé dans sa cabine et qui se demande si avec les expériences qu'il y fait, il peut déterminer si le bateau est en mouvement ou non. ;-)
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Lavage et entretien
Posté par kantien . En réponse au journal De l'usage et de l'entretien des masques de protection. Évalué à 3.
Au début (avant la norme AFNOR), je suivais ton patron (merci, au passage pour ton journal de l'époque) ou celui du CHU de grenoble. ;-)
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Lavage et entretien
Posté par kantien . En réponse au journal De l'usage et de l'entretien des masques de protection. Évalué à 4.
J'en sais rien, mais pas besoin de le faire si tu ne veux pas. C'est un carré de 20cm sur 20cm que tu dois replier d'une certaine façon : tu as un tuto ici, avec les dimensions en bas de page.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
# Lavage et entretien
Posté par kantien . En réponse au journal De l'usage et de l'entretien des masques de protection. Évalué à 5. Dernière modification le 09 avril 2020 à 17:35.
Pour ma part, j'utilise un masque maison en coton à chaque sortie depuis le début du confinement.
Au niveau de l'entretien, je ne le passe pas à la machine, mais je le fait tremper dans l'eau chauffée à la bouilloire électrique (entre 80°C et 90°C) avec de la javel [1] pendant 30 minutes, puis je finis par un coup de fer à repasser. Avec une telle température de lavage, il finit par rétrécir et j'en refais un autre : c'est assez rapide à faire si on a du stock de tissu ou de vieux draps, et pour le patron je suis maintenant celui fournit par l'AFNOR.
[1]: je mets aussi de la lessive, puis un rinçage rapide avec un adoucissant pour que le tissu ne deviennent pas trop rêche.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.