Journal Non, l'inférence de types n'est pas du typage faible. Oui, elle rend les programmes plus lisibles

Posté par .
Tags : aucun
40
21
nov.
2018

En lisant le journal sur la sortie de JDK 10 qui mentionnait notamment l'ajout d'une inférence de type sommaire, j'ai vu qu'il y avait une mauvaise compréhension et un fort rejet de ce concept.

Ça m'a bien surpris car je trouve au contraire l'inférence de types géniale : non seulement elle permet de bénéficier d'un langage fortement typé sans taper des types à rallonge partout, mais aussi elle rend la lecture des programmes bien plus facile (et je ne parle pas de la puissance de l'inférence de types comme implémentée dans Haskell notamment).

D'où ce court journal pour, j'espère, convaincre les réticents que c'est bien.

Non, l'inférence de type n'est pas du typage faible

Illustrons la différence par deux programmes.

Tout d'abord regardons un programme avec du typage faible :

function getTempCelsius() {
    return "12°C"
}

let fahrenheit = getTempCelcius() * 9/5 + 32

Et voilà l'équivalent avec de l'inférence de types :

function getTempCelsius() {
    return "12°C"
}

let fahrenheit = getTempCelsius() * 9/5 + 32

Beaucoup plus clair n'est-ce pas ?

En fait, un des programmes est en JavaScript (typage faible), et l'autre en TypeScript (typage largement plus fort). Dans le 1er cas, le programme ne retourne aucune erreur, que ce soit à la compilation ou à l'exécution, mais la variable fahrenheit prend la valeur NaN. Dans le 2e cas, on a un message d'erreur à la compilation qui indique qu'on tente de multiplier une chaine de caractères et un entier.

Maintenant regardons l'équivalent typé statiquement (en java) :

String getTempCelsius() {
    return "12°C"
}

int fahrenheit = getTempCelsius() * 9/5 + 32

On a tapé deux types en plus, mais on a la même erreur qu'en TypeScript, rien de plus, rien de moins.

Bref, vu de loin, un programme dans un langage utilisant l'inférence de type ressemble à celui dans un langage faiblement typé, mais en réalité on a des garanties très proches d'un langage fortement typé.

Oui, l'inférence de types rend les programmes plus lisibles

Il y a deux façons de lire les programmes :

  • Sans se poser trop de questions, pour comprendre comment ça marche. Par exemple, si on lit let fahrenheit = getTempCelsius() * 9/5 + 32, on devine facilement que c'est une conversion de Celsius en Fahrenheit sans avoir besoin des informations de type. Et pourvu que le nom des variables et des fonctions ne soit pas trop mauvais, ça marche pour n'importe quel programme.

  • En se posant des questions, par exemple pour déboguer un programme. Dans ce cas là on a besoin des informations de type. Par exemple, dans l'exemple de la conversion de Celsius en Fahrenheit, on peut se demander quel type retourne la fonction, est-ce que les nombres sont des entiers, quel est le type de la variable fahrenheit.

Avec un éditeur ad hoc, on peut lire les programmes de la première façon sans être pollué par des informations de type à rallonge et regarder les informations de type quand c'est utile.

Certes, avec notre exemple des degrés, avoir des types explicites ou non ne change pas grand chose à la longueur du programme et à la lisibilité, mais quand on commence à avoir des types plus longs à écrire comme Map<String, Map<String, String>>, les informations de type commencent vite à prendre plus de place que la logique du programme, surtout si on écrit des fonctions courtes comme c'est généralement recommandé.

Bref

J'ai répondu aux deux points qui m'agaçaient le plus dans les commentaires du journal sur le JDK 10, j'espère que ça vous a autant servi que ça m'a dé-aggacé :)

Avant de finir, sachez juste que les deux points de ce journal sont relativement cosmétiques et qu'utiliser un langage avec un système de types puissant est extrêmement utile et va bien au delà d'éviter de taper des types.

  • # Définition implicites ?

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

    Si j'en crois les exemples du journal, et que j'ai bien compris, l'inference de type est une forme de typage implicite (le type de la valeur retournée désigne le type que la fonction retournera). D'un point de vue de la lecture c un peu gênant: ça nécessite d'aller voir l'implémentation pour savoir quel type la fonction retourne. En plus, on risque de perdre une info précieuse lors de la compilation: le message d'erreur se trouvera a l'appel de la fonction et non a la définition/implémentation de celle-ci. A moins que quelque chose m'échappe.

    • [^] # Re: Définition implicites ?

      Posté par (page perso) . Évalué à 10 (+13/-1). Dernière modification le 21/11/18 à 16:40.

      En effet, quelque chose t'échappe:

      • premièrement, ça ne change strictement rien à la compilation, la résolution du type est un peu plus complexe, mais une fois résolu, l'erreur sera strictement identique (sémantiquement, que tu exprimes ton type ou non, si quelque chose d'autre l'exprime déjà, c'est juste une redondance, ni plus ni moins),

      • ça freine pas la lecture, n'importe quel éditeur digne de ce nom de plus fait déjà l'inférence pour toi et t'indique le type sans que t'ai rien de plus à ouvrir.

    • [^] # Re: Définition implicites ?

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

      le message d'erreur se trouvera a l'appel de la fonction et non a la définition/implémentation de celle-ci

      Effectivement, comme tu ne tapes pas le type de retour, tu n'auras pas de message d'erreur si le type de retour n'est pas celui que tu t'imaginais dans ta tête (tant que tu n'auras pas écrit une autre fonction qui appelle la 1ère).

      Cependant ce n'est pas un problème :

      • Ce que je fais (et je ne dois pas être le seul), c'est de regarder le type que retourne la fonction dans l'éditeur, et modifier la fonction jusqu'à ce qu'elle retourne le type voulu. Ou alors ne pas la modifier et garder le type retourné (ça arrive très souvent que le type "naturel" soit mieux que ce que je pensais vouloir à l'origine)
      • Quand tu écris une autre fonction qui appelle la première, peu importe que le message d'erreur apparaisse au niveau de la fonction appelée ou appelante : tu dois de toute façon décider si le mieux est de modifier la fonction appelée ou appelante (par exemple dans l'exemple du journal, si tu préfères que la fonction getTemp retourne un nombre ou si tu vas te débrouiller avec la chaine retournée).
      • [^] # Re: Définition implicites ?

        Posté par . Évalué à 2 (+3/-3). Dernière modification le 21/11/18 à 20:14.

        Quand tu écris une autre fonction qui appelle la première, peu importe que le message d'erreur apparaisse au niveau de la fonction appelée ou appelante : tu dois de toute façon décider si le mieux est de modifier la fonction appelée ou appelante (par exemple dans l'exemple du journal, si tu préfères que la fonction getTemp retourne un nombre ou si tu vas te débrouiller avec la chaine retournée).

        Bah, je truve quand même bien pratique que l'erreur me soit retournée lors de la compilation de la fonction appelée. Si je compile une fonction pour qu'elle retourne un int et que je retourne un char dans le corps de la fonction, je préfère le savoir avant.

        Pour ma part, avoir un langage à typage statique qui fait ce genre de chose me paraît étrange (d'ailleurs j'ai l'impression que le posteur initial confond les notions de typage statique/dynamique et typage fort/pas fort).

        • [^] # Re: Définition implicites ?

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

          Bah, je truve quand même bien pratique que l'erreur me soit retournée lors de la compilation de la fonction appelée. Si je compile une fonction pour qu'elle retourne un int et que je retourne un char dans le corps de la fonction, je préfère le savoir avant.

          Chez nous une des règles pour Scala (qui a de l'inférence de type, mais qui ne fais pas non plus de miracles et arrive parfois à se perdre ou ne pas générer un "bon" type de retour) c'est "variable ou méthode publique" => "type explicite". Par contre pour les variables et méthodes internes / privées, au contraire on encourage à utiliser l'inférence, ça rend le code plus concis ; et de toute façon comme c'est forcément utilisé dans le même projet (ou alors c'est du code mort donc ça n'a pas lieu d'exister), le type inféré est donc forcément confronté à son appel et si bug il y a, il sera détecté à la compilation.

        • [^] # Re: Définition implicites ?

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

          Si je compile une fonction pour qu'elle retourne un int et que je retourne un char dans le corps de la fonction, je préfère le savoir avant.

          Si c'est la raison d'être de ta fonction c'est qu'on contrat donc tu l'explicate. Si c'est un internal de ton code l'expliciter est généralement superflux. Ça te fait te répéter sans gagner nécessairement en lisibilité. C'est ce que font les langages qui ont inclus récemment des lambdas par exemple.

          Ça te paraît étrange parce que ça vient généralement avec les types algébriques qui apportent potentiellement une grande expressivité au langage. Mais cette sophistication peut rendre rébarbatif le fait d'expliciter les types.

          • [^] # Re: Définition implicites ?

            Posté par . Évalué à 9 (+7/-0). Dernière modification le 22/11/18 à 08:17.

            Un superflux, c’est un flux avec une cape et le slip par dessus son pantalon ?

            (Pour les histoires de contrat qu’il faut « explicater », j’imagine que c’est le correcteur automatique qui a rajouté sa touche personnelle ?)

      • [^] # Re: Définition implicites ?

        Posté par . Évalué à 9 (+7/-0). Dernière modification le 21/11/18 à 22:05.

        Effectivement, comme tu ne tapes pas le type de retour

        Inference de type n'implique pas de ne pas être explicite sur les types de retour des fonctions. Et surtout, ca empêche pas de nommer ses fonctions décemment pour qu'on comprendre ce qu'elle fait et ce qu'elle retourne.
        Ca veut juste dire que le compilateur n'a pas forcement besoin que tu explicites le type dans certains contextes. En pratique, être implicite sur les types de retour des fonctions, c'est une assez mauvaise pratique: ca force a lire l'implementation pour savoir ce qui est retourne, et si le language supporte des types avancés, genre union ou autres, ca peut t'emmener loin quand tu debugges.

        Typiquement, ca c'est bien:

        /// C'est assez clair ici que name() retourne une string, meme sans regarder le type de retour. Non pas que ca soit important ici, mais pour 
        /// le code appelant, on devine aisément que le nom va être une String
        public func name() -> String {
            // implementation
        }
        
        /// comme dit au dessus, c'est assez evident que name est une String, donc pas la peine de se répéter
        let a = name().lowercased()

        Et typiquement ca c'est mal:

        /// Nom de function alakon, bon courage pour savoir ce que c'est cense faire
        public func foo() {
            // implementation, de preference bien longue avec des if imbriqués et des early return
        }
        
        /// bonne fete des morts pour deviner ce qu'il se passe ici.
        let a = foo()

        Linuxfr, le portail francais du logiciel libre et du neo nazisme.

    • [^] # Re: Définition implicites ?

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

      Pour moi, l'inférence de type oblige à avoir un bon IDE. Ce n'est pas, à mes yeux, un gros défaut : je ne conçois pas de bosser sans un bon IDE.

      L'IDE va afficher une erreur lors de l'implémentation s'il y en a une (en Java, la même fonction ne peut pas renvoyer de temps en temps un String et de temps en temps un int), mais ne pourra rien faire s'il n'y en a pas (en Python, la même fonction peut renvoyer différents types).

      Dans le second cas, tu auras ton avertissement lors de l'utilisation, mais je ne vois pas trop comment on pourrait faire autrement.

      En Python, PyCharm fait de l'inférence mais tu peux en plus spécifier les types (en entrée et en sortie), auquel cas tu auras ton avertissement dès l'implémentation.

      • [^] # Re: Définition implicites ?

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

        Bof, Ne pas se soucier de la lisibilité du code juste parce qu'un IDE fait le boulot est à mon avis une très mauvaise idée.

        Il y a des cas ou justement on n'a pas de bon IDE sous la main : je pense par exemple lorsqu'on veut merger des pull request sous GitHub, mais il ya d'autres cas de figure (exemples de code dans un bouquin par exemple).

        De plus rien ne te dit que tu auras accès au code source de la méthode que tu appelles au moment ou tu vas éditer ton propre code.

        (en Java, la même fonction ne peut pas renvoyer de temps en temps un String et de temps en temps un int)

        Ca fait longtemps que j'ai plus fait de POO, mais il me semble que le polymorphisme permet de le faire …. ou alors il ne prend en compte que les paramètres de la fonction ? (faudra que je m'y remette un de ces 4).

        • [^] # Re: Définition implicites ?

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

          Il y a des cas ou justement on n'a pas de bon IDE sous la main : je pense par exemple lorsqu'on veut merger des pull request sous GitHub

          Tu merge du code non testé, si ce n'est automatiquement au moins manuellement (donc sur un poste à toi où tu auras checkout la branche et donc tu peux la review dans un outil correct) ? C'est pas à l'inférence de type qu'il faut s'en prendre.

          • [^] # Re: Définition implicites ?

            Posté par . Évalué à 4 (+3/-1). Dernière modification le 22/11/18 à 09:43.

            Je m'attendais à ce genre de réaction ( qui ne vaut même pas que j'y réponde, mais je le fais quand même). Je ne suis pas forcément la personne qui merge, mais il est possible que quelqu'un me demande mon avis sur un bout de code. Dans ce cas, ne pas devoirsortir l'artillerie lourde (IDE qui prend toute la RAM + clone + le reste), ça peut être bien pratique (le context switch par rapport à ce que je suis en train de faire est moins pénalisant).

        • [^] # Re: Définition implicites ?

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

          Ca fait longtemps que j'ai plus fait de POO, mais il me semble que le polymorphisme permet de le faire …. ou alors il ne prend en compte que les paramètres de la fonction ? (faudra que je m'y remette un de ces 4).

          Ouh la y a un mélange de plein de trucs là.

          Tu peux carrément renvoyer une fois Int et une fois String, oui, mais ton type de retour devra correspondre aux deux cas et donc être leur ancêtre commun : Object ; pas très utile comme information de type.

          Par ailleurs par polymorphisme tu veux dire polymorphisme ad hoc, plus communément appelé surcharge dans le monde Java ? Faire deux fonctions "semblables" (mettons pour le moment, le même nom et les mêmes paramètres ; on verra plus tard que c'est faux mais c'est pour la démonstration) mais qui diffèrent par leur type de retour ? Du coup ça n'a plus rien à voir, c'est plus "la même fonction" dont on chercherait à spécifier un type de retour.

          Et c'était "faux" comme cas car de toute façon en Java la signature d'une fonction n'inclut pas le type de retour. Tu ne peux pas avoir deux fonctions à la signature identique (type des paramètres, nom… je ne sais plus dans quelle mesure les exceptions checked comptent, je crois que non, comme le type de retour) mais qui ne diffèrent que par le type de retour.

          • [^] # Re: Définition implicites ?

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

            Merci pour cette réponse argumentée et non méprisante (contrairement à la précédente réponse que tu as faite).

          • [^] # Re: Définition implicites ?

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

            Tu ne peux pas avoir deux fonctions à la signature identique (type des paramètres, nom… je ne sais plus dans quelle mesure les exceptions checked comptent, je crois que non, comme le type de retour) mais qui ne diffèrent que par le type de retour.

            Et la visibilité de la méthode aussi, non ?

            • [^] # Re: Définition implicites ?

              Posté par (page perso) . Évalué à 2 (+0/-0). Dernière modification le 22/11/18 à 17:48.

              Je crois que oui, ça ne "compte pas" dans la signature pour la surcharge, et tu ne peux pas la réduire, par contre l'augmenter oui. En tout cas c'est le cas en Scala, et vu qu'un des points de mon plan de carrière est "ne plus jamais écrire de Java" je compte pas aller vérifier pour Java :-D

              D'ailleurs tant qu'on y est sur la surcharge en Scala, un truc cool, tu peux avoir un def foo: String (une méthode sans paramètre et renvoyant une String) dans une classe parente, et la surcharger par override val foo = "bar" dans une classe fille si, dans cette classe fille, tu sais que c'est une constante et que ça n'a donc pas besoin d'être une méthode.

  • # Oui mais

    Posté par (page perso) . Évalué à 7 (+6/-1). Dernière modification le 21/11/18 à 16:54.

    Il se trouve qu'en Haskell ou en Elm (les deux langages en FP que j'ai pu pratiquer jusqu'à présent), on écrit tout de même les signatures des fonctions et donc les types des arguments, principalement parce que le code est destiné à être lu par des êtres humains, qui n'ont pas toujours le temps ou l'envie d'analyser le corps d'une fonction pour inférer eux-même les types "manuellement".

    Donc en pratique, quand on est un minimum rigoureux, on tape quand même les types. La bonne nouvelle, c'est que cette signature est séparée physiquement de la fonction (au moins par un retour chariot) et donc :

    • La fonction reste tout de même légère en tant que telle.
    • La signature se concentre uniquement sur les types des paramètres d'entrée et du retour sans être polluée par d'autres détails techniques, et ça se lit facilement et on peut même parfois savoir ce que fait la fonction sans lire la doc, juste en lisant la signature
    • [^] # Re: Oui mais

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

      Il se trouve qu'en Haskell ou en Elm (les deux langages en FP que j'ai pu pratiquer jusqu'à présent), on écrit tout de même les signatures des fonctions et donc les types des arguments

      En Haskell (je ne connais pas Elm) ce n'est pas nécessaire. Tu as d'ailleurs intérêt à ne pas le faire car cela te permet de te rendre compte que ta fonction est plus générique que ce que tu pensais en l'écrivant.

      principalement parce que le code est destiné à être lu par des êtres humains, qui n'ont pas toujours le temps ou l'envie d'analyser le corps d'une fonction pour inférer eux-même les types "manuellement".

      Elle est affichée au survol dans ton éditeur ou avec les commandes :info ou :type dans ghci.

      • [^] # Re: Oui mais

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

        En Haskell (je ne connais pas Elm) ce n'est pas nécessaire. Tu as d'ailleurs intérêt à ne pas le faire car cela te permet de te rendre compte que ta fonction est plus générique que ce que tu pensais en l'écrivant.

        Je ne suis pas un expert d'Haskell, mais ce que tu dis me surprend beaucoup, compte tenu qu'il est très simple d'écrire une signature de fonction aussi générique soit-elle. Au lieu d'utiliser Int ou String, tu utilises a par exemple.
        Bref, je ne sais pas d'où sort cette recommandation de ne pas écrire les signatures de fonction mais ça ne me semble pas un très très bon conseil.

        "
        Functions also have types. When writing our own functions, we can choose to give them an explicit type declaration. This is generally considered to be good practice except when writing very short functions.
        "

        (issu de http://learnyouahaskell.com/types-and-typeclasses)

        "
        It is considered good style to add a type signature to every top-level variable.
        "

        (issu de https://wiki.haskell.org/Type_signature)

        • [^] # Re: Oui mais

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

          il est très simple d'écrire une signature de fonction aussi générique soit-elle. Au lieu d'utiliser Int ou String, tu utilises a par exemple

          Ce n'est pas parce que c'est simple à écrire que c'est simple à remarquer (il y a par exemple le cas fréquent d'une fonction sur les chaînes de caractères qui s'appliquent souvent aussi à des tableaux, mais il y a énormément de cas moins évidents).

          Haskell est un des langages avec la meilleure inférence de types, c'est dommage de faire le boulot du compilateur à la main et en moins bien.

        • [^] # Re: Oui mais

          Posté par . Évalué à 5 (+4/-0). Dernière modification le 21/11/18 à 23:10.

          Dans l'absolu, ce n’est pas une mauvaise pratique de mettre les signatures car, pour moi au moins, elles font partie de la documentation.

          Cependant, selon le code sur lequel l’on travaille et pendant la phase « brouillon » du code, mettre systématiquement des signatures reviendrait à toujours rouler sur un vélo la tête dans le guidon. En effet, les besoins immédiats ont tendance à faire écrire des signatures (et implémentations) mono-morphiques même là où cela ne comporte aucun avantage. Et plus tard, par exemple, il n’est pas rare de se rendre compte que des implémentations « obtuses » respectivement de type Foo et Bar ont beaucoup de ressemblances ; on se mettra alors à faire du refactoring a posteriori alors qu’il aurait été inutile si l’on avait écrit une implémentation plus générale du premier coup. Il y a d’autres cas plus gênants, voire limitants, qui peuvent arriver quand on met systématiquement les signatures mais c’est presque toujours lors d’usages avancés de Haskell (donc à un stade où l’on n'a plus besoin des recommandations d’un quidam sur DLFP).

          En gros, je trouve plus efficace de se défaire des signatures pendant la phase « brouillon » du cycle de vie d’un code et de les mettre au moment où le code passe en mode release. Mes 2$.

          • [^] # Re: Oui mais

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

            Quelle est la probabilité d'oublier de mettre les bonnes signatures, en mode release ? Et quelle est la probabilité de ne pas oublier de les changer, en mode brouillon, lorsque le refactoring aura entraîné une erreur de compilation ?

            • [^] # Re: Oui mais

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

              Dans mon cas, les deux probabilités sont nulles grâce à deux options de configuration :

              • brouillon : -Wno-missing-signatures
              • release : -Wmissing-signatures
            • [^] # Re: Oui mais

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

              La revue de code si tu as la chance d'en bénéficier. Mais cela dit, l'IDE peut tout à fait t'afficher les types attendus sans avoir à regarder l'implémentation (vu qu'il les connaît).

    • [^] # Re: Oui mais

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

      En go, on écrit les types des paramètres et des valeurs retournées des fonctions. Par contre, on a de l'inférence de type dans le corps de la fonction. Exemple :

      func computeMeanOfTemperatures(nb int) float64 {
          temperatures := getLastTemperatures(nb)
          sum := 0.0
          for _, t := range temperatures {
              sum += t
          }
          return sum / len(temperatures)
      }

      Je trouve que c'est un bon équilibre.

  • # Primitive obsession & strong types

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

    Je suis en partie d'accord avec toi, même si je trouve que les types dans les signatures de fonction c'est mieux.

    Mais ton exemple de type lourd 'Map>' est pas un très bon exemple. Il faut quasiment toujours renommer ce genre de type pour lui donner un nom porteur de sens. Et utiliser des types fort pour éviter de l'utiliser avec les mauvaises clefs par exemple.

  • # et si plusieurs return avec différents types

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

    Que se passe-t-il si la fonction retourne, selon les cas, des types différents ? Comment l'inférence de type se fait-elle ?

    Exemple en pseudo code :

    function getTempCelsius() {
        if (temperature>=12) {
            return "12°C"
        } else if (temperature>=-273) {
            return -1
        } else  {
            return null
        }
    }
    
    • [^] # Re: et si plusieurs return avec différents types

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

      Ça va dépendre des langages, mais certains autorise les types unions. Ici, ça dira que ça retourne soit une chaîne de caractères, soit un entier, soit null. J'imagine que d'autres langages peuvent essayer de trouver un ancêtre commun, genre Object (je n'ai pas d'exemple d'un tel langage en tête). Enfin, certains langages vont juste donner une erreur à la compilation.

      Globalement, ça dépend surtout de la manière dont les types sont définis par le langage, pas vraiment de l'inférence de type. Même sur un tel exemple, le type donné par l'inférence de type sera très certainement le même que celui qu'un humain aurait donné.

      • [^] # Re: et si plusieurs return avec différents types

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

        J'imagine que d'autres langages peuvent essayer de trouver un ancêtre commun, genre Object (je n'ai pas d'exemple d'un tel langage en tête).

        Scala.

        Mais bon celui qui m'écrit ça comme fonction prend une tarte. Dans un cas comme ça, on écrit un ADT, genre (en simplifié pour le boilerplate Scala, et modifié pour montrer le mix classe avec valeur interne / singleton) :

        sealed abstract class Temperature
        final case class Hot(value: String) extends Temperature // le cas "12" mais avec la valeur histoire de
        case object AbsoluteZero extends Temperature // le cas >=-273 que j'imagine être une erreur et devrait être <=
        case object Cold extends Temperature // le null
        
        def getTempCelsius(raw: Int): Temperature =
          if (raw >= 12)
            Hot(raw.toString)
          else if (raw <= -273)
            AbsoluteZero
          else
            Cold
    • [^] # Re: et si plusieurs return avec différents types

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

      Que se passe-t-il si la fonction retourne, selon les cas, des types différents ?

      Tes 2 else devraient renvoyer une exception.

  • # Pas toujours facile

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

    mais aussi elle rend la lecture des programmes bien plus facile

    Sauf quand on cherche justement à déterminer le type d'une variable…

    Pourquoi vouloir ça ? Pour porter du C++11 vers de vieux systèmes sans compilateurs C++11 par ex.

    Perso j'aime bien mettre le type de mes variables, et si je me retrouve à devoir taper std::vector<std::pair<std::string very::deep::namespace::VeryLongTypeName>> bon déjà il est largement temps de revoir le code ou au moins faire un typedef, là où le typage automatique n'aurait servi que de cache-misère.

Envoyer un commentaire

Suivre le flux des commentaires

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