Sortie de JDK 10

Posté par . Édité par Dring, BAud, Aldoo, Xavier Claude, Davy Defaud, ZeroHeure, Lucas, Nonolapéro, olivierweb et Bruno Michel. Modéré par ZeroHeure. Licence CC by-sa.
Tags :
38
9
nov.
2018
Java

Cette dépêche aura mis du temps à venir au monde, et depuis le JDK 11 — la version avec support étendu (LTS) — est sorti, mais il est encore temps de troller^W discuter de façon constructive de l’évolution d’un langage qui reste aujourd’hui au cœur des entreprises.

C’est l’occasion de (re)voir les ajouts côté langage, les changements et retraits côté API, les évolutions de la machine virtuelle Java (JVM), la gestion du code source ; le tout documenté au travers des JEP à la base des spécifications de Java.

Sommaire

Côté langage

Un ajout : l’inférence de type pour les variables locales (JEP 286)

Il est désormais possible de déclarer des variables locales en remplaçant le type par le mot var afin de laisser le compilateur deviner le type :

// ici, le compilo comprend que edt est de type Map<String, String>
var edt = Map.of("lundi", "piscine", "mardi", "pétanque");

Ainsi, Java s’aligne sur de nombreux autres langages (sur la JVM, notamment Scala et Kotlin). Ce changement devrait inciter à éviter d’écrire des lignes à rallonge dont le seul but est d’éviter d’écrire les types des résultats intermédiaires. En effet, certains types peuvent être soit inconnus du programmeur (qui a la flemme d’ouvrir la Javadoc), soit très longs à écrire (types génériques), voire impossibles à écrire comme les types non dénotables, par exemple pour une classe anonyme :

var x = new Object() { int a; }; x.a = 42;

Remarque : var n’est pas un mot-clé, mais un identifiant de type réservé. Ainsi, il est toujours possible de déclarer une variable ou une méthode de nom var… mais pas une classe !

Côté API

Changements

JEP 314

On parle ici d’une amélioration sensible de l’internationalisation en s’appuyant sur des extensions d’Unicode. Voici la liste des nouvelles venues :

  • cu (type de devise, « currency type») ;
  • fw (premier jour de la semaine, « first day of week ») ;
  • rg (region override) ;
  • tz (time zone).

Tout ça va permettre aux classes de formatage (java.text.DateFormat, java.text.NumberFormat…) de récupérer directement les informations Unicode disponibles.

Suppressions (API obsolètes)

Suppression de l’attribut java.lang.SecurityManager.inCheck.
Suppression des méthodes suivantes :

  • java.lang.Runtime.getLocalizedInputStream(java.io.InputStream) ;
  • java.lang.Runtime.getLocalizedOutputStream(java.io.OutputStream) ;
  • java.lang.SecurityManager.classDepth(java.lang.String) ;
  • java.lang.SecurityManager.classLoaderDepth() ;
  • java.lang.SecurityManager.currentClassLoader() ;
  • java.lang.SecurityManager.currentLoadedClass() ;
  • java.lang.SecurityManager.getInCheck() ;
  • java.lang.SecurityManager.inClass(java.lang.String) ;
  • java.lang.SecurityManager.inClassLoader().

Côté JVM

JEP 304

Le ramasse‐miettes (garbage collector) est désormais défini par une interface plus propre, permettant dans le futur d’ajouter ou retirer plus facilement des implémentations de ramasse‐miettes. À noter que, dans cette version, les auteurs se sont contentés de faciliter ce travail, sans le mettre en œuvre.

JEP 307

Le ramasse‐miettes par défaut (nommé « G1 » et promu à ce poste dans le JDK 9) est amélioré pour limiter les cas de latence, et ainsi limiter les impacts de la version précédente pour les utilisateurs.

JEP 310

La mémoire consommée par des processus java est réduite en améliorant le partage de méta‐données. Cela a également un effet positif sur la vitesse de démarrage, avec des objectifs ambitieux : des lancements jusqu’à 20 à 30 % plus rapides, et une économie de 340 Mio sur des processus consommant 13 Gio. À noter que ce dernier chiffre concerne un cas de serveur d’applications faisant tourner six JVM.

JEP 312

Pas mal d’actions émises par la JVM imposent aujourd’hui un arrêt de tous les fils d’exécution (threads) en cours. Cette JEP permet à la JVM d’arrêter un ou plusieurs fils d’exécution spécifiques, sans tout stopper.

Pour se figurer l’impact, il faut se dire qu’aujourd’hui la capture d’une pile d’appel complète — lors de l’interception d’une exception — imposait ce type de blocage complet. Désormais, seul un fil d’exécution sera interrompu, permettant au reste de l’application de continuer joyeusement sa route.

On va donc vers une réduction générale de la latence.

JEP 316

L’idée ici est d’utiliser les nouvelles mémoires économiques de type NV-DIMM en permettant à l’utilisateur de spécifier son choix. Cela se fait, bien sûr non pas dans le code, mais dans les arguments de la JVM, sous la forme : -XX:AllocateHeapAt=<path>.

En effet, ces mémoires sont typiquement accessibles via le système de fichiers, que ce soit sous Windows ou GNU/Linux.

JEP 317

Graal, un nouveau compilateur à la volée (JIT — Just In Time — en anglais) introduit dans le JDK 9 est désormais activable via des options de la ligne de commande, que voici :

-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler

Rien de réellement nouveau, mais cela va permettre d’avoir plus de testeurs (et donc d’améliorer la qualité).

Côté code

JEP 296

Le code source de Java, jusqu’ici réparti dans huit dépôts Mercurial séparés, est regroupé au sein d’un dépôt unique. Pour les évolutions ou corrections qui touchent plusieurs composants (par exemple « jdk » et « langtools »), cela permettra d’avoir un seul commit atomique. Cela est cohérent, sachant que tous les composants suivaient le même cycle de vie de toute façon.

JEP 313

Un peu de nettoyage avec le retrait de l’outil javah, qui n’était de toute façon plus utilisé depuis le JDK 9 et affichait des avertissements lorsqu’on l’invoquait. En charge de la génération de fichiers natifs d’en‐tête, cette fonction avait déjà été réimplémentée directement dans javac depuis le JDK 8.

JEP 322

La numérotation des versions Java change ! On passe donc de :

openjdk version "1.8.0_161"
OpenJDK Runtime Environment (build 1.8.0_161-b14)
OpenJDK 64-Bit Server VM (build 25.161-b14, mixed mode)

À ceci (pour l’exemple, on imagine un build 42 d’un JDK 11 LTS) :

openjdk 11 2018-09-20 LTS
OpenJDK Runtime Environment (build 11+42-LTS)
OpenJDK 64-Bit Server VM (build 11+42-LTS, mixed mode)

Changement a priori cosmétique, mais qui pourrait avoir de lourdes conséquences… L’avenir répondra à cette question. En effet, beaucoup d’éditeurs de logiciels, font des tests assez fins sur les versions pour s’assurer que tel ou tel bogue ne risque pas de les affecter.

Pour les lecteurs de la Javadoc du JRE, l’étiquette @since reste alignée avec la propriété système java.specification.version. Et pour l’occasion, deux nouvelles propriétés sont ajoutées :

  • java.version.date, qui fournit la date de disponibilité de cette version ;
  • java.vendor.version, optionnelle, pour la version spécifique d’un correctif de distribution GNU/Linux par exemple.

JEP 319

Jusqu’ici, le jeu de certificats déclaré dans le JDK était par défaut vide, obligeant un développeur à les rajouter. Désormais, un certain nombre d’autorités sont intégrées, dont notamment :

  • Let’s encrypt ;
  • Digicert ;
  • Comodo ;
  • et quelques autres.

Aller plus loin

  • # Inférence de types

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

    Ça y est, enfin, de l'inférence de type… c'est sans doute le changement le plus important dans le langage, dommage que ce soit avec 15 ans de retard.

    • [^] # Re: Inférence de types

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

      dommage que ce soit avec 15 ans de retard.

      Quel optimisme…

    • [^] # Re: Inférence de types

      Posté par (page perso) . Évalué à 0 (+8/-9).

      C'est surtout aussi inutile que casse gueule… le typage fort c'est un avantage pas un inconvénient.

      Quand je lis "certains types peuvent être soit inconnus du programmeur", je comprend qu'on est pas en train de parler de programmation mais de bidouille, d'un code mal maitrisé qui a toute les chances d'être buggé jusqu'au trognon.

      • [^] # Re: Inférence de types

        Posté par (page perso) . Évalué à 7 (+8/-4).

        C'est surtout aussi inutile que casse gueule… le typage fort c'est un avantage pas un inconvénient.

        L'inférence de type n'a rien à voir avec typage fort ou non.

        L'inférence de type est utilisé en C++ moderne, OCaml ou Rust par exemple alors qu'ils sont également fortement typés.

        L'inférence de type signifie que le compilateur va déduire à partir du contexte le type de l'objet en question. S'il y arrive bien sûr. Une fois le type assigné à l'objet en interne, il va s'assurer que cela est cohérent avec le reste du programme. S'il y a conflit, une erreur va sauter à la compilation pour prévenir l'utilisateur que justement sa déclaration n'est pas bonne et qu'il doit le résoudre manuellement quelque part.

        L'inférence de type permet surtout un code plus lisible et maintenable, en évitant les noms à rallonge de partout, qu'au moindre changement de nom tu aies une cascade de changements connexes à effectuer ou vérifier si ton éditeur t'aide. Et grâce au typage fort justement, tu as la garantie que les données sont correctement manipulées et représentées. Au moindre problème de ce côté, cela ne compilera de toute façon pas. Il n'y a rien de caché au programmeur.

        • [^] # Re: Inférence de types

          Posté par (page perso) . Évalué à 3 (+8/-6).

          C'est assez naïf comme raisonnement. Ce n'est pas parce que le compilateur connait le type que le "programmeur" oui.

          Suffit de passer ce "var" magique à une API qui a des méthodes de même nom mais avec des arguments de types différents pour être sûr d'avoir du code Java du niveau d'un code javascript…

          Ça va compiler sans problèmes, ça c'est sûr!
          Je ne parle même pas des problèmes de refactoring que cela implique.

          L'argument C++ et autre, je vois pas le rapport, en C++ on peut aussi se contenter de void** !

          code plus lisible et maintenable,

          C'est tout le contraire, on ne sait plus de quoi on parle:

          var toto = getNextValue();
          Tu m'explique comment tu arrives à mieux lire le type de toto pour savoir quoi en faire 3 lignes plus bas?

          var titi = toto.apply();
          toujours sûr de savoir de quoi on cause?
          Je suis très curieux de voir un exemple réel…

          en évitant les noms à rallonge

          on parle de type, pas de nom de variable ou de paramètre, gagner quelques octets dans un code source n'apporte rien à moins d'avoir de sérieuses douleurs au doigts.
          Aux dernières nouvelles, le temps à "taper" le code est ridiculement faible par rapport au temps à passer à réfléchir sur l'architecture, les algorithmes, bref à quoi frapper sur le clavier…

          • [^] # Re: Inférence de types

            Posté par (page perso) . Évalué à 10 (+9/-0).

            Suffit de passer ce "var" magique à une API qui a des méthodes de même nom mais avec des arguments de types différents pour être sûr d'avoir du code Java du niveau d'un code javascript…

            Je ne vois pas le rapport. Le problème de JavaScript ou de Python à ce niveau c'est que tu peux avoir une variable de type entier qui est passé à une fonction où le type attendu est une chaîne de caractères ou inversement.

            Là tu parles d'un cas où l'inférence de type a retrouvé le type tout seul et a pu appeler une méthode qui est du bon type. Du coup il n'y a pas de comportement bizarres à prévoir. D'autant plus que le compilateur ne fait pas des divinations très poussées, cela reste souvent assez élémentaire.

            Quand tu écris :

            auto entier = 0;
            auto booleen = false;
            auto chaine = "chaîne de caractère";
            auto classe_complexe = copie_de_cette_classe;
            auto autre_classe_complexe = AutreClasseComplexe(entier, booleen);

            Tu crois qu'il va comprendre quoi ? Ce sera évident le type résultant. Et c'est 90% des cas où l'inférence de type sera utilisé. Le compilateur ne va clairement pas se vautrer sur ça. Car quand ce n'est pas assez clair ce qu'il doit faire, il va râler.

            Je ne parle même pas des problèmes de refactoring que cela implique.

            Grâce au typage fort et à la simplicité de l'écriture, cela fonctionne mieux que sans. Testé et approuvé.

            L'argument C++ et autre, je vois pas le rapport, en C++ on peut aussi se contenter de void** !

            J'ai dis C++ moderne (donc C++11, 14 ou 17). Le C++ à la C avec des pointeurs nus et des casts implicites et compagnie cela ne se fait plus dans les projets modernes (ou ne devraient pas se faire). Bientôt tu vas me parler de Java 1 aussi ?

            Note que Rust et OCaml sont eux irréprochables en terme d'inférence de type et de typage fort. Des langages de conception modernes n'ayant aucun historique à porter là dessus.

            Tu m'explique comment tu arrives à mieux lire le type de toto pour savoir quoi en faire 3 lignes plus bas?

            L'inférence de type est un outil, si tu trouves qu'à un moment c'est plus lisible d'être explicite, fais-le. Cela ne signifie pas que la fonctionnalité est mauvaise pour autant.

            on parle de type, pas de nom de variable ou de paramètre, gagner quelques octets dans un code source n'apporte rien à moins d'avoir de sérieuses douleurs au doigts.

            Je ne parle pas de gagner en temps de frappe, quoique. Mais un code peut avoir des types à rallonge. Du coup tu perds en lisibilité car ton code sera dense qui va noyer ce qui est réellement important.

            L'exemple le plus connu est sans doute les itérateurs en C++. Je préfère mille fois lire ça :

            for (auto it = conteneur.cbegin(); it != conteneur.cend(); it++)
                 // Utiliser l'itérateur

            Ou mieux encore :

            for (auto element : conteneur)
                 // Utiliser l'élément

            Que :

            for (std::vector<UneClassePerso>::const_iterator it = conteneur.cbegin(); it != conteneur.cend(); it++)
                 // Utiliser l'itérateur

            Car oui tu peux rapidement avoir des noms complexes du genre, et je te passe le cas quand c'est un tuple, une map ou autre qui peuvent grossir la taille de la déclaration.

            Si tu fais des fonctions courtes, que tes variables et méthodes sont bien nommées (bref, respecter les bonnes pratiques) tu es largement gagnant à utiliser l'inférence de type. C'est comme râler sur les templates, lambda, etc. en pointant sur un cas de mauvaise utilisation. Alors que pourtant ils sont utiles aussi. C'est comme tout, faut pas en abuser mais on n'a aps à s'en priver.

            • [^] # Re: Inférence de types

              Posté par (page perso) . Évalué à 3 (+6/-4).

              J'ai l'impression que par lisible tu veux dire "rapide à lire".
              Oui, "for (auto element : conteneur)" c'est rapide à lire

              Par lisible, j’entends compréhensible, et ceci ne l'est pas, car si la définition de "conteneur" est 40 lignes plus haut ou si c'est lui même un "auto" machin, le code n'est PAS compréhensible. Même pas en rêve avec tes technos dites "modernes" et les bonnes pratiques de mamie.

              Pour reprendre sur Java et ne pas digresser sur le C++ qui n'a jamais eu de syntaxe claire,

              for (String str : parts){
              }
              est parfaitement compréhensible, on a tout le contexte. On ne peux pas se tromper sur l'utilisation/le type de 'str'.

              Je ne dis pas que le compilateur va se tromper, c'est le programmeur qui ne peux pas s'y retrouver (à moins que ta "bonne pratique" c'est frde nommer les variables/les paramètres en incluant le type.
              genre :

              var myInt = getNextValue();
              Et ici, franchement, on a frôle la débilité profonde.

              Comme tu es sérieux et rigoureux, tu vas écrire pour ton compteur :

              var count = getNextValue();
              et peut être même l'incrémenter quelques lignes plus bas.
              Et comment tu vas être sûr que tu ne risque pas te faire un overflow?
              count c'est un "int", un "long" ? Ah, non pas de bol, il est lu d'un stream octet par octet, c'est un "byte".

              Conclusion, tu ne peux pas comprendre en lisant le code (faut aller voir la signature du getNextValue() pour savoir, une perte de temps), bref, c'est casse gueule au possible et complètement inutile (et c'est pas parce que Rust le fait que c'est bien).

              Cette "évolution" de Java est juste là pour faire plaisir aux amateurs de Javascript qui voudrait goûter à Java.

              (notons au passage que les gros projets Javascript migrent vers Cleartype pour justement avoir du code "lisible" et plus robuste que la loterie à base de "let" et "var")

              • [^] # Re: Inférence de types

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

                var count = getNextValue();

                et peut être même l'incrémenter quelques lignes plus bas.
                Et comment tu vas être sûr que tu ne risque pas te faire un overflow?
                count c'est un "int", un "long" ? Ah, non pas de bol, il est lu d'un stream octet par octet, c'est un "byte".

                Sur un langage moderne (j'imagine que Java a maintenant l'équivalent), on peut manipuler sans soucis le type d'une variable au type inféré:

                    auto counter=getNextValue();
                
                    if ( counter == numeric_limits<decltype(counter)>::max() )
                        throw overflow_error("counter too big");
                
                    ++counter;

                Intérêt: on devient insensible à une évolution de l'API de getNextValue(), par exemple si le type de retour passe de "int" à "long"…

                • [^] # Re: Inférence de types

                  Posté par (page perso) . Évalué à 4 (+7/-4). Dernière modification le 10/11/18 à 11:26.

                  LA question que je pose depuis le début et que tout le monde fait semblant de ne pas comprendre et d'éluder (avec un petit downvote au passage), c'est quand tu lis :

                  auto counter=getNextValue();
                  
                  if ( counter == numeric_limits<decltype(counter)>::max() )
                   throw overflow_error("counter too big");
                  
                  ++counter;

                  comment tu connais le type de retour de getNextValue() sans aller voir sa déclaration (dans un autre fichier ou des centaines de lignes plus loin) ?

                  Ce n'est pas en répondant que "le langage XY a aussi des var/auto" ou "j'y arrive" que tu réponds à la question. Idem pour Renaud. Vos postures a base d'argument d'autorité genre "tu l'utilises pas tu sais pas", "je m'en sers, ca marche", "OCaml c'est bien", c'est le genre de poudre aux yeux qu'on laissera aux politiques. Et n'allez pas penser que je suis réfractaire à la nouveauté, il y en a de très intéressantes, mais pas celle ci.

                  De plus, je ne suis pas fan du code que tu proposes, tester les limites des types en dynamique alors que le type ne changera pas en runtime tout ca parce que c'est "mieux" d'écrire "auto counter" que "int counter", ca n'a pas de sens (et je ne parle même pas des performances…).

                  • [^] # Re: Inférence de types

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

                    comment tu connais le type de retour de getNextValue()

                    Tu passe la souris dessus (ou utilise le raccourci clavier idoine) dans ton IDE et il te le dit?

                    Comme le typage est statique, l'environnement de développement (comme le compilateur) peut déterminer sans équivoque (car s'il y a équivoque, ça ne compile pas) les types et les proposer (avec la documentation, si elle existe) de façon contextuelles, si tu en as besoin.

                  • [^] # Re: Inférence de types

                    Posté par . Évalué à 10 (+9/-0). Dernière modification le 10/11/18 à 14:40.

                    comment tu connais le type de retour de getNextValue()

                    On ne le connaît pas exactement mais on s'en fout, on sait que c'est un entier et ça suffit. On a géré le cas du dépassement de manière générique et donc, si jamais le type de retour de getNextValue() change, on aura rien à changer ici. Que cet entier soit un int, un size_t, un uint16_t ou autre chose, ça ne change rien, au code et à sa logique.

                    De plus, je ne suis pas fan du code que tu proposes, tester les limites des types en dynamique

                    Rien n'est dynamique ici, decltype(counter) est évalué à la compilation et max() est constexpr donc sera également évaluée à la compilation.

                    et je ne parle même pas des performances…

                    Niveau performance, ça ne changera rien et niveau typage, c'est plus générique donc plus maintenable que si tu avais spécifié le type directement.

                    • [^] # Re: Inférence de types

                      Posté par (page perso) . Évalué à -8 (+1/-10).

                      On ne le connaît pas exactement mais on s'en fout

                      C'est vrai, après tout, tant que ça compile… :) dans la même veine j'ai
                      "et si ça plante, on reboot."
                      et
                      "ici ça marche"
                      et
                      "j'écris du code c'est pas mon job de m'occuper des bugs"

                      Avec des raisonnements comme ça, c'est sûr qu'on fait des logiciels de qualité.

                      • [^] # Re: Inférence de types

                        Posté par . Évalué à 8 (+7/-1). Dernière modification le 10/11/18 à 15:44.

                        Tu voulais une réponse, tu l'as. Elle ne te plaît pas, tant pis pour toi. Des tas de gens utilisent l'inférence de type depuis des lustres sans que le monde ne se soit écroulé pour autant. Si tu ne veux pas l'utiliser, tu n'es pas obligé, personne ne va venir derrière toi. On t'a expliqué tous les avantages à utiliser auto en C++ ou var en Java, maintenant on ne peut plus rien faire. Tu ne veux pas être convaincu, tu veux juste râler.

                      • [^] # Re: Inférence de types

                        Posté par . Évalué à 7 (+6/-0). Dernière modification le 13/11/18 à 13:57.

                        Je pourrais avoir exactement le même raisonnement et te demander ou est ce que ta variable est stocké sur la pile (relativement au pointeur de pile) par ce que je viens du monde assembleur et que c'est hyper important. Mais tu me répondrait avec raison que je n'ai pas besoin de cette information. Si j'en ai réellement besoin, c'est plus probablement que j'ai un autre problème dans mon programme, ou qu'il faut que je l'explicite dans ce cas précis.

                        bépo powered

                    • [^] # Re: Inférence de types

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

                      Franchement, aller faire une ligne if pour vérifier que ça marchera parce qu’on a la flemme de le savoir avant, je n’y crois pas. La personne qui a codé tout en auto, il ne fera pas le test, il espère que ça marche.

                      En cas de bug, pour un débordement ou autre, si tu dois remonter récursivement 10 déclarations automatiques, tu vas galérer et te retrouver à refaire tout ton typage sur un bout de papier pour comprendre. Alors qu’avec un peu de rigueur, c’est plus simple. auto sur un itérateur, je suis pour et je suis d’accord que ça aide, mais de là à généraliser…

                      En Haskell, je préfère tenter des définitions de type, pour pas avoir de surprise. Mais grâce aux classes de types, on peut déclarer toutes les fonctions le plus générique possible.

                      • [^] # Re: Inférence de types

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

                        En cas de bug, pour un débordement ou autre, si tu dois remonter récursivement 10 déclarations automatiques, tu vas galérer et te retrouver à refaire tout ton typage sur un bout de papier pour comprendre. Alors qu’avec un peu de rigueur, c’est plus simple. auto sur un itérateur, je suis pour et je suis d’accord que ça aide, mais de là à généraliser…

                        Je ne sais pas de quoi tu parle, mais pas de la réalité. Tu n'a pas le droit de retourner var (c'est de l'inférence de type « locale »). Donc tu n'a pas à remonter quoi que ce soit.

                        En C++, c'est possible depuis le C++14 (avant ça n'est pas véritablement utilisable), mais je ne vois pas l'intérêt.

                  • [^] # Re: Inférence de types

                    Posté par . Évalué à 4 (+2/-0). Dernière modification le 11/11/18 à 18:38.

                    La question que je pose depuis le début […] [c'est] comment tu connais le type de retour de getNextValue() sans aller voir sa déclaration (dans un autre fichier ou des centaines de lignes plus loin) ?

                    La réponse:
                    - souvent c'est évident selon le contexte (par exemple ce qui se trouve à droite est le constructeur d'une classe)
                    - quand ça ne l'est pas, je peux demander à mon éditeur de me donner le type (chez moi c'est C-c C-t, chacun ses goûts)

                    Si tu refuses d'avoir des outils pour t'aider dans ta programmation, tu as toujours le loisir de râler sur tes collègues, pendant la relecture de code, quand ils utilisent un auto/var à un endroit où ce n'est pas clair pour toi.

                    De plus, je ne suis pas fan du code que tu proposes, tester les limites des types en dynamique alors que le type ne changera pas en runtime tout ca parce que c'est "mieux" d'écrire "auto counter" que "int counter", ca n'a pas de sens (et je ne parle même pas des performances…).

                    Je pense qu'aucun test dynamique n'est fait dans le code C++ qu'il montre. Par ailleurs, Java est un langage qui fait un test de typage dynamique à chaque écriture dans un tableau, donc je dirais qu'il faut se détendre un peu.

              • [^] # Re: Inférence de types

                Posté par (page perso) . Évalué à 9 (+6/-0). Dernière modification le 10/11/18 à 01:15.

                J'ai l'impression que par lisible tu veux dire "rapide à lire".

                Non, je veux dire que je peux aussi facilement retrouver l'information qui m'intéresse comme le nom de la variable de l'élément itéré. Cette information n'est pas masquée par le bruit de l'imposant nom du type.

                Par lisible, j’entends compréhensible, et ceci ne l'est pas, car si la définition de "conteneur" est 40 lignes plus haut ou si c'est lui même un "auto" machin, le code n'est PAS compréhensible. Même pas en rêve avec tes technos dites "modernes" et les bonnes pratiques de mamie.

                Et pourtant en pratique cela fonctionne très bien. Déjà quand tu évites d'avoir des fonctions de 20 kilomètres de long et que tu as un environnement de travail efficace, ça pose de suite moins de problèmes théoriques. Je rappelle qu'en programmation une bonne pratique est d'avoir des portions de codes réutilisables, réduites (des fonctions donc courtes) au maximum ce qui implique peu de variables dans le même scope. Cela allège grandement ta charge de travail pour ta mémoire

                Enfin c'est amusant, moi je m'en suis servi, comme d'autres, dans des projets conséquents sans soucis et toi tu juges apparemment sans t'en être servi dans un langage avec un typage fort. Je te propose de tester en pratique avant d'émettre des de telles positions.

                Car bon, inférence de types = JavaScript = merde, on a vu mieux comme argumentation. Je ne suis pas fan du JavaScript, mais ses défauts ne sont pas liés à son inférence de type (ou disons que son inférence de types gêne car il est faiblement typé).

                Je ne dis pas que le compilateur va se tromper, c'est le programmeur qui ne peux pas s'y retrouver (à moins que ta "bonne pratique" c'est frde nommer les variables/les paramètres en incluant le type.

                Un bon nommage peut largement induire aussi le type de l'objet. Tu te doutes que "i" dans une boucle est un entier, que "it" dans le même cas est un itérateur, que "name" est une chaîne de caractères. Cela aide à la compréhension et évite de devoir se préoccuper du type exact.

                Car bon, même avec le type explicite à la déclaration, si tu es à 40 lignes plus bas et que tu as un doute, tu dois revenir en arrière pour vérifier. Ce n'est pas mieux.

                c'est casse gueule au possible et complètement inutile (et c'est pas parce que Rust le fait que c'est bien).

                Dixit le gars qui n'a jamais expérimenté tout cela au quotidien. Amusant.

                Sinon l'inférence de types est également nécessaire pour exploiter les lambdas anonymes. Mais là encore, je sens que c'est une fonctionnalité sans intérêt pour toi.

                (notons au passage que les gros projets Javascript migrent vers Cleartype pour justement avoir du code "lisible" et plus robuste que la loterie à base de "let" et "var")

                Tu comprendras quand que le soucis du JavaScript n'est pas l'inférence de types en lui même ?

                Le soucis de JavaScript est d'être faiblement typé, avec inférence de types et une faible rigueur de comportements. C'est ce cocktail qui est explosif. Cela tombe bien, beaucoup de langages modernes ont l'inférence de types pour ses avantages, mais ont un typage fort et un comportement rigoureux pour éviter ses effets de bord. Preuve qu'on peut avoir tout cela en même temps sans soucis.

                Car ce cocktail, c'est lui qui fait que ta fonction attend un paramètre d'un certain type, que finalement tu lui envoies une valeur d'un autre type et qu'une conversion implicite absurde est appliquée. Le tout au runtime histoire que cela se détecte qu'en vol. C'est vraiment cette combinaison qui rend la maintenance d'un code JavaScript délicat.

                Avec l'inférence de types et un typage fort, cela n'arrive pas. Tu n'as pas le type explicité partout, mais en cas de conflit le compilateur te prévient et tu résous cela en corrigeant le bogue ou en créant une conversion explicite du type A vers B si cela a un sens. Donc en cas de refactorisation tu es gagnant, tu peux changer le type d'une variable (passer de int à long, améliorer le nom de ta classe) sans devoir tout ressaisir (il y aura un peu de travail évidemment mais moins). Et tu allèges considérablement le code d'informations qui sont essentiellement redondantes ce qui rend le code plus aéré et donc lisible.

                • [^] # Re: Inférence de types

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

                  Je trouve que les discussions sur les types sont plus claires avec le découpage suivant (c'est un peu plus facile avec un tableau, mais tant pis…) :

                  • Typage faible / fort : est-ce que les types sont bien délimités (java, lisp) ou bien on a des conversions implicites qui permettent d'interpréter les valeurs autrement (c, javascript). En réalité il s'agirait plutôt d'une distribution continue des langages sur un axe allant de l'un à l'autre.
                  • Typage statique / dynamique : est-ce que les variable ont un type, connu au moment de la compilation (java, c), ou bien les variables n'ont pas de type mais les valeurs oui, noté dedans lors de l'exécution (lisp, javascript).
                  • Typage explicite / implicite (dans le cas du typage statique uniquement) : le type est-il indiqué explicitement par le développeur (C, java avant) ou bien peut-il être déduit la plupart du temps par le compilateur (ocaml).

                  Dans ce cas :

                  • ocaml est fortement typé, statiquement et implicitement.
                  • java ou C++ étaient fortement typés, statiquement et explicitement, mais pour partie (variables locales) ça devient plus implicitement.
                  • C est faiblement typé, statiquement et explicitement.
                  • lisp est fortement typé, dynamiquement.
                  • javascript est faiblement typé, dynamiquement.

                  L'inférence (une déduction ici) de type étant utilisée par le compilateur pour le typage statique et implicite (le compilateur doit connaître les types des variables mais les devine souvent tout seul).

                  Le soucis de JavaScript est d'être faiblement typé, avec inférence de types et une faible rigueur de comportements.

                  Du coup plutôt faiblement typé, dynamiquement, et donc sans inférence de type (les valeurs ont un type au moment de l'exécution, mais pas les variables, qui peuvent simplement contenir n'importe quelle valeur). En tout cas autant que je comprenne javascript, je ne suis pas spécialiste (et il se peut que le compilateur puisse essayer de deviner les types de certaines valeurs et variables pour des optimisations, je ne sais pas, mais c'est encore autre chose il me semble).

                  Et le pire de tout ça est à mon goût le typage faible (à rapprocher sans doute de que tu décrivais comme "faible rigueur de comportements") : le fait qu'il bricole avec les valeurs pour un certain nombre d'opérations, ce qui peut donner des résultats parfois inattendus. On peut voir ça comme un héritage du langage, qui manipulait à la base des données faiblement typées (des valeurs stockées dans des pages html, représentées souvent sous forme de chaînes).

          • [^] # Re: Inférence de types

            Posté par . Évalué à 4 (+2/-0). Dernière modification le 10/11/18 à 00:49.

            Ce n'est pas parce que le compilateur connait le type que le "programmeur" oui.

            Bah si il le connaît. ;-)

            Je n'écris presque jamais aucune annotation de type en OCaml (je n'écris les types que dans les interfaces de mes modules, ou quand le compilateur ne peut inférer le type) et je n'ai aucun problème pour connaître le type de mes données à la lecture du code. C'est pareil pour les codes que je lis, et c'est le cas de tous le programmeurs habitués au langage. Les seuls que je vois écrire des annotations, c'est ceux qui viennent des langages avec annotations explicites (comme Java ou C++).

            Cela étant, contrairement à ce qu'a dit Renault, ce n'est pas spécialement moderne comme caractéristique d'un langage : le système date du début des années 80.

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

          • [^] # Re: Inférence de types

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

            Suffit de passer ce "var" magique à une API qui a des méthodes de même nom mais avec des arguments de types différents pour être sûr d'avoir du code Java du niveau d'un code javascript…

            Je ne suis pas sûr de comprendre ce que tu as voulu dire par là. Mais juste pour être certains qu'on parle de la même chose : le mot-clef var fait de l'inférence de type pas du Duck Typing. La variable a un type bien défini, nommé, et en général identique à celui de la valeur de l'expression affectée à la variable. La variable n'est pas du tout un argument valide pour une méthode qui attendrait un type différent mais avec des méthodes/propriétés de même nom.

            Pour des exemples d'usage, personnellement je m'en sers quasiment partout dans mes déclarations de variables initialisées dans des méthodes :

            Je trouve que :

            var myMap = new HashMap<String, String>();

            est plus lisible que :

            HashMap<String, String> myMap = new HashMap<String, String>();

            Ça évite de se répéter et il n'y a aucune perte d'information disponible visuellement dans cette situation.

            Ensuite pour les retours de fonctions, le type n'est pas forcément l'information la plus importante. Des fois c'est la sémantique qui est importante, pas l'implémentation.
            Dans l'exemple que tu donnes plus bas où on fait des calculs sur des entiers avec risque de dépassement, clairement le type est important et tu as intérêt à le rendre visible. On est dans un cas où l'implémentation est importante.
            Par contre, si tu écris du code métier avec beaucoup d'abstraction, très rapidement tu te moques du type, le point important pour le lecteur est qu'est-ce qu'on veut faire, pas comment on le fait.

            var myContacts = GetContacts();
            for (var contact : myContacts){
               Display(contact.Name);
            }

            L'exemple ci-dessus est parfaitement compréhensible, peu importe le type de myContacts. Rajouter le type permettrait d'expliciter le comment on gère ça sous le capot. Mais si le lecteur est intéressé par le comportement de la méthode et non par son implémentation, c'est juste de la pollution visuelle. Contrairement à ce que tu insinues dans un de tes commentaires, ça n'a rien à voir avec le fait de se contenter d'un code qui compile, c'est simplement une question de qu'est-ce qu'on veut mettre en valeur dans son code.

            • [^] # Re: Inférence de types

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

              1

              Je suis tout à fait d'accord avec toi quand tu considère que sur la ligne (et uniquement celle ci)

              var myMap = new HashMap<String, String>();

              il n'y a pas de perte d'information, mais je ne vois pas le gain par rapport à :

              HashMap<String, String> myMap = new HashMap<>();

              car ce n'est ni plus substantiellement long à écrire, ni à lire, ni à comprendre, ni à stocker.
              Donc, pour moi, à ce niveau, 'var' c'est inutile.

              2

              Sur ton exemple :

              var myContacts = getContacts();
              for (var contact : myContacts){
                 display(contact.name);
              }

              le problème est masqué car le var myContacts = getContacts(); est juste au dessus de ta boucle.
              Intercales un ou deux écrans de code et tu n'a plus le contexte.
              Pour pas te perdre, tu vas avoir tendance à généraliser ce que tu fais dans ton exemple, cad nommer tes variables en fonction de leurs types.

              Par contre, si tu écris du code métier avec beaucoup d'abstraction, très rapidement tu te moques du type

              J'aurais tendance à dire que dans ces cas, les classes manipulées implémentent souvent de multiples interfaces et qu'il est donc important de bien savoir ce que l'on manipule.
              Peut être un exemple simple et court qui te parleras plus :

              void store(String s, int nb) throws IOException {
                var stream = getCurrentStream();
                for(int i=0;i<nb;i++){
                    stream.write(s, 0, s.length());
                }
              }

              Il se passe quoi? On écrit un fichier? On écrit sur une socket? On a flingué les perfs ? Le code est correct?

              Par contre si tu écris :

              void store(String s, int nb) throws IOException {
                FileOutput stream = getCurrentStream();
                for(int i=0;i<nb;i++){
                    stream.write(s, 0, s.length());
                }
              }

              C'est tout de suite plus clair, on écrit dans un fichier
              et, en plus, de la pire des façons.

              On écrira donc :

              void store(String s, int nb) throws IOException {
                FileOutput stream = getCurrentStream();
                BufferedOutputStream b = new BufferedOutputStream(stream);
                for(int i=0;i<nb;i++){
                    b.write(s, 0, s.length());
                }
                b.flush();
              }

              Donc, pour moi, à ce niveau, 'var' c'est casse gueule.

              Désolé de devoir me contenter de code de quelques lignes pour la lisibilité, les vrais problèmes apparaissent avec la complexité et la taille. Bien que je travaille en équipe sur un "codebase" d'un demi million de lignes Java depuis des années, je comprend très bien que "var" puisse être attrayant pour de tout petits projets, mais même dans ce cas j'ai du mal a y voir un véritable gain.

              • [^] # Re: Inférence de types

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

                1

                car ce n'est ni plus substantiellement long à écrire, ni à lire, ni à comprendre, ni à stocker.
                Donc, pour moi, à ce niveau, 'var' c'est inutile.

                Le gain principal c'est qu'il n'y a pas de répétition, ça fait moins d'information à lire / à analyser. C'est pas énorme, mais on n'a jamais dit que ça révolutionnait le monde.

                2

                Désolé si ce n'était pas clair dans mon commentaire précédent, mais je n'ai jamais dit que c'était bien de mettre des var partout. Je dis juste qu'il y a des fois, c'est ce que fait le code qui est important, pas comment il le fait. Ce n'est clairement pas le cas de tous les codes.

                le problème est masqué car le var myContacts = getContacts(); est juste au dessus de ta boucle.
                Intercales un ou deux écrans de code et tu n'a plus le contexte.

                Je ne vois pas ce qu'un écran ou deux changeraient. Au pire, tu ne sais plus que ça n'a pas été initialisé avec la méthode getContacts() mais ça n'a aucun rapport avec le typage.

                Pour pas te perdre, tu vas avoir tendance à généraliser ce que tu fais dans ton exemple, cad nommer tes variables en fonction de leurs types.

                Mes variables sont nommées en fonction de ce qu'elles contiennent, pas en fonction du type. Qu'est-ce qui te fait dire ça ?

                Peut être un exemple simple et court qui te parleras plus :

                On est d'accord dans ton exemple, l'implémentation sous-jacente est importante, c'est donc un cas où il ne faut pas utiliser var.

                Pour le formuler autrement : développer c'est empiler des abstractions les une au dessus des autres. Le but c'est de choisir le niveau d'abstraction qui te permet d'être le plus efficace. Et ce n'est pas le niveau qui donne le plus d'information qui est le meilleur (sinon on serait tous en train de faire de la mécanique quantique pour comprendre notre code), c'est celui qui donne l'information la plus pertinente. Et ça ça dépend du contexte. Par exemple, je reprends ma variable contacts.
                Si tu as besoin de trier une liste de contacts avec de grosses contraintes de performances, tu as intérêt à savoir précisément le type mais aussi à avoir des statistiques sur le contenu et le contexte d'appel. Tu as besoin de beaucoup d'informations. Tu vas écrire ta fonction avec un typage explicite et pas mal de commentaires pour documenter tout ce que le langage ne te permet pas d'écrire formellement.
                Si tu as besoin d'afficher le nom de chaque contact, tu te moques de tout ça, tu ne le précises pas. Et si tu devais lire une telle fonction mais avec toutes ces informations, tu insulterais certainement le développeur qui te fait perdre ton temps avec des détails inutiles. Le type est juste un de ces détails inutiles parmi d'autres. La différence c'est que c'est une information apportée par le langage, non par les commentaires et que jusqu'à cette version il n'y avait donc aucun moyen de dire "le type de cette variable, c'est pas ce qu'il y a de plus important pour comprendre le code".

                • [^] # Re: Inférence de types

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

                  He ben dites donc…

                  Alors ce qu'est var en java. Ce n'est pas de l'inférence de type. Ils ont appelé ça comme ça parce que ça fait cool et parce que d'autres langages l'appellent aussi comme ça, mais ça n'en est pas. Quand on parle d'inférence de type locale, ça veut juste dire qu'on a ajouté du sucre syntaxique à ton langage rien de plus. Je suis pas allé voir comment ils ont implémenté ça, mais une inférence de type locale, c'est presque un contre sens tellement ça ne sert à rien comparé à ce à quoi sert l'inférence de type (la vraie).

                  Tout ça pour dire que cette évolution ne touche strictement rien à la sémantique de java. Donc les comparaisons avec js (qui n'a aucune inférence de type, ça ne sert à rien avec un typage faible) ou Ocaml (c'est sacrément utile quand on manipule des types algébriques) n'ont pas un grand intérêt. Pour vous dire c'est tellement faible comme fonctionnalité que vous pouvez déjà l'utiliser avec des versions plus vielles de java et lombok.

                  Reste que oui ça peut faire débat et Java 10 étant sorti il y a un certain temps, vous inquiétez pas la communauté a déjà trollé gras dessus. Ça a conduit la création de guidelines par OpenJDK. Vous avez une explication plus simple et en français ici : https://java.developpez.com/actu/196738/Bonnes-pratiques-pour-l-usage-du-mot-cle-var-introduit-dans-Java-10-un-billet-de-blog-de-Fabrice-Bouye/

    • [^] # Re: Inférence de types

      Posté par . Évalué à 5 (+3/-0). Dernière modification le 09/11/18 à 21:46.

      On pouvait déjà faire de l'inférence de type depuis java 8, avec les lambda, ils ont simplement étendu les cas où le compilateur va appliquer cette inférence.

      • [^] # Re: Inférence de types

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

        Et pour être complet, j'ajoute que ça avait été commencé dans java 7, avec l'opérateur "diamant", qui permet d'écrire:

        List<String> l=new ArrayList<>()

        à la place de

        List<String> l=new ArrayList<String>()

        Mais là, ça ne concerne que les génériques, qui sont de toute façon "oubliés" après compilation.

        • [^] # Re: Inférence de types

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

          Peut être qu'un jour, ils se bougeront chez Oracle et changerons le bytecode pour inclure les génériques…(dont l'implémentation actuelle n'est pas géniale).
          Le "diamant" c'est pas la plus brillante des idées.
          On aurait aimé passer de

          List<String> l=new ArrayList<String>();

          à

          List<String> l=new ArrayList();

          Mais j'ai l'impression que c'est comme pouvoir synchroniser les constructeurs pour éviter de gérer les barrières mémoires avec des accès à des "final"…. c'est pas pour demain…

    • [^] # Re: Inférence de types

      Posté par (page perso) . Évalué à 5 (+4/-1). Dernière modification le 10/11/18 à 15:20.

      15 ans de retard.

      Sur qui? Python, Ruby, PHP, Javascript & co n'ont toujours pas de typage digne de ce nom!

      Incubez l'excellence sur https://linuxfr.org/board/

      • [^] # Re: Inférence de types

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

        Mais Perl6 si ;-)

      • [^] # Re: Inférence de types

        Posté par . Évalué à 2 (+0/-0). Dernière modification le 13/11/18 à 20:50.

        Sur C#

        Les génériques, LINQ, PLINQ, var, async/await, la TPL… Tout ça c'était déjà en C# en 2010 voire 2004. ;-)

        "Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)

        • [^] # Re: Inférence de types

          Posté par (page perso) . Évalué à 3 (+2/-1). Dernière modification le 14/11/18 à 00:07.

          Du coup plus personne n'utilise C# !

          Incubez l'excellence sur https://linuxfr.org/board/

          • [^] # Re: Inférence de types

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

            Mono utilise C#
            Xamarin utilise C#
            Unity utilise C#
            Plein de monde utilise .NET (et par là même, C#).

            "Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)

  • # On pourrait écrire

    Posté par (page perso) . Évalué à 10 (+17/-0).

    var var = "Binks";

    ?

    Python 3 - Apprendre à programmer en Python avec PyZo et Jupyter Notebook → https://www.dunod.com/sciences-techniques/python-3

Envoyer un commentaire

Suivre le flux des commentaires

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