Je lis le code beaucoup plus souvent que je l'écris, et je suppose que la plupart des programmeurs travaillant sur des logiciels industriels le font. Je suppose que l’avantage de l’inférence de type est moins de verbosité et moins de code écrit. Par contre, si vous lisez le code plus souvent, vous voudrez probablement du code lisible.
Le compilateur en déduit le type; il y a de vieux algorithmes pour cela. Mais la vraie question est: pourquoi le programmeur voudrait-il déduire le type de mes variables lorsque je lis le code? N’est-il pas plus rapide pour quiconque de lire le type plutôt que de penser au type?
Edit: En conclusion, je comprends pourquoi c'est utile. Mais dans la catégorie des fonctionnalités linguistiques, je le vois dans un seau avec une surcharge d’opérateur - utile dans certains cas, mais affectant la lisibilité en cas d’abus.
la source
Réponses:
Jetons un coup d'oeil à Java. Java ne peut pas avoir de variables avec des types inférés. Cela signifie que je dois souvent épeler le type, même si cela est parfaitement évident pour un lecteur humain.
Et parfois, il est tout simplement ennuyeux de préciser le type entier.
Ce typage statique prolixe me gêne, le programmeur. La plupart des annotations de type sont des remplissages de lignes répétitifs, des régurgiations sans contenu de ce que nous savons déjà. Cependant, j'aime bien la dactylographie statique, car elle peut vraiment aider à détecter les bogues, alors utiliser une dactylographie dynamique n'est pas toujours une bonne réponse. L'inférence de type est le meilleur des deux mondes: je peux omettre les types non pertinents, mais assurez-vous quand même que mon programme (type) se termine.
Bien que l'inférence de type soit vraiment utile pour les variables locales, elle ne devrait pas être utilisée pour les API publiques qui doivent être documentées sans ambiguïté. Et parfois, les types sont vraiment essentiels pour comprendre ce qui se passe dans le code. Dans de tels cas, il serait insensé de ne compter que sur l'inférence de type.
De nombreuses langues supportent l'inférence de type. Par exemple:
C ++. Le
auto
mot clé déclenche l'inférence de type. Sans cela, épeler les types de lambdas ou d'entrées en conteneurs serait un enfer.C #. Vous pouvez déclarer des variables avec
var
, ce qui déclenche une forme limitée d'inférence de type. Il gère toujours la plupart des cas où vous souhaitez une inférence de type. Dans certains endroits, vous pouvez laisser le type complètement (par exemple, dans lambdas).Haskell et toutes les langues de la famille ML. Bien que le type d'inférence de type utilisé ici soit assez puissant, vous voyez toujours des annotations de type pour des fonctions, et ce pour deux raisons: la première est la documentation et la seconde est une vérification indiquant que l'inférence de type a réellement trouvé les types que vous attendiez. S'il y a une différence, il y a probablement une sorte de bogue.
la source
int
, il peut s'agir de n'importe quel type numérique, même pairchar
. De plus, je ne vois pas pourquoi vous voudriez épeler tout le type carEntry
vous pouvez simplement taper le nom de la classe et laisser votre IDE effectuer l’importation nécessaire. Le seul cas où vous devez épeler le nom complet est lorsque vous avez une classe du même nom dans votre propre paquet. Mais cela me semble de toute façon mal conçu.int
exemple, je pensais au (à mon avis, un comportement plutôt sain d'esprit) de la plupart des langages qui comportent une inférence de type. Ils en déduisent généralement unint
ouInteger
quoi que ce soit appelé dans cette langue. La beauté de l'inférence de type est qu'elle est toujours facultative. vous pouvez toujours spécifier un type différent si vous en avez besoin. En ce qui concerne l'Entry
exemple: bon point, je le remplacerai parMap.Entry<Integer, Map<Integer, SomeObject<SomeObject, T>>>
. Java n'a même pas d'alias de type :(colKey
est à la fois évident et sans importance: nous ne nous soucions que de ce qu’il soit approprié comme second argument dedoSomethingWith
. Si je devais extraire cette boucle dans une fonction qui produit un Iterable de-(key1, key2, value)
triples, la signature la plus générale serait<K1, K2, V> Iterable<TableEntry<K1, K2, V>> flattenTable(Map<K1, Map<K2, V>> table)
. Dans cette fonction, le type réel decolKey
(Integer
, notK2
) est absolument sans importance.View.OnClickListener listener = new View.OnClickListener()
. Vous sauriez toujours le type même si le programmeur était "paresseux" et le raccourcissait àvar listener = new View.OnClickListener
(si cela était possible). Ce type de redondance est commune - je ne vais pas risquer une devinette ici - et en le retirant ne découlent de la réflexion sur les lecteurs futurs. Chaque fonctionnalité linguistique doit être utilisée avec précaution, je ne remets pas cela en question.C'est vrai que le code est lu beaucoup plus souvent qu'il n'est écrit. Cependant, la lecture prend aussi du temps, et deux écrans de code sont plus difficiles à naviguer et à lire qu'un écran de code. Nous devons donc définir des priorités pour obtenir le meilleur rapport informations utiles / effort de lecture. Ceci est un principe UX général: trop d’informations à la fois submergent et dégradent réellement l’efficacité de l’interface.
Et, selon mon expérience , le type exact n'a souvent aucune importance. Vous nichez parfois des expressions:
x + y * z
,monkey.eat(bananas.get(i))
,factory.makeCar().drive()
. Chacune de celles-ci contient des sous-expressions qui donnent une valeur dont le type n'est pas écrit. Pourtant, ils sont parfaitement clairs. Nous sommes d'accord pour laisser le type non déclaré, car il est assez facile de comprendre le contexte, et l'écrire ferait plus de mal que de bien (encombrer la compréhension du flux de données, prendre un écran précieux et un espace mémoire à court terme).Une des raisons pour ne pas imbriquer des expressions comme il n'y a pas de lendemain est que les lignes deviennent longues et que le flux de valeurs devient flou. L'introduction d'une variable temporaire aide à cela, elle impose un ordre et donne un nom à un résultat partiel. Cependant, tout ce qui profite de ces aspects ne tire pas également des avantages de son type:
Est-ce important
user
un objet entité, d'un entier, d'une chaîne ou de quelque chose d'autre? Dans la plupart des cas, il ne suffit pas de savoir qu'il représente un utilisateur, provient de la requête HTTP et permet d'extraire le nom à afficher dans le coin inférieur droit de la réponse.Et quand il fait affaire, l'auteur est libre d'écrire le type. C'est une liberté qui doit être utilisée de manière responsable, mais il en va de même pour tout ce qui peut améliorer la lisibilité (noms de variable et de fonction, formatage, conception de l'API, espace blanc). Et en effet, la convention dans Haskell et ML (où tout peut être déduit sans effort supplémentaire) consiste à écrire les types de fonctions non locales, ainsi que de variables et de fonctions locales, le cas échéant. Seuls les novices permettent d'inférer chaque type.
la source
user
importance importe si vous essayez d’élargir la fonction, car elle détermine ce que vous pouvez faire avec le fichieruser
. Ceci est important si vous souhaitez ajouter un contrôle de cohérence (en raison d'une vulnérabilité de sécurité, par exemple), ou oublier que vous devez réellement faire quelque chose avec l'utilisateur en plus de simplement l'afficher. Certes, ces types de lecture pour l’agrandissement sont moins souvent que la lecture du code, mais ils constituent également une partie essentielle de notre travail.Je pense que l'inférence de type est très importante et devrait être supportée dans n'importe quelle langue moderne. Nous développons tous dans les IDE et ils pourraient beaucoup vous aider si vous souhaitez connaître le type inféré, et que peu d’entre nous y travaillent
vi
. Pensez à la verbosité et au code de cérémonie en Java, par exemple.Mais vous pouvez dire que ça va, mon IDE va m'aider, ça pourrait être un argument valable. Cependant, certaines fonctionnalités n'existeraient pas sans l'aide de l'inférence de type, les types anonymes C # par exemple.
Linq ne serait pas aussi bien qu'il l'est maintenant sans l'aide de l'inférence de type,
Select
par exempleCe type anonyme sera clairement déduit de la variable.
Je n'aime pas l'inférence de type sur les types de retour
Scala
car je pense que votre argument s'applique ici. Il devrait être clair pour nous ce qu'une fonction renvoie pour pouvoir utiliser l'API plus facilement.la source
Map<String,HashMap<String,String>>
? Bien sûr, si vous n'êtes pas en utilisant les types, puis les épelant a peu d' avantages.Table<User, File, String>
est plus informatif cependant, et il est avantageux de l'écrire.Je pense que la réponse à cette question est très simple: cela évite de lire et d’écrire des informations redondantes. Particulièrement dans les langages orientés objet où vous avez un type des deux côtés du signe égal.
Ce qui vous indique également quand vous devriez ou ne devriez pas l'utiliser - quand l'information n'est pas redondante.
la source
Supposons que l'on voit le code:
Si
someBigLongGenericType
est assignable à partir du type de retour desomeFactoryMethod
, quelle est la probabilité qu'une personne lisant le code remarque si les types ne correspondent pas exactement, et avec quelle facilité une personne qui a remarqué la différence peut-elle reconnaître si elle était intentionnelle ou non?En permettant l'inférence, un langage peut suggérer à quelqu'un qui lit le code que, lorsque le type d'une variable est explicitement indiqué, il doit essayer de trouver une raison à cela. Cela permet aux lecteurs de code de mieux concentrer leurs efforts. Si, au contraire, dans la grande majorité des cas, lorsqu'un type est spécifié, il se trouve être exactement le même que ce qui aurait été déduit, quelqu'un qui lit le code peut être moins enclin à remarquer le temps où il est subtilement différent. .
la source
Je vois qu'il y a déjà un certain nombre de bonnes réponses. Je vais en répéter certaines, mais parfois, vous voulez simplement mettre les choses dans vos propres mots. Je vais commenter avec quelques exemples de C ++ parce que c'est le langage avec lequel je suis le plus familier.
Ce qui est nécessaire n’est jamais imprudent. L'inférence de type est nécessaire pour rendre pratiques d'autres fonctionnalités du langage. En C ++, il est possible d'avoir des types indicibles.
C ++ 11 a ajouté des lambdas qui sont également indéniables.
L'inférence de type sous-tend également les modèles.
Mais vos questions étaient les suivantes: "Pourquoi le programmeur voudrait-il déduire le type de mes variables lorsque je lis le code? N’est-il pas plus rapide pour quiconque de lire le type que de penser au type qui existe?"
L'inférence de type supprime la redondance. Lorsqu’il s’agit de lire du code, il peut parfois être plus rapide et plus facile d’avoir des informations redondantes dans le code, mais la redondance peut occulter les informations utiles . Par exemple:
Il ne faut pas beaucoup de connaissances de la bibliothèque standard pour qu'un programmeur C ++ identifie que i est un itérateur de
i = v.begin()
sorte que la déclaration de type explicite a une valeur limitée. Par sa présence, il masque les détails les plus importants (tels quei
le début du vecteur). La réponse précise de @amon fournit un exemple encore meilleur de la verbosité qui éclipse les détails importants. En revanche, l'utilisation de l'inférence de type met davantage en évidence les détails importants.Bien que la lecture du code soit importante, cela ne suffit pas. À un moment donné, vous devrez arrêter de lire et commencer à écrire un nouveau code. La redondance dans le code rend la modification du code plus lente et plus difficile. Par exemple, disons que j'ai le fragment de code suivant:
Dans le cas où j'ai besoin de changer le type de valeur du vecteur pour changer le code deux fois:
Dans ce cas, je dois modifier le code à deux endroits. Contraste avec l'inférence de type où le code d'origine est:
Et le code modifié:
Notez que je n'ai maintenant plus qu'à changer une ligne de code. Extrapolez ceci à un programme volumineux et l'inférence de type peut propager les modifications de types beaucoup plus rapidement qu'avec un éditeur.
La redondance dans le code crée la possibilité de bogues. Chaque fois que votre code dépend de la conservation de deux informations équivalentes, une erreur est possible. Par exemple, il existe une incohérence entre les deux types dans cette instruction qui n’est probablement pas destinée:
La redondance rend l’intention plus difficile à discerner. Dans certains cas, l'inférence de type peut être plus facile à lire et à comprendre, car elle est plus simple que la spécification de type explicite. Considérons le fragment de code:
Dans le cas qui
sq(x)
renvoie unint
, il n'est pas évident de savoir s'ily
s'agit d'unint
type de retoursq(x)
ou s'il convient aux déclarations utiliséesy
. Si je modifie un autre code tel quesq(x)
ne retourne plusint
, il est incertain à partir de cette ligne seule si le type dey
doit être mis à jour. Contrastez avec le même code mais en utilisant l'inférence de type:En cela, l'intention est claire,
y
doit être du même type que celui renvoyé parsq(x)
. Lorsque le code change le type de retour desq(x)
, le type dey
changement à faire correspondre automatiquement.En C ++, il y a une deuxième raison pour laquelle l'exemple ci-dessus est plus simple avec l'inférence de type. L'inférence de type ne peut pas introduire de conversion de type implicite. Si le type de retour
sq(x)
n'est pasint
, le compilateur insère silencieusement une conversion implicite enint
. Si le type de retoursq(x)
est un type complexe qui définitoperator int()
, cet appel de fonction masqué peut être arbitrairement complexe.la source
typeof
est rendu inutile par le langage. Et c’est un déficit de la langue elle-même qui devrait être corrigé à mon avis.