Journal Rust en version 0.12

Posté par  . Licence CC By‑SA.
Étiquettes :
15
13
oct.
2014

Hop une nouvelle version du compilateur Rust est sortie. Voilà l'anonce.

Perso j'avais regardé la langage y'a presque un an où j'avais commis ca (je me souviens plus du numéro de version du compilo de l'époque). Ça a pas mal changé depuis, je pense que je suis bon pour relire un tuto en entier. Remarque ça tombe bien le guide a été refait entièrement…

Ça va tout déchirer pour la v1 je pense :)

(oui ceci est un journal bookmark)

  • # dépêches en cours de rédaction

    Posté par  (site web personnel) . Évalué à 3.

    Il y a une dépêches en cours de rédaction à ce sujet : https://linuxfr.org/redaction/news/rust-0-12-non-pas-le-jeu-video

  • # simple ?

    Posté par  (site web personnel) . Évalué à -5.

    Pourquoi faire aussi complexe ?
    ```
    fn twice(x: int, f: |int| -> int) -> int {
    f(x) + f(x)
    }

    fn main() {
    let square = |x: int| { x * x };

    twice(5i, square); // evaluates to 50
    

    }
    ```En ocaml, on peut l'écrire :

    let twice x f = (f x) + (f x)

    let _ =
    let square x = x *x in
    twice 5 square

    Pourquoi créer un nouveau langage pour faire aussi verbeux et compliquer qu'avant ?

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

    • [^] # Re: simple ?

      Posté par  . Évalué à 10.

      Pff…

      Faut encore le répéter ?

      Rust n'invente rien. C'est pas son objectif. Il agrège l'état de l'art et cherche à le mettre à disposition facilement.

      Ensuite pour le comparer à Ocaml, je doute que tu fasse une bibliothèque qui s'interface avec du C aussi facilement en Ocaml qu'avec rust (non, on ne compare pas 2 langages sur une exemple de 5 lignes…).

      Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

      • [^] # Re: simple ?

        Posté par  (site web personnel) . Évalué à 0. Dernière modification le 13 octobre 2014 à 16:29.

        Quitte à refaire un langage, pourquoi créer une séparation artificielle des concepts quand il n'y en a pas besoin ? Les closure en ocaml sont détecté quand ils sont à l'intérieur d'une autre fonction, pourquoi faire 2 syntaxes différentes ?

        Ensuite pour le comparer à Ocaml, je doute que tu fasse une bibliothèque qui s'interface avec du C aussi facilement en Ocaml qu'avec rust (non, on ne compare pas 2 langages sur une exemple de 5 lignes…).

        J'ai regarder rapidement, et cela semble être la même chose qu'en ocaml avec une déclaration "externe". La complexité est généralement dans l'accès aux données structurés et dans la gestion de la mémoire, pas vraiment dans les appelles de fonction avec des paramètres scalaires.

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

        • [^] # Re: simple ?

          Posté par  . Évalué à 4. Dernière modification le 13 octobre 2014 à 16:34.

          Tu as un exemple ?

          html5ever, une bibliothèque développée en Rust mais utilisable en C nativement.

          Y’a pas besoin de boilerplate genre caml_startup, CAMLlocal3, …

        • [^] # Re: simple ?

          Posté par  . Évalué à 4.

          L'exemple que tu demandais

          La complexité est généralement dans l'accès aux données structurés et dans la gestion de la mémoire […]

          Ça tombe bien c'est l'un des gros intérêt de rust de gérer la mémoire de manière fine et haut niveau.

          Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

          • [^] # Re: simple ?

            Posté par  (site web personnel) . Évalué à 2.

            Ça tombe bien c'est l'un des gros intérêt de rust de gérer la mémoire de manière fine et haut niveau.

            Je parlais des binding C. j'ai vu le problème en ocaml et en Perl, par exemple.

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

        • [^] # Re: simple ?

          Posté par  . Évalué à 6.

          Il me semble que la séparation n'est pas artificielle justement. En effet, rust force les annotations de type pour les fonctions, alors que le type des clotures peut souvent être inféré, donc la syntaxe est plus légère.

          De plus, en ocaml, la syntaxe de capture implicite est possible car il n'y a qu'une seule sémantique pour les appels de fonction, appel par "valeur" (pour une bonne définition de valeur en ocaml, pointeur ou entier/unit/etc).

          En rust, il y a plusieurs syntaxe de clotures qui permettent de controler la capture. Il est possible de capturer les valeurs par référence ou par valeur.

              let b = 3u;
              let f = ref | a :uint | a + b

          n'est pas équivalent à

              let b = 3u;
              let f = | a : uint | a + b

          qui est encore différent de

             static z : uint = 3;
             fn foo(x : uint) -> uint {
                 x + z
              }

          En effet, dans le second cas, il faut probablement appeler les destructeurs des variables capturés quand la cloture sort du scope alors que dans le premier et dernier cas, il n'y a pas besoin.

          En outre, les clotures ne peuvent pas être récursives, et il est donc parfaitement légitime qu'elles soient anonymes. "fn" est un opérateur de nommage récursif, alors que let ne l'est pas. C'est un peu la disction let/let rec. C'est donc une syntaxe plus proche du

          let id = fun x -> x

          Bon, évidement, tout cela n'empêcherait pas d'avoir une syntaxe plus unifiée, j'imagine, mais je ne pense pas que ça soit un réel problème. J'ai l'impression que les fonctions sont des valeurs statiques (qui ne sont donc pas gérées par le système de lifetime) alors que les clotures en sont.

          • [^] # Re: simple ?

            Posté par  (site web personnel) . Évalué à 6.

            J'imagine bien que la syntaxe permet des subtilités. Mais je croyais que le C++ avait calmé les concepteurs concernant les subtilités des langages. Cela peut devenir une énorme source d'erreur.

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

            • [^] # Re: simple ?

              Posté par  . Évalué à 4.

              Hmm le problème du C++ de ce coté là c'est avant tout le fait que c'est un patchwork d'ajouts au fils des années sans jamais remettre en cause l'existant. Alors forcement il ne peut pas être simple si l'ambition est de laisser les gens choisir entre les possibilités ajoutées au fils du temps.

              Concernant Rust, on peut noter le travail de simplification et de généralisation autour des types de pointeurs. Il y a carrément eu un type de pointeur (celui qui était sensé déclencher un système de ramasse miettes) qui a disparu car ils se sont rendu compte qu'on pouvait réaliser la même chose sans construction syntaxique dédié.

              Après, tu parlais de simplicité du langage dans ton premier post, il faut pas oublier le but premier de Rust est d'obtenir un langage "système". C'est à dire qu'on doit pouvoir contrôler précisément le comportement et les performances des programmes (notamment d'un point de vue gestion mémoire). Étant donné cela le langage sera forcement plus compliqué à utiliser que les langages promouvant l'utilisation d'un ramasse miette. Mais on ne peut pas avoir le beurre et l'argent du beurre. Bien que je trouve qu'ils aient fait un super travail avec le système d'analyse des lifetime des variables (et donc des fragments de mémoires alloués dynamiquement qui peuvent y être associé).

              • [^] # Re: simple ?

                Posté par  (site web personnel) . Évalué à 2.

                J'avoue qu'avoir enfin un remplacement pour le C, est vraiment bienvenue. Rajouter l'équivalent des type somme, les fonctions de 1er ordres sont vraiment bien. Il est aussi nécessaire de mieux gérer la mémoire.

                Mais j'ai été déçu d'apprendre qu'ils ne pourront jamais de faire de transformation automatique appel récursif vers une boucle, ce qui est la base quand on veut avoir une base de code fonctionnel. Si le langage devient aussi complexe et subtil que C++, pourquoi ne pas rester à C++ ?

                Bref, je suis un peu déçu.

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

                • [^] # Re: simple ?

                  Posté par  . Évalué à 1. Dernière modification le 14 octobre 2014 à 10:45.

                  Mais j'ai été déçu d'apprendre qu'ils ne pourront jamais de faire de transformation automatique appel récursif vers une boucle

                  C’est pas faute d’avoir essayé (les dev’ de Rust étaient aussi favorable à cette optimisation), mais il y avait plus d’inconvénients que d’avantages.

                  Many of us have lisp and ML backgrounds and would quite like them. Their absence is heartache and sadness, not arrived-at lightly.
                  […]
                  I'm sorry to be saying all this, and it is with a heavy heart, but we tried and did not find a way to make the tradeoffs associated with them sum up to an argument for inclusion in rust.

                  • [^] # Re: simple ?

                    Posté par  (site web personnel) . Évalué à 2.

                    Il y a quand même des cas ou cela marche, on dirait :

                    We find most cases of tail _recursion_ convert reasonably well to loops, and most cases of non-recursive tail calls encode state machines that convert reasonably well to loops wrapped around enums. Neither of these are _quite_ as pretty as the tail-call-using variants, but they do work and are "as fast"*, as well as idiomatic for C and C++ programmers (who are our primary audience).

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

                    • [^] # Re: simple ?

                      Posté par  . Évalué à 4.

                      Il y a quand même des cas ou cela marche, on dirait

                      Oui mais:

                      • il faut utiliser une convention d’appel particulière (fastcc pour LLVM), et ça semble avoir un impact sur les perf’ et provoquer des changements d’ABI ("it turns into callee-restore when you enable the -tailcallopt flag")
                      • ça pose problème pour avoir une destruction déterministe des objets (objets au sens large, pas sens OO)

                      On pourrait aussi regretter que Rust wrap en cas d’overflow (et va donc silencieusement produire de mauvais résultats) au lieu de trap ou un truc du genre. Ce qui est dommage pour un langage qui se veut safe. Et c’est un peu pour les même raisons :

                      The reason that Rust provides wrapping integers is that so far, the costs of a better semantics — both in terms of runtime overhead and in terms of implementation effort for the Rust team — exceed the perceived benefits.

                      Source

                      • [^] # Re: simple ?

                        Posté par  (site web personnel) . Évalué à 2.

                        On pourrait aussi regretter que Rust wrap en cas d’overflow (et va donc silencieusement produire de mauvais résultats) au lieu de trap ou un truc du genre.

                        Ce qui est idiot en plus, c'est que ce si tu fais du code qui utilise cette propriété (genre filtre numérique), l'optimisateur se croit avec des entiers qui ne wrapent pas, et te détruit ton code.

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

                        • [^] # Re: simple ?

                          Posté par  . Évalué à 1.

                          Un entier c’est pas censé wrapper. Par contre un type modulaire oui.
                          La différence existe en Ada, et plus ou moins en C (overflow sur unsigned ça wrap, overflow sur int c’est un UB).

                      • [^] # Re: simple ?

                        Posté par  . Évalué à 2.

                        Plutôt d'accord sur le problème du wrapping en cas d'overflow, mais ça reste quand même mieux que le C/C++ avec son comportement indéfini (bon c'est pas dûr).

                        C'est de la faute des concepteurs de CPUs!! Ils auraient du suivre le MIPS ou toutes les instructions entière ont un mode 'trap sur overflow' ça permet de détecter le dépassement entier avec un cout quasi-nul.

                        Le seul CPU qui prévoit de fournir l'équivalent de ce que fournissait le MIPS, c'est le Mill un CPU même pas implémenté en FPGA à l'heure actuel..

                        • [^] # Re: simple ?

                          Posté par  . Évalué à 1.

                          Il y a aussi un mieux par rapport au C sur le fait que les casts sont pas implicites et que les fonctions de conversion fournies par la lib standards sont "safe".

                          Du genre,

                          let x : u8 = 3u.to_u8().expect("Interger Overflow");

                          Évidement, c'est plus lent parce qu'il y a un pointeur en plus et une comparaison, du coup il y a toujours la possibilité d'avoir un cast classique avec

                          let x : u8 = 3u as u8;

                          En outre, la conversion n'est qu'un sous ensemble minime des cas possibles d'overflow, mais ça reste assez pratique.

                          • [^] # Re: simple ?

                            Posté par  . Évalué à 3.

                            Hum, avec une syntaxe pareil 99% des gens utiliseront les cast classique au lieu de la version safe :-(

                • [^] # Re: simple ?

                  Posté par  . Évalué à 3.

                  Honnêtement, j'ai eu la même réaction que toi au début sur la syntaxe qui est clairement moins simple que du ML, mais à l'usage, ça ne m'a pas plus dérangé que ça. La gestion de la mémoire est plus complexe, en effet il y a plusieurs "subtilités" comme tu dis, mais je ne comparerais pas avec C++.

                  Je suis incapable de coder en C++, j'en fais en stage à chaque fois, puis au bout de 6 mois, je remercie tout les dieux de la création d'avoir la possibilité d'arrêter d'en faire. Avec rust, c'est différent, car en effet il faut réfléchir a un modèle qui n'est pas forcément celui auquel tu es habitué quand tu codes dans un langage avec GC, mais le compilateur t'aide. En fait dès que tu te foire, il t'engueule. C'est quand même un énorme plus comparé à C++.

                  Finalement, je pense que des langages comme ocaml et rust sont complémentaires. Rust, comme le dit si bien sa page d'accueil, est un "system programming language" qui a vocation à donner le maximum de contrôle à l'utilisateur en altérant le moins possible la sureté.

                  Ocaml, c'est plus un langage de haut niveau qui a vocation à donner plus de flexibilité et d'expressivité à l'utilisateur tout en ayant un modèle d'execution et de compilation simple, au détriment du contrôle de l'utilisateur sur la mémoire. À mon avis, pour écrire un compilateur ou un site web, ocaml est plus adapté que rust, mais rust l'est plus pour écire un solver de contraintes ou une pile réseau. Quant à C++, je préfère ne pas entendre parler.

            • [^] # Re: simple ?

              Posté par  (site web personnel) . Évalué à 2. Dernière modification le 15 octobre 2014 à 09:13.

              J'imagine bien que la syntaxe permet des subtilités. Mais je croyais que le C++ avait calmé les concepteurs concernant les subtilités des langages. Cela peut devenir une énorme source d'erreur.

              Mmmh, entre nous il y a un compromis à trouver entre avoir trop de subtilités et une simplification extrême de la grammaire à la ML/OCaml.

              Sous peine de finir avec des horreurs du genre .+ .- .* int_of_float and co vendu au nom de la "safety" et de l'inférence de type.
              Pour résulter en un truc "simple" mais qui vous pourrit joyeusement la vie à l'usage ;;;;;;;;;;;;;;;

              • [^] # Re: simple ?

                Posté par  (site web personnel) . Évalué à 3. Dernière modification le 15 octobre 2014 à 10:13.

                Tu compares int_of_float avec ça ?

                    let x : u8 = 3u.to_u8().expect("Integer Overflow");
                
                    let x : u8 = 3u as u8;
                

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

                • [^] # Re: simple ?

                  Posté par  (site web personnel) . Évalué à 2. Dernière modification le 15 octobre 2014 à 10:45.

                  Tu compares int_of_float avec ça ?

                  Je n'ai jamais dit que l'approche de Rust était la bonne. Juste que celle d'OCaml "le tout explicit et simple" était une connerie.

                  • [^] # Re: simple ?

                    Posté par  (site web personnel) . Évalué à 2.

                    "le tout explicit et simple" était une connerie.

                    Je ne suis pas d'accord. Le +. et *. est chiant, mais en ouvrant le bon module (open float), cela se corrige.

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

                    • [^] # Re: simple ?

                      Posté par  (site web personnel) . Évalué à 5. Dernière modification le 15 octobre 2014 à 11:58.

                      Je ne suis pas d'accord. Le +. et *. est chiant, mais en ouvrant le bon module (open float), cela se corrige.

                      Donc je traduis tes propos en quelque chose de moins biaisé :

                      "Donc oui le "tout explicite" est une connerie, ".+" en est le preuve. Mais depuis OCaml 4.0 ( 2ans max si ma mémoire est bonne), on a admit sans admettre que c'était une connerie et on s'est décider à fournir l'operator overloading pour OCaml aprés 10 ans de refus ( comme pour le threading d'ailleurs ).
                      Et tu peux maintenant utiliser de manière transparente l'overloading en ouvrant le module float tout en foutant à la poubelle le principe du 'tout est explicite'.
                      "

                      Sinon entre parenthèses, l'operator overloading existe en C++ depuis 20 ans maintenant.

                      Mais j'ai vraiment pas envie de m'éterniser là dessus: faire avouer à un programmeur OCaml que leur langage a des problèmes c'est comme essayer de faire avouer à un Apple-boy que son Mac n'est pas parfait: long, pénible et peine perdu.

                      De manière général, quand un langage généraliste reste un langage de niche comme OCaml…..C'est qu'il y a des raisons à ça…

                      • [^] # Re: simple ?

                        Posté par  (site web personnel) . Évalué à 2.

                        Le problème est qu'il voulait simplifier l'inférence de type avec la structure du langage. Si tu rajoute du polymorphisme, l'inférence ne marche plus.

                        on s'est décider à fournir l'operator overloading pour OCaml aprés 10 ans de refus ( comme pour le threading d'ailleurs ).

                        Je ne savais pas qu'il refusais jusqu'à présent.

                        De manière général, quand un langage généraliste reste un langage de niche comme OCaml…..C'est qu'il y a des raisons à ça…

                        Sans doute, mais de la même façon, je n'arrive pas à comprendre comment Java peut être aussi populaire.

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

                        • [^] # Re: simple ?

                          Posté par  (site web personnel) . Évalué à 8.

                          je n'arrive pas à comprendre comment Java peut être aussi populaire

                          Parce que un peu comme pour PHP il suffit d'être médiocre pour arriver à faire des choses avec.

                          De rien, ne me remerciez pas… --->[]

                        • [^] # Re: simple ?

                          Posté par  . Évalué à 1.

                          Je trouve que la solution de rust pour la surcharge des opérateur n'est pas si mal. Chaque opérateur a un Trait associé que tu dois implémenter pour ton type.

                          Exemple :

                          pub trait Add<RHS, Result> {
                              fn add(&self, rhs: &RHS) -> Result;
                          }

                          L'inférence fonctionne la plupart du temps, et quand ce n'est pas le cas il suffit d'ajouter une petite annotation, comme

                          3u + 4

                          De plus, il y a toujours une vérification :

                          let x = 3u;
                          x + 3.0
                          mismatched types: expected `uint`, found `<generic float #0>` (expected uint, found floating-point variable)
                          

                          Tu compares int_of_float avec ça ?

                          tu es mauvaise langue, c'est exactement la même chose que int_of_float sauf que ça gère les cas ou la valeur n'est pas convertible. Je sais pas trop ce qu'ocaml fait dans ce cas, mais là la méthode
                          .to_u8() renvoit juste une option.

                          .expect(error) c'est une méthode qui dit 'Renvoit v si Som(v) ou failure "error" si none'

                          • [^] # Re: simple ?

                            Posté par  (site web personnel) . Évalué à 2.

                            J'ai du mal avec ton exemple. Add est commutatif, on dirait que ta définition ne l'est pas. Dans "3.0 + 1", tu fais jouer l'addition flottante ou entière ? Est-ce qu'une addition avec un entier et un flottant peut exister ? Est-ce que Add est polymorphique ? Qu'est-ce qui se passe si tu joues avec les classes mères ?

                            Je sais pas trop ce qu'ocaml fait dans ce cas, mais là la méthode .to_u8() renvoit juste une option.

                            Ocaml renvoit une exception.

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

                            • [^] # Re: simple ?

                              Posté par  . Évalué à 1. Dernière modification le 15 octobre 2014 à 14:30.

                              La définition est commutative, même si à première vue c'est pas clair. RHS, c'est le type des éléments additionnés, et Result c'est le type de retour. Ça permet d'implémenter des lois externes. L'implémentation pour int est en fait Add.

                              Dans "3.0 + 1", tu fais jouer l'addition flottante ou entière ?

                              Aucune des deux, justement. Ça ne compile pas. En effet, comme expliqué précédement, les deux opérandes doivent avoir le même type.

                              Est-ce que Add est polymorphique ?

                              Oui et non du coup. + est un opérateur polymorphe parce qu'il est implémenté pour tout les types T implémentant le trait Add.
                              Donc

                              1u + 1u
                              1.0 + 1.0

                              Mais il n'est pas "polymorphique" comme en javascript ou php ou tu peux ajouter deux types différents. Mieux, il est impossible d'implémenter un tel opérateur plus en rust. (enfin, pas à ma connaissance.
                              Je connais pas trop haskell, mais il me semble que c'est assez similaire avec leur typeclass Num.

                              Qu'est-ce qui se passe si tu joues avec les classes mères ?

                              pas compris ;)

                              Ocaml renvoit une exception

                              En fait non :

                              int_of_float nan;;
                              - : int = 0
                              • [^] # Re: simple ?

                                Posté par  (site web personnel) . Évalué à 2.

                                Qu'est-ce qui se passe si tu joues avec les classes mères ?

                                Je veux dire si tu définit un trait avec une classe mère et que tu utilises des objets fils dans le ADD, il se passe quoi ? Si tu utilises 2 trait un pour la classe mère, l'autre pour le fils, etc…

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

                                • [^] # Re: simple ?

                                  Posté par  . Évalué à 1.

                                  il n'y a pas (encore) d'héritage en rust.

                                  Sinon, je crois que j'ai compris ce que tu disais sur la commutativité, mais c'est vrai en ocaml aussi,
                                  tu peux définir

                                  let (.+) x y = if x < 3 then x + y else y

                                  qui n'est pas commutatif. Tu peux parfaitement faire des trucs contre intuitifs.

                                  D'ailleurs, + n'est pas vraiment commutatif dans des langages comme ocaml ou rust, par exemple

                                  let i = ref 0 in
                                  !i + (incr i ; 1)

                                  Sinon, pour éviter que les choses ne tournent mal, il n'est uniquement possible d'implémenter un trait pour un type que si le trait ou le type est défini dans le module courant, ce qui évite à des libs externes de faire des bétises qui t'impactent sans que tu t'en rendes compte.

                                  • [^] # Re: simple ?

                                    Posté par  (site web personnel) . Évalué à 2.

                                    Tu peux parfaitement faire des trucs contre intuitifs.

                                    Oui, mais + est toujours un plus entier, sauf redéfinition.

                                    Si tu veux changer ça, avoir à la fois du polymorphisme, de l'inférence de type et de l'héritage, à priori, c'est impossible. On ne peut avoir que 2 des 3. Je serais curieux de voir un langage sans héritage, pour voir.

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

                                    • [^] # Re: simple ?

                                      Posté par  (site web personnel) . Évalué à 2. Dernière modification le 15 octobre 2014 à 17:17.

                                      Si tu veux changer ça, avoir à la fois du polymorphisme, de l'inférence de type et de l'héritage, à priori, c'est impossible. On ne peut avoir que 2 des 3. Je serais curieux de voir un langage sans héritage, pour voir.

                                      Golang n'a pas d'heritage.

                                      Il ont une approche basé sur de la composition anonyme et de l'interfaçage structurel.

                                      Et si ça a l'air louche au premier abord, je trouve leur approche TRES puissante à l'usage.

                                      Primo, ça permet de definir des methodes dans des classes défini dans des modules externes, de la même manière que les "traits" dans Rust.

                                      Secondo, la composition anonyme autorise une approche "Mixin", en ayant l'avantage de l'heritage multiple sans avoir les problèmes de l'heritage en Diamant.

                                      Tertio, l'utilisation de go-pointer comme composant anonyme autorise de se rapprocher de ce qu'on peut faire dans un langage à prototype: Tu crée un objet depuis un objet.
                                      ça te permet de garder sa contextualisation, son interface et de l'étendre de manière dynamique.

                                      • [^] # Re: simple ?

                                        Posté par  . Évalué à 1.

                                        Il ont une approche basé sur de la composition anonyme et de l'interfaçage structurel.

                                        Hmm, si on n'a pas fait d'études ça veut dire quoi en pratique ?

                                        Tertio, l'utilisation de go-pointer comme composant anonyme autorise de se rapprocher de ce qu'on peut faire dans un langage à prototype: Tu crée un objet depuis un objet.
                                        ça te permet de garder sa contextualisation, son interface et de l'étendre de manière dynamique.

                                        Je ne suis pas sûr de comprendre non plus. En vrai ce serait bien si tu avais des bouts de codes, liens et/ou des mots clés pour voir des exemples d'utilisation.

                                        • [^] # Re: simple ?

                                          Posté par  (site web personnel) . Évalué à 2. Dernière modification le 16 octobre 2014 à 12:20.

                                          La composition anonyme est juste une "astuce" pour avoir une forme appauvri d’héritage et eviter les collisions de noms

                                          Par exemple, pour une struct animal

                                              struct Animal{
                                                 int size;
                                              }
                                          

                                          un heritage typique est :

                                              struct Ours: Animal{
                                          
                                              }
                                          
                                              Ours ours;
                                              print(ours.size)
                                          

                                          Une composition serait de faire :

                                              struct Ours{
                                                 Animal animal_data;
                                              }
                                              Ours ours
                                              print(ours.animal_data.size)
                                          

                                          Une composition anonyme en Go ressemble à ça

                                              struct Ours{
                                                 Animal;
                                              }
                                              Ours ours
                                              print(ours.size)
                                          

                                          La principal différence avec l'heritage est que la composition anonyme n'implique pas le polymorphisme.

                                          Il est impossible en golang de faire qqchose comme :

                                              struct Arbre{
                                                 virtual msg() { print("hello arbre") }
                                              }
                                          
                                              struct Tilleul {
                                                 Arbre;
                                                 virtual msg() { print("hello tilleuls") }
                                              }
                                          
                                              Arbre* a = new Tilleuls()
                                              a->msg()
                                              # hello tilleuls
                                          

                                          Ça ne marchera simplement pas dans le cas d'une composition anonyme, simplement car la composition n'implique pas le système de vtable qu'a C++ par exemple.
                                          Mais d'un autre coté, ça permet de composer un objet qui contient plusieurs sous objets de manière completement transparente comme on peut faire en héritage multiple et même si ces objets ont un parent en commun : on s'en fout.

                                          Pour le polymorphisme, c'est fait en Golang via le concept d'interface et d’héritage structurel et non par type comme en Java.
                                          C'est assez bien expliqué ici http://golangtutorials.blogspot.ch/2011/06/polymorphism-in-go.html

                                          Grosso-modo contrairement à Java ou similaire, pour "matcher" une interface, tu n'as pas besoin de l’hériter ou d'en dériver.
                                          Tout objet possédant une "signature" similaire à celle de l'interface ( une methode Speak() ) dans l’exemple, sera considérer comme un objet parfaitement valide.

                                          C'est puissant, trés puissant.

                                          Quand au dernier point à propos de la composition anonyme avec un go-pointer, Ceci l'illustre bien

                                              type JohnLenon struct {
                                                  nom string
                                              }
                                          
                                              type JohnLenonEnConcert struct{
                                                  *JohnLenon
                                          
                                                  concert string
                                          
                                              }
                                          
                                          
                                              func main() {
                                          
                                                  john := JohnLenon{"john's name"}
                                                  john_concert := JohnLenonEnConcert{&john, "A paris"}
                                          
                                                  fmt.Printf(" nom de john: %s \n", john.nom)
                                                  fmt.Printf(" nom de john en concert : %s \n", john_concert.nom)
                                          
                                                  john.nom = "bob";
                                                  fmt.Printf(" nom de john en concert : %s \n", john_concert.nom)    
                                          
                                              }
                                          
                                          
                                           nom de john: john's name 
                                           nom de john en concert : john's name 
                                           nom de john en concert : bob     
                                          
                                          

                                          La composition dans ce cas reference un objet déja existant… qui veut etre modifié… cloné… partagé… etc..

                                        • [^] # Re: simple ?

                                          Posté par  (site web personnel) . Évalué à 2.

                                          Si j'ai bien compris go, le type de go est donné par la présence ou non des méthodes. Il n'y a pas d'héritage dans le sens typage, mais "récupération de code". Ce qui peut revenir au même avec typage de structure.

                                          Par contre, je n'ai pas compris le lien avec les prototypes.

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

                                          • [^] # Re: simple ?

                                            Posté par  (site web personnel) . Évalué à 1.

                                            Par contre, je n'ai pas compris le lien avec les prototypes.

                                            J'ai essayer de l'expliquer dans le post précédent le tiens.

                                            Ceci dit, je suis à peut prêt aussi doué pour les explications détaillées que greenpeace pour construire des centrales nucléaires.

                                            • [^] # Re: simple ?

                                              Posté par  (site web personnel) . Évalué à 1.

                                              C'est vrai qu'en prototype, on peut modifier les pères à la volé, ce que permet ton dernier exemple.

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

                            • [^] # Re: simple ?

                              Posté par  . Évalué à 1.

                              Bon, j'ai du une bétise en fait, Add n'est clairement pas commutatif.

        • [^] # Re: simple ?

          Posté par  . Évalué à 2.

          Honnêtement, je n’en sais rien, mais toutes les discussions sont publiques sur la liste de discussion du projet et je peux te dire qu’ils ont discuté longtemps de la syntaxe des clôtures: chaque choix a été pris pour une raison bien précise.

          Écrit en Bépo selon l’orthographe de 1990

    • [^] # Re: simple ?

      Posté par  . Évalué à 10.

      Pourquoi faire aussi complexe ?

      Pour que les programmeurs C++ puissent comprendre.

      • [^] # Re: simple ?

        Posté par  . Évalué à 2.

        J’ai bien aimé la blague ;-) donc en C++ :

        int twice(int x, int (*f)(int))
        {
          return f(x) + f(x);
        }
        
        int main() {
          int (*square)(int) = [] (int x) { return x * x; } ;
        
          twice(5,square);
        
          return 0;
        }

        Avec des types génériques :

        template <class T> T twice(const T& x, T (*f)(const T&))
        {
          return f(x) + f(x);
        }
        
        template <class T> T square(const T& x)
        {
           return x*x;
        }
        
        int main() {
          twice(5,square);   // Évalué à 50
          twice(5.0,square); // Évalué à 50.0
        
          return 0;
        }

        Je n’ai pas essayé à faire une lambda template. Les objets de type T pouvant devenir gros, j’ai décidé de passer les paramètres par référence, pour préciser au compilateur que l’objet ne serait pas modifié, j’ai ajouté const. Ce que je n’ai pas fait sur le premier exemple avec des paramètres par copie.

        • [^] # Re: simple ?

          Posté par  . Évalué à 4. Dernière modification le 14 octobre 2014 à 18:47.

          En C++ n’aime pas trop les pointeurs, moins les pointeurs de fonctions, et encore moins les casts d’une fermeture vers un pointeur. On aurait pu mettre :

          auto square = [](auto x) { return x * x; };
          auto twice = [](auto x, auto f) { return f(x) + f(x); };
          

          Ça marche avec des int et des double, mais aussi avec des foncteurs, sans perdre le type. Des const& ou des références universelles peuvent être rajoutés, au besoin. Mais c’est vrai qu’une traduction littérale aurait été plus proche de:

          template <typename Func>
          auto twice2(int x, Func f)
          {
          return f(x) + f(x);
          }
          Ou, à la rigeur, d’un paramètre std::function.

          • [^] # Re: simple ?

            Posté par  . Évalué à 2.

            Bon, j’ai essayé ce code :

            // includes …
            int main() {
              auto square = [](auto x) { return x * x; };
              auto twice = [](auto x, auto f) { return f(x) + f(x); };
            
              std::cout << twice(5,square) << std::endl;
              std::cout << twice(5.1,square) << std::endl;
            
              return 0;
            }

            Le but étant de vérifier comment il instancie les différentes versions en fonction des types.

            Compilation :

            $ g++ -std=c++11 main.cpp
            main.cpp: In function 'int main()':
            main.cpp:29:25: error: parameter declared 'auto'
               auto square = [](auto x) { return x * x; };
                                     ^
            main.cpp: In lambda function:
            main.cpp:29:37: error: 'x' was not declared in this scope
               auto square = [](auto x) { return x * x; };
                                                 ^
            main.cpp: In function 'int main()':
            main.cpp:30:24: error: parameter declared 'auto'
               auto twice = [](auto x, auto f) { return f(x) + f(x); };
                                    ^
            main.cpp:30:32: error: parameter declared 'auto'
               auto twice = [](auto x, auto f) { return f(x) + f(x); };
                                            ^
            
            …

            Je ne suis pas un pro du C++11, quelqu’un pourrait m’expliquer ?

            • [^] # Re: simple ?

              Posté par  (site web personnel) . Évalué à 2.

              Avec CLang c'est un peu plus explicite :

              main.cpp:4:20: error: 'auto' not allowed in lambda parameter
                auto square = [](auto x) { return x * x; };
                                 ^~~~
              main.cpp:5:19: error: 'auto' not allowed in lambda parameter
                auto twice = [](auto x, auto f) { return f(x) + f(x); };
                                ^~~~
              main.cpp:5:27: error: 'auto' not allowed in lambda parameter
                auto twice = [](auto x, auto f) { return f(x) + f(x); };
                                        ^~~~
              main.cpp:7:16: error: no matching function for call to object of type '<lambda at main.cpp:5:16>'
                std::cout << twice(5,square) << std::endl;
                             ^~~~~
              main.cpp:5:16: note: candidate template ignored: couldn't infer template argument '$auto-0-0'
                auto twice = [](auto x, auto f) { return f(x) + f(x); };
                             ^
              main.cpp:8:16: error: no matching function for call to object of type '<lambda at main.cpp:5:16>'
                std::cout << twice(5.1,square) << std::endl;
                             ^~~~~
              main.cpp:5:16: note: candidate template ignored: couldn't infer template argument '$auto-0-0'
                auto twice = [](auto x, auto f) { return f(x) + f(x); };
              
              • [^] # Re: simple ?

                Posté par  (site web personnel) . Évalué à 2.

                En fait si on veut avoir la même chose avec les std:function et que ça marche :

                #include <iostream>
                #include <functional>
                
                template <class T> std::function<T(T x)> square = [] (T x) {
                    return x * x;
                };
                
                template <class T> std::function<T(T x, std::function<T(T x)> f)> twice = [] (T x, std::function<T(T x)> f) {
                    return f(x) + f(x);
                };
                
                
                int main() {
                    std::cout << twice<int>(5, square<int>) << std::endl;
                    std::cout << twice<double>(5.1, square<double>) << std::endl;
                
                    return 0;
                }

                Par contre il faut se méfier, il faut activer c++1y g++ -std=c++1y main.cpp pour avoir des variables paramétrables.

                En fait square et twice sont des variables de type std::function mais paramétrés (version template).

                Après si on veut simplifier un poil, on peut utiliser auto :

                #include <iostream>
                #include <functional>
                
                template <class T> std::function<T(T x)> square = [] (auto x) {
                    return x * x;
                };
                
                template <class T> std::function<T(T x, std::function<T(T x)> f)> twice = [] (auto x, auto f) {
                    return f(x) + f(x);
                };
                
                
                int main() {
                    std::cout << twice<int>(5, square<int>) << std::endl;
                    std::cout << twice<double>(5.1, square<double>) << std::endl;
                
                    return 0;
                }
                • [^] # Re: simple ?

                  Posté par  (site web personnel) . Évalué à 3.

                  Je suis rassuré, Rust fait moins pire que C++.

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

                  • [^] # Re: simple ?

                    Posté par  (site web personnel) . Évalué à 2.

                    sauf que c'est pas comparable, on a d'un côté que du int, de l'autre du générique.

                    Si tu veux la même chose que l'exemple Rust c'est ici : https://linuxfr.org/users/outs/journaux/rust-en-version-0-12#comment-1567855

                    • [^] # Re: simple ?

                      Posté par  . Évalué à 3. Dernière modification le 15 octobre 2014 à 12:59.

                      je pense que la même chose en rust c'est un truc comme :

                      fn square<T : Mul<T,T>>(x : &T) -> T { *x * *x }
                      fn twice<T : Add<T,T>>(x : &T, f : |&T| -> T ) -> T { f(x) + f(x) }
                      
                      fn main() {
                              println!("{}", twice(&5i, square));
                              println!("{}", twice(&5f64, square));
                      }

                      j'ai utilisé des références parce que même si les types entiers sont Clonable, et qu'on pourrait forcer le trait Clone sur T, vu que tu as l'air de vouloir que ça soit le plus générique possible des références m'ont semblées plus apropriées, même si le *x * *x est assez moche.

                • [^] # Re: simple ?

                  Posté par  . Évalué à 2.

                  Est-on obligé d’écrire : <int> ou le compilateur peut-il le déduire seul ?

                • [^] # Re: simple ?

                  Posté par  . Évalué à 3. Dernière modification le 16 octobre 2014 à 01:06.

                  En C++, il vaut mieux éviter les std::function qui posent des problèmes de performance, aussi bien en temps d’exécution qu’en espace mémoire, et les remplacer par des templates ou des auto, en particulier pour des fermetures. Écrire auto f = [](){} et std::function<…> f = [](){} n’est pas du tout équivalent.

                  Pour ce genre de détails, qui sont légions en C++, il y a pas mal de bouquins intéressant, en particulier Effective Modern C++ de Scott Meyers, en early release chez O’Reilly pour tout ce qui est C++11/C++14.

              • [^] # Re: simple ?

                Posté par  (site web personnel) . Évalué à 3.

                auto comme paramètre de lambda, c'est du C++14.

                • [^] # Re: simple ?

                  Posté par  (site web personnel) . Évalué à 3.

                  ho mince, tu as raison, en fait en C++14 (avec -std=c++1y) ça passe direct.

                  Donc au final entre Rust et C++ c'est C++ qui gagne avec

                    auto square = [](auto x) { return x * x; };
                    auto twice = [](auto x, auto f) { return f(x) + f(x); };
        • [^] # Re: simple ?

          Posté par  (site web personnel) . Évalué à 2.

          D'ailleurs la première version s'écrirait plutôt :

          int twice(int x, std::function<int(int)> f) {
              return f(x) + f(x);
          };
          
          int main() {
              auto square = [] (int x) { return x * x; };
          
              std::cout << twice(5, square) << std::endl;
          
              return 0;
          }
          • [^] # Re: simple ?

            Posté par  (site web personnel) . Évalué à 2.

            Ou même :

            using namespace std;
            
            int main() {
                 auto square = [] (int x) -> int { return x * x; };
            
                 auto twice = [] (int x, function<int(int)> f) -> int { return f(x) + f(x); };
            
                 cout << twice(5, square) << endl; // 50
                 return 0;
            }
    • [^] # Re: simple ?

      Posté par  . Évalué à 5.

      En haskell :

      twice :: (Num a) => a -> (a -> a) -> a
      twice x f = f x + f x
      
      square :: (Num a) => a -> a
      square x = x * x

      Dans ghci :

      Prelude> twice 5 square
      50
      Prelude> twice 5 (^2)
      50

      Les déclarations de type ne sont pas nécessaire, mais permette de garantir que la fonction prend un type numérique et retourne le même type. Fonctionne avec Int, Integer, Float, etc.

      • [^] # Re: simple ?

        Posté par  . Évalué à 5. Dernière modification le 14 octobre 2014 à 19:50.

        En Haskell, on aime le point free et les monades, d’où :

        square :: Num a => a -> a
        square = join (*)

        et de la même façon, mais twice pouvant prendre une fonction plus générale :

        Num a => b -> (b -> a) -> a
        twice = flip (join (+) .)
        • [^] # Re: simple ?

          Posté par  . Évalué à 2.

          Hum, ton join, c'est l'instance Reader ? Pas mal, ça…

          • [^] # Re: simple ?

            Posté par  . Évalué à 4.

            join, dans le module Control.Monad, c’est le join classique de monades. Haskell a choisit d’utiliser bind et return, mais on pourrait utiliser join à la place. Dans tous les cas passer de l’un à l’autre est assez simple:

            join :: Monad m => m (m a) -> ma
            join = (>>= id)

            Dans l’exemple que je donne plus haut, on tient compte du fait que (->) r soit une instance de Monad, pour laquelle join soit applicable, avec pour définition :

            instance Monad ((->) r) where
              return = const
              m >>= f = \x -> f (m x) x

            On a (*) : Num a => a -> a -> a, donc (*) est une instance de Monad pour Num a => (->) a. Puisque join = (>>= id), alors join (*) = id >>= (*) = \x -> (*) (id x) x = \x -> (*) x x = \x -> x * x, soit un fonction qui prend un nombre en argument et retourne sa valeur multipliée par elle-même.

            • [^] # Re: simple ?

              Posté par  (site web personnel) . Évalué à 9.

              On parle d'une addition et d'une multiplication, tu as vu la tronche de ton code ? Qui peut comprendre en 10s ce qu'il fait exactement ?

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

        • [^] # Re: simple ?

          Posté par  . Évalué à 2.

          Je croyais qu’il fallait éviter les monades si possible pour faire du « pur »…

          Sinon, pour square, quitte à définir la fonction sans paramètre, j’aurais écrit :

          square :: Num a => a -> a
          square = (^2)

          Quand à ton deuxième code, j’ai du mal à comprendre l’avantage à part rendre obscur une fonction simple.

          • [^] # Re: simple ?

            Posté par  . Évalué à 3. Dernière modification le 16 octobre 2014 à 01:17.

            Je croyais qu’il fallait éviter les monades si possible pour faire du « pur »…

            Non, on évite les effets de bords, qui sont implémentés par des monades spécifiques telles que IO ou ST (pour le parallélisme). Pour ces cas particuliers justement les monades permettent de cacher les effets de bord dans une structure algébrique tout à pure, la monade. On peut difficilement faire plus pur que la monade (->) r qui correspond aux fonctions !

            Il n’y a pas d’avantages autre qu’humoristique ou algébrique à utiliser les définitions que j’ai donné. Ça reste tout de même instantanément compréhensible par les Haskellers, de part leur esprit tordu. Dans le même genre on peut lire The evolution of a Haskell programmer.

  • # La guerre de l'indentation, les espaces contrent-attaquent

    Posté par  . Évalué à 1.

    Sur le guide de Rust on peut lire:

    println!("Hello, world!");

    This line does all of the work in our little program. There are a number of details that are important here. The first is that it's indented with four spaces, not tabs. Please configure your editor of choice to insert four spaces with the tab key.

    Pourquoi ça ? En tout cas je viens d'essayer sur Debian avec des vrais tabulations, ça fonctionne aussi.

Suivre le flux des commentaires

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