Pourquoi ne peut-il y avoir de conversions implicites?

14

Si je comprends bien, les conversions implicites peuvent provoquer des erreurs.

Mais cela n'a pas de sens - les conversions normales ne devraient-elles pas également provoquer des erreurs?

Pourquoi ne pas avoir

len(100)

travailler par la langue qui l'interprète (ou la compile) comme

len(str(100))

d'autant plus que c'est le seul moyen (que je connaisse) pour que ça marche. La langue sait quelle est l'erreur, pourquoi ne pas la corriger?

Pour cet exemple, j'ai utilisé Python, bien que je pense que pour quelque chose d'aussi petit, il est fondamentalement universel.

Quelklef
la source
2
perl -e 'print length(100);'imprime 3.
2
Et donc la nature de la langue et son système de types. Cela fait partie de la conception de python.
2
Il ne le résout pas, car il ne connaît pas votre itinéraire. peut-être que vous vouliez faire quelque chose de complètement différent. comme poursuivre une boucle, mais n'a jamais rien fait de tel que la programmation auparavant. donc s'il le corrige par lui-même, l'utilisateur ne sait pas qu'il s'est trompé ni que l'implémentation ne fera pas ce qu'il attend.
Zaibis
5
@PieCrust Comment Python est-il censé se convertir? Il n'est pas vrai que toutes les conversions possibles renverraient le même résultat.
Bakuriu
7
@PieCrust: les chaînes et les tableaux sont des itérables. pourquoi serait strle moyen implicite de convertir un int en un itérable? que diriez-vous range? ou bin( hex, oct) ou chr(ou unichr)? Tous ceux-ci renvoient des itérables, même si cela vous strsemble le plus évident dans cette situation.
njzk2

Réponses:

37

Pour ce que ça vaut len(str(100)), len(chr(100))et len(hex(100))sont tous différents. strn'est pas le seul moyen de le faire fonctionner, car il existe plusieurs conversions différentes en Python d'un entier en une chaîne. L'un d'eux est bien sûr le plus courant, mais il ne va pas nécessairement de soi que c'est celui-là que vous vouliez dire. Une conversion implicite signifie littéralement "cela va sans dire".

L'un des problèmes pratiques des conversions implicites est qu'il n'est pas toujours évident de savoir quelle conversion sera appliquée là où il existe plusieurs possibilités, ce qui fait que les lecteurs font des erreurs d'interprétation du code car ils ne parviennent pas à déterminer la conversion implicite correcte. Tout le monde dit toujours que ce qu'il voulait, c'est "l'interprétation évidente". C'est évident pour eux parce que c'est ce qu'ils voulaient dire. Cela pourrait ne pas être évident pour quelqu'un d'autre.

C'est pourquoi (la plupart du temps) Python préfère explicite à implicite, il préfère ne pas le risquer. Le cas principal où Python exerce une contrainte de type est en arithmétique. Elle permet 1 + 1.0parce que l'alternative serait trop gênant pour vivre avec, mais elle ne permet pas 1 + "1"parce qu'il pense que vous devriez avoir à spécifier si vous voulez dire int("1"), float("1"), ord("1"), str(1) + "1", ou autre chose. Il ne permet pas non plus (1,2,3) + [4,5,6], même s'il peut définir des règles pour choisir un type de résultat, tout comme il définit des règles pour choisir le type de résultat 1 + 1.0.

D'autres langues ne sont pas d'accord et ont beaucoup de conversions implicites. Plus ils incluent, moins ils deviennent évidents. Essayez de mémoriser les règles de la norme C pour les "promotions entières" et les "conversions arithmétiques habituelles" avant le petit déjeuner!

Steve Jessop
la source
+1 Pour montrer comment l'hypothèse de départ, qui lenne peut fonctionner qu'avec un seul type, est fondamentalement erronée.
KChaloux
2
@KChaloux: en fait, dans mon exemple str, chret hextous renvoient le même type! J'essayais de penser à un type différent dont le constructeur peut prendre juste un entier, mais je n'ai encore rien trouvé. L'idéal serait un conteneur Xlen(X(100)) == 100;-) numpy.zerossemble un peu obscur.
Steve Jessop
2
Pour des exemples des problèmes possibles, voir ceci: docs.google.com/document/d/… et destroyallsoftware.com/talks/wat
Jens Schauder
1
La conversion implicite (je pense que ce qu'on appelle la contrainte) peut provoquer des choses désagréables comme avoir a == b, b == cmais a == cne pas être vrai.
bgusach
Excellente réponse. Tout ce que je voulais dire, seulement mieux formulé! Mon seul petit reproche est que les promotions d'entiers C sont assez simples par rapport à la mémorisation des règles de coercition JavaScript, ou à enrouler votre tête autour d'une base de code C ++ avec une utilisation imprudente des constructeurs implicites.
GrandOpener
28

Si je comprends bien, les conversions implicites peuvent provoquer des erreurs.

Vous manquez un mot: les conversions implicites peuvent provoquer des erreurs d' exécution .

Pour un cas simple comme vous le montrez, ce que vous vouliez dire est assez clair. Mais les langues ne peuvent pas fonctionner sur les cas. Ils doivent travailler avec des règles. Pour de nombreuses autres situations, il n'est pas clair si le programmeur a fait une erreur (en utilisant le mauvais type) ou si le programmeur voulait faire ce que le code suppose qu'il voulait faire.

Si le code suppose une erreur, vous obtenez une erreur d'exécution. Puisqu'ils sont fastidieux à retrouver, de nombreuses langues se trompent du côté de vous dire que vous avez foiré et de vous laisser dire à l'ordinateur ce que vous vouliez vraiment dire (corriger le bogue ou faire explicitement la conversion). D'autres langages font la supposition, car leur style se prête à un code rapide et facile à déboguer.

Une chose à noter est que les conversions implicites rendent le compilateur un peu plus complexe. Vous devez faire plus attention aux cycles (essayons cette conversion de A en B; oups qui n'a pas fonctionné, mais il y a une conversion de B en A! Et ensuite vous devez vous soucier des cycles de taille C et D aussi ), qui est une petite motivation pour éviter les conversions implicites.

Telastyn
la source
La plupart parlait de fonctions qui ne peuvent fonctionner qu'avec un seul type, ce qui rend la conversion évidente. Quel serait celui qui fonctionnerait avec plusieurs types, et le type que vous insérez fait une différence? print ("295") = print (295), donc cela ne ferait pas de différence, sauf pour les variables. Ce que vous avez dit a du sens, sauf pour le paragraphe 3d ... Pouvez-vous répéter?
Quelklef
30
@PieCrust - 123 + "456", vouliez-vous "123456" ou 579? Les langages de programmation ne font pas de contexte, il leur est donc difficile de "comprendre", car ils auraient besoin de connaître le contexte dans lequel l'ajout est effectué. Quel paragraphe n'est pas clair?
Telastyn
1
De plus, peut-être que l'interprétation en base 10 n'est pas ce que vous vouliez. Oui, les conversions implicites sont une bonne chose , mais uniquement lorsqu'elles ne peuvent pas masquer une erreur.
Déduplicateur
13
@PieCrust: Pour être honnête, je ne m'attendrais jamais len(100)à revenir 3. Je trouverais beaucoup plus intuitif qu'il calcule le nombre de bits (ou octets) dans la représentation de 100.
user541686
3
Je suis avec @Mehrdad, d'après votre exemple, il est clair que vous pensez qu'il est évident à 100% que cela len(100)devrait vous donner le nombre de caractères de la représentation décimale du nombre 100. Mais c'est en fait une tonne d'hypothèses que vous faites tout en ignorant d'eux. Vous pouvez expliquer avec force pourquoi le nombre de bits, d'octets ou de caractères de la représentation hexadécimale ( 64-> 2 caractères) doit être renvoyé par len().
funkwurm
14

Les conversions implicites sont tout à fait possibles. La situation où vous rencontrez des problèmes est lorsque vous ne savez pas de quelle manière quelque chose devrait fonctionner.

Un exemple de cela peut être vu en Javascript où l' +opérateur travaille de différentes manières à différents moments.

>>> 4 + 3
7
>>> "4" + 3
43
>>> 4 + "3"
43

Si l'un des arguments est une chaîne, alors l' +opérateur est une concaténation de chaîne, sinon c'est l'addition.

Si on vous donne un argument et que vous ne savez pas s'il s'agit d'une chaîne ou d'un entier, et que vous souhaitez en ajouter, cela peut être un peu compliqué.

Une autre façon de gérer cela est de l'héritage de base (que perl suit - voir Programmation est difficile, allons-y Scripting ... )

Dans Basic, la lenfonction n'a de sens que d'être invoquée sur une chaîne (docs pour Visual Basic : "Toute expression de chaîne valide ou nom de variable. Si l'expression est de type Object, la fonction Len renvoie la taille telle qu'elle sera écrite dans le fichier par la fonction FilePut. ").

Perl suit ce concept de contexte. La confusion qui existe en JavaScript avec la conversion implicite des types pour l' +opérateur étant parfois plus et parfois concaténation ne se fait pas en Perl car +est toujours plus et .est toujours concaténation.

Si quelque chose est utilisé dans un contexte scalaire, c'est un scalaire (par exemple, en utilisant une liste comme scalaire, la liste se comporte comme s'il s'agissait d'un nombre correspondant à sa longueur). Si vous utilisez un opérateur de chaîne ( eqpour le test d'égalité, cmppour la comparaison de chaînes), le scalaire est utilisé comme s'il s'agissait d'une chaîne. De même, si quelque chose a été utilisé dans un contexte mathématique ( ==pour le test d'égalité et <=>pour la comparaison numérique), le scalaire est utilisé comme s'il s'agissait d'un nombre.

La règle fondamentale pour toute programmation est "faire ce qui surprend le moins". Cela ne veut pas dire qu'il n'y a pas de surprise là-dedans, mais l'effort est de surprendre le moins la personne.

En allant chez un proche cousin de perl-php, il y a des situations où un opérateur peut agir sur quelque chose dans des contextes de chaîne ou numériques et le comportement peut surprendre les gens. L' ++opérateur en est un exemple. Sur les chiffres, il se comporte exactement comme prévu. Lorsque vous agissez sur une chaîne, par exemple "aa", il incrémente la chaîne ( $foo = "aa"; $foo++; echo $foo;imprime ab). Il roulera également de sorte que azlorsqu'il sera incrémenté ba. Ce n'est pas encore particulièrement surprenant.

$foo = "3d8";
echo "$foo\n";
$foo++;
echo "$foo\n";
$foo++;
echo "$foo\n";
$foo++;
echo "$foo\n";

( ideone )

Cela imprime:

3d8
3d9
3e0
4

Bienvenue aux dangers des conversions implicites et des opérateurs agissant différemment sur la même chaîne. (Perl gère ce bloc de code un peu différemment - il décide que "3d8"lorsque l' ++opérateur est appliqué est une valeur numérique dès le début et passe 4immédiatement ( idéone ) - ce comportement est bien décrit dans perlop: incrémentation automatique et décrémentation automatique )

Maintenant, pourquoi une langue fait quelque chose d'une manière et une autre le fait d'une autre manière, cela revient aux pensées de conception des designers. La philosophie de Perl est qu'il y a plus d'une façon de le faire - et je peux penser à un certain nombre de façons de faire certaines de ces opérations. D'un autre côté, Python a une philosophie décrite dans PEP 20 - Le Zen de Python qui stipule (entre autres): "Il devrait y avoir une - et de préférence une seule - manière évidente de le faire."

Ces différences de conception ont conduit à différentes langues. Il existe un moyen d'obtenir la longueur d'un nombre en Python. La conversion implicite va à l'encontre de cette philosophie.

Lecture connexe: Pourquoi Ruby n'a-t-il pas une conversion implicite de Fixnum en chaîne?

Communauté
la source
À mon humble avis, un bon langage / cadre devrait souvent avoir plusieurs façons de faire les choses qui gèrent différemment les cas d'angle communs (mieux vaut traiter un cas d'angle commun une fois dans un langage ou un cadre que 1000 fois dans 1000 programmes); le fait que deux opérateurs font la même chose la plupart du temps ne doit pas être considéré comme une mauvaise chose, si lorsque leurs comportements diffèrent, chaque variation présente des avantages. Il ne devrait pas être difficile de comparer deux variables numériques xet yde manière à produire une relation d'équivalence ou un classement, mais les opérateurs de comparaison de l'IIRC Python ...
supercat
... n'implémente ni relations d'équivalence ni classement, et Python ne fournit aucun opérateur pratique qui le fasse.
supercat
12

ColdFusion a fait la plupart de cela. Il définit un ensemble de règles pour gérer vos conversions implicites, a un seul type de variable, et c'est parti.

Le résultat est une anarchie totale, où l'ajout de "4a" à 6 est 6,16667.

Pourquoi? Eh bien, parce que la première des deux variables est un nombre, le résultat sera donc numérique. "4a" est analysé comme une date et considéré comme "4 AM". 4 AM est 4: 00/24: 00, ou 1 / 6ème de jour (0.16667). Ajoutez 6 et vous obtenez 6.16667.

Les listes ont des caractères de séparation par défaut d'une virgule, donc si vous ajoutez un élément à une liste contenant une virgule, vous venez d'ajouter deux éléments. En outre, les listes sont des chaînes secrètes, de sorte qu'elles peuvent être analysées en tant que dates si elles ne contiennent qu'un seul élément.

La comparaison de chaînes vérifie si les deux chaînes peuvent être analysées en premier aux dates. Parce qu'il n'y a pas de type de date, il le fait. Même chose pour les nombres et les chaînes contenant des nombres, la notation octale et les booléens ("vrai" et "oui", etc.)

Plutôt que d'échouer rapidement et de signaler une erreur, ColdFusion gère à la place les conversions de types de données pour vous. Et pas dans le bon sens.

Bien sûr, vous pouvez corriger les conversions implicites en appelant des fonctions explicites comme DateCompare ... mais vous avez alors perdu les "avantages" de la conversion implicite.


Pour ColdFusion, c'est un moyen d'aider les développeurs à se renforcer. Surtout lorsque tous ces développeurs sont habitués au HTML (ColdFusion fonctionne avec des balises, il s'appelle CFML, plus tard, ils ont également ajouté des scripts via <cfscript>, où vous n'avez pas besoin d'ajouter des balises pour tout). Et cela fonctionne bien lorsque vous voulez simplement que quelque chose fonctionne. Mais lorsque vous avez besoin que les choses se passent de manière plus précise, vous avez besoin d'un langage qui refuse de faire des conversions implicites pour tout ce qui pourrait ressembler à une erreur.

Pimgd
la source
1
wow, "anarchie totale" en effet!
hoosierEE
7

Vous dites que les conversions implicites pourraient être une bonne idée pour les opérations sans ambiguïté, comme int a = 100; len(a), où vous entendez évidemment convertir l'int en chaîne avant d'appeler.

Mais vous oubliez que ces appels peuvent être syntaxiquement sans ambiguïté, mais ils peuvent représenter une faute de frappe faite par le programmeur qui voulait passer a1, qui est une chaîne. Il s'agit d'un exemple artificiel, mais avec les IDE fournissant la saisie semi-automatique pour les noms de variables, ces erreurs se produisent.

Le système de type vise à nous aider à éviter les erreurs, et pour les langues qui choisissent une vérification de type plus stricte, les conversions implicites sapent cela.

Découvrez les problèmes de Javascript avec toutes les conversions implicites effectuées par ==, à tel point que beaucoup recommandent maintenant de s'en tenir à l'opérateur no-implicit-conversion ===.

Avner Shahar-Kashtan
la source
6

Considérez un instant le contexte de votre déclaration. Vous dites que c'est la "seule façon" que cela pourrait fonctionner, mais êtes-vous vraiment sûr que cela fonctionnera comme ça? Et ça:

def approx_log_10(s):
    return len(s)
print approx_log_10(3.5)  # "3" is probably not what I'm expecting here...

Comme d'autres l'ont mentionné, dans votre exemple très spécifique, il semble simple pour le compilateur de comprendre ce que vous vouliez dire. Mais dans un contexte plus large de programmes plus compliqués, il n'est souvent pas du tout évident si vous vouliez entrer une chaîne, ou taper un nom de variable pour un autre, ou appeler la mauvaise fonction, ou l'une des dizaines d'autres erreurs logiques potentielles .

Dans le cas où l'interpréteur / compilateur devine ce que vous vouliez dire, parfois cela fonctionne comme par magie, et d'autres fois vous passez des heures à déboguer quelque chose qui mystérieusement ne fonctionne pas sans raison apparente. Il est beaucoup plus sûr, dans le cas moyen, d'être informé que la déclaration n'a pas de sens, et de passer quelques secondes à la corriger en fonction de ce que vous vouliez réellement dire.

GrandOpener
la source
3

Les conversions implicites peuvent être très pénibles à utiliser. Dans PowerShell:

$a = $(dir *.xml) # typeof a is a list, because there are two XML files in the folder.
$a = $(dir *.xml) # typeof a is a string, because there is one XML file in the folder.

Soudain, il y a deux fois plus de tests nécessaires et deux fois plus de bugs qu'il y en aurait sans conversion implicite.

Esben Skov Pedersen
la source
2

Les conversions explicites sont importantes car elles expliquent clairement votre intention. Tout d'abord, l'utilisation de transtypages explicites raconte une histoire à quelqu'un qui lit votre code. Ils révèlent que vous avez intentionnellement fait ce que vous avez fait. De plus, la même chose s'applique au compilateur. Ce qui suit est illégal en C # par exemple

double d = 3.1415926;
// code elided
int i = d;

Le casting vous fera perdre la précision, ce qui pourrait facilement être un bug. Par conséquent, le compilateur refuse de compiler. En utilisant une distribution explicite, vous dites au compilateur: "Hé, je sais ce que je fais." Et il ira: "D'accord!" Et compilez.

Paul Kertscher
la source
1
En fait, je n'aime pas la plupart des lancers explicites plutôt que ceux implicites; (X)yÀ mon humble avis, la seule chose qui devrait signifier est "je pense que Y est convertible en X; faites la conversion si possible, ou lancez une exception sinon"; si une conversion réussit, il faut être capable de prédire quelle sera la valeur résultante * sans avoir à connaître de règles spéciales sur la conversion du type de ytype X. Si on leur demandait de convertir -1,5 et 1,5 en entiers, certains langages donneraient (-2,1); certains (-2,2), certains (-1,1) et certains (-1,2). Alors que les règles de C ne sont guère obscures ...
supercat
1
... ce type de conversion semble être effectué de manière plus appropriée via une méthode que via un cast (dommage. NET n'inclut pas de fonctions efficaces de conversion et de conversion, et les Java sont surchargés de manière quelque peu idiote).
supercat
Vous dites au compilateur " Je pense que je sais ce que je fais".
gnasher729
@ gnasher729 Il y a du vrai dedans :)
Paul Kertscher
1

Les langages qui prennent en charge les conversions / conversions implicites, ou le typage faible comme il est parfois appelé, feront pour vous des hypothèses qui ne correspondent pas toujours au comportement que vous vouliez ou attendiez. Les concepteurs de la langue peuvent avoir eu le même processus de réflexion que vous avez fait pour un certain type de conversion ou de séries de conversions, et dans ce cas, vous ne verrez jamais de problème; mais s'ils ne l'ont pas fait, votre programme échouera.

L'échec peut cependant être évident ou non. Étant donné que ces conversions implicites se produisent au moment de l'exécution, votre programme fonctionnera avec bonheur et vous ne verrez un problème que lorsque vous examinerez la sortie ou le résultat de votre programme. Un langage qui nécessite des transtypages explicites (certains appellent cela un typage fort) vous donnerait une erreur avant même que le programme ne commence l'exécution, ce qui est un problème beaucoup plus évident et plus facile à corriger pour que les transtypages implicites se soient mal déroulés.

L'autre jour, un de mes amis a demandé à son enfant de 2 ans ce qu'était 20 + 20 et il a répondu 2020. J'ai dit à mon ami: "Il va devenir programmeur Javascript".

En Javascript:

20+20
40

20+"20"
"2020"

Ainsi, vous pouvez voir les problèmes que les conversions implicites peuvent créer et pourquoi ce n'est pas quelque chose qui peut simplement être corrigé. La solution, je dirais, consiste à utiliser des transtypages explicites.

Fred Thomsen
la source