Demander des éclaircissements sur les contradictions apparentes concernant les langues faiblement typées

178

Je pense que je comprends la frappe forte , mais chaque fois que je cherche des exemples de ce qu'est un typage faible, je finis par trouver des exemples de langages de programmation qui contraignent / convertissent simplement les types automatiquement.

Par exemple, dans cet article intitulé Typing: Strong vs Weak, Static vs Dynamic dit que Python est fortement typé car vous obtenez une exception si vous essayez de:

Python

1 + "1"
Traceback (most recent call last):
File "", line 1, in ? 
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Cependant, une telle chose est possible en Java et en C #, et nous ne les considérons pas faiblement typés juste pour cela.

Java

  int a = 10;
  String b = "b";
  String result = a + b;
  System.out.println(result);

C #

int a = 10;
string b = "b";
string c = a + b;
Console.WriteLine(c);

Dans cet autre article intitulé Weakly Type Languages, l'auteur dit que Perl est faiblement typé simplement parce que je peux concaténer une chaîne en un nombre et vice versa sans aucune conversion explicite.

Perl

$a=10;
$b="a";
$c=$a.$b;
print $c; #10a

Ainsi, le même exemple rend Perl faiblement typé, mais pas Java et C # ?.

Gee, c'est déroutant entrez la description de l'image ici

Les auteurs semblent laisser entendre qu'un langage qui empêche l'application de certaines opérations sur des valeurs de types différents est fortement typé et au contraire signifie faiblement typé.

Par conséquent, à un moment donné, je me suis senti poussé à croire que si une langue fournit beaucoup de conversions automatiques ou de coercition entre les types (comme perl) peut finir par être considérée comme faiblement typée, alors que d'autres langues qui fournissent seulement quelques conversions peuvent finir par être considéré comme fortement typé.

J'ai tendance à croire, cependant, que je dois me tromper dans cette interprétation, je ne sais simplement pas pourquoi ni comment l'expliquer.

Donc, mes questions sont:

  • Qu'est-ce que cela signifie vraiment pour une langue d'être vraiment faiblement typée?
  • Pouvez-vous citer de bons exemples de frappe faible qui ne sont pas liés à la conversion automatique / à la coercition automatique effectuée par la langue?
  • Une langue peut-elle être faiblement typée et fortement typée en même temps?
Edwin Dalorzo
la source
8
Le typage fort ou faible est une question de conversion de type (de quoi d'autre pourrait-il s'agir?) Si vous voulez un exemple de langage "très" faible, regardez ceci: destroyallsoftware.com/talks/wat .
Wilduck
2
@Wildduck Toutes les langues fournissent des conversions de type, mais elles ne sont pas toutes considérées comme faiblement typées. Mes exemples ci-dessous montrent comment les programmeurs considèrent un langage faiblement typé sur la base des mêmes exemples que ceux possibles sur d'autres langages considérés comme fortement typés. En tant que telle, ma question prévaut toujours. Quelle est la différence?
Edwin Dalorzo
1
La réponse courte, je pense, est que "Typedness" n'est pas un état binaire. Java et C # sont plus fortement typés mais pas absolument.
Jodrell
3
Je pense que cela convient mieux au génie logiciel .
zzzzBov
4
@Brendan Et la somme d'un flottant et d'un entier? L'entier n'est-il pas forcé en un flottant en Python? Diriez-vous maintenant que Python n'est pas absolument fortement typé?
Edwin Dalorzo

Réponses:

210

MISE À JOUR: Cette question a fait l'objet de mon blog le 15 octobre 2012. Merci pour la super question!


Qu'est-ce que cela signifie vraiment pour une langue d'être "faiblement typée"?

Cela signifie "ce langage utilise un système de type que je trouve déplaisant". Un langage "fortement typé" par contre est un langage avec un système de typage que je trouve agréable.

Les termes n'ont essentiellement aucun sens et vous devez les éviter. Wikipédia énumère onze significations différentes pour «fortement typé», dont plusieurs sont contradictoires. Cela indique que les risques de confusion créés sont élevés dans toute conversation impliquant le terme «fortement typé» ou «faiblement typé».

Tout ce que vous pouvez vraiment dire avec certitude, c'est qu'un langage "fortement typé" en discussion a une restriction supplémentaire dans le système de types, soit à l'exécution ou à la compilation, qu'un langage "faiblement typé" en discussion manque. Ce que cette restriction pourrait être ne peut être déterminé sans autre contexte.

Au lieu d'utiliser «fortement typé» et «faiblement typé», vous devez décrire en détail le type de sécurité de type que vous entendez. Par exemple, C # est un langage à typage statique et un langage de type sécurisé et un langage sans mémoire , pour la plupart. C # permet à ces trois formes de typage "fort" d'être violées. L'opérateur de cast enfreint le typage statique; il dit au compilateur "J'en sais plus que vous sur le type d'exécution de cette expression". Si le développeur se trompe, le runtime lèvera une exception afin de protéger la sécurité du type. Si le développeur souhaite interrompre la sécurité de type ou la sécurité de la mémoire, il peut le faire en désactivant le système de sécurité de type en créant un bloc "dangereux". Dans un bloc non sécurisé, vous pouvez utiliser la magie du pointeur pour traiter un int comme un flottant (violant la sécurité de type) ou pour écrire dans la mémoire que vous ne possédez pas. (Violation de la sécurité de la mémoire.)

C # impose des restrictions de type qui sont vérifiées à la fois à la compilation et à l'exécution, ce qui en fait un langage «fortement typé» par rapport aux langages qui font moins de vérification à la compilation ou moins de vérification à l'exécution. C # vous permet également, dans des circonstances spéciales, d'effectuer une exécution finale autour de ces restrictions, ce qui en fait un langage «faiblement typé» par rapport aux langages qui ne vous permettent pas d'effectuer une telle exécution finale.

Qu'est-ce que c'est vraiment? Il est impossible de dire; cela dépend du point de vue du locuteur et de son attitude vis-à-vis des différentes caractéristiques du langage.

Eric Lippert
la source
14
@edalorzo: Il est basé sur le goût et les opinions personnelles sur (1) quels aspects de la théorie des types sont pertinents et lesquels ne le sont pas, et (2) si un langage est nécessaire pour appliquer ou simplement encourager les restrictions de type. Comme je l'ai souligné, on pourrait raisonnablement dire que C # est fortement typé car il permet et encourage le typage statique, et on pourrait tout aussi raisonnablement dire qu'il est faiblement typé car il permet la possibilité de violer la sécurité de type.
Eric Lippert
4
@edalorzo: Quant au montage, encore une fois, c'est une question d'opinion. Un compilateur en langage assembleur ne vous permettra pas de déplacer un double 64 bits de la pile vers un registre 32 bits; il vous permettra de déplacer un pointeur 32 bits vers un double 64 bits de la pile dans un registre 32 bits. En ce sens, le langage est "typeafe" - il impose une restriction à la légalité du programme basée sur une classification de type de données. La question de savoir si cette restriction est "forte" ou "faible" est une question d'opinion, mais il s'agit clairement d'une restriction.
Eric Lippert
2
Je pense que je comprends votre point maintenant, une langue vraiment faiblement typée devrait être totalement non typée ou monotypée, ce qui dans la vraie vie est pratiquement impossible. En tant que tel, tout langage a une certaine définition de types, qui sont sûrs, et en fonction du nombre de trous que le langage fournit pour violer ou manipuler ses données ou types de données, vous pouvez finir par le considérer plus ou moins faiblement typé, peut-être même dans certains contextes seulement.
Edwin Dalorzo
7
@edalorzo: C'est exact. Par exemple, le calcul lambda non typé est à peu près aussi faiblement typé que possible. Chaque fonction est une fonction d'une fonction à une fonction; toutes les données peuvent être transmises à n'importe quelle fonction sans restriction car tout est du «même type». La validité d'une expression dans le calcul lambda non typé dépend uniquement de sa forme syntaxique, et non d'une analyse sémantique qui classe certaines expressions comme ayant certains types.
Eric Lippert
3
@Mark Je lui donnerais un autre +1 pour avoir prédit que tout le monde donnerait des interprétations différentes sur le sujet. Ce "faiblement typant" semble être un "concept mythique" ou une "légende urbaine", tout le monde l'a vu, mais personne ne peut prouver qu'il existe :-)
Edwin Dalorzo
64

Comme d'autres l'ont noté, les termes «fortement typé» et «faiblement typé» ont tellement de significations différentes qu'il n'y a pas de réponse unique à votre question. Cependant, puisque vous avez spécifiquement mentionné Perl dans votre question, laissez-moi essayer d'expliquer dans quel sens Perl est faiblement typé.

Le fait est que, en Perl, il n'existe pas de "variable entière", de "variable flottante", de "variable chaîne" ou de "variable booléenne". En fait, dans la mesure où l'utilisateur peut ( en général) dire, il n'y a même pas entier, virgule flottante, chaîne ou booléen valeurs : tout ce que vous avez sont « scalaires », qui sont toutes ces choses en même temps. Ainsi, vous pouvez, par exemple, écrire:

$foo = "123" + "456";           # $foo = 579
$bar = substr($foo, 2, 1);      # $bar = 9
$bar .= " lives";               # $bar = "9 lives"
$foo -= $bar;                   # $foo = 579 - 9 = 570

Bien sûr, comme vous le notez correctement, tout cela peut être considéré comme une simple coercition de type. Mais le fait est qu'en Perl, les types sont toujours forcés. En fait, il est assez difficile pour un utilisateur de dire quel pourrait être le "type" interne d'une variable: à la ligne 2 de mon exemple ci-dessus, demander si la valeur de $barest la chaîne "9"ou le nombre 9n'a pratiquement pas de sens, car, comme en ce qui concerne Perl, c'est la même chose . En effet, il est même possible pour un scalaire Perl d'avoir en interne à la fois une chaîne et une valeur numérique en même temps, comme c'est par exemple le cas $fooaprès la ligne 2 ci-dessus.

Le revers de tout cela est que, puisque les variables Perl ne sont pas typées (ou plutôt n'exposent pas leur type interne à l'utilisateur), les opérateurs ne peuvent pas être surchargés pour faire des choses différentes pour différents types d'arguments; vous ne pouvez pas simplement dire "cet opérateur fera X pour les nombres et Y pour les chaînes", car l'opérateur ne peut pas (ne veut pas) dire quel type de valeurs sont ses arguments.

Ainsi, par exemple, Perl a et a besoin à la fois d'un opérateur d'addition numérique ( +) et d'un opérateur de concaténation de chaînes ( .): comme vous l'avez vu ci-dessus, il est parfaitement bien d'ajouter des chaînes ( "1" + "2" == "3") ou de concaténer des nombres ( 1 . 2 == 12). De même, les opérateurs de comparaison numériques ==, !=, <, >, <=, >=et <=>comparer les valeurs numériques de leurs arguments, alors que les opérateurs de comparaison de chaînes eq, ne, lt, gt, le, geet cmples comparer lexicographique sous forme de chaînes. Donc 2 < 10, mais 2 gt 10(mais "02" lt 10, pendant "02" == 2). (Attention, certains autres langages, comme JavaScript, essaient de prendre en charge un typage faible de type Perl tout enfaisant également la surcharge de l'opérateur. Cela conduit souvent à la laideur, comme la perte d'associativité pour +.)

(Le problème ici est que, pour des raisons historiques, Perl 5 a quelques cas d'angle, comme les opérateurs logiques au niveau du bit, dont le comportement dépend de la représentation interne de leurs arguments. Ceux-ci sont généralement considérés comme un défaut de conception ennuyeux, car la représentation interne peut changer pour des raisons surprenantes, et donc prédire ce que ces opérateurs font dans une situation donnée peut être délicat.)

Cela dit, on pourrait dire que Perl n'avoir des types forts; ce ne sont tout simplement pas les types auxquels vous pourriez vous attendre. Plus précisément, en plus du type "scalaire" décrit ci-dessus, Perl a également deux types structurés: "array" et "hash". Ceux-ci sont très distincts des scalaires, au point où les variables Perl ont des sigils différents indiquant leur type ( pour les scalaires, pour les tableaux, pour les hachages) 1 . Il existe des règles de coercition entre ces types, vous pouvez donc écrire par exemple , mais beaucoup d'entre elles sont assez avec perte: par exemple, assigne la longueur du tableau à$@%%foo = @bar$foo = @bar@bar$foo, pas son contenu. (En outre, il existe quelques autres types étranges, comme les typesglobs et les poignées d'E / S, que vous ne voyez pas souvent exposés.)

De plus, une légère lacune dans cette belle conception est l'existence de types de référence, qui sont un type spécial de scalaires (et qui peuvent être distingués des scalaires normaux, en utilisant l' refopérateur). Il est possible d'utiliser des références comme des scalaires normaux, mais leurs valeurs chaîne / numérique ne sont pas particulièrement utiles, et elles ont tendance à perdre leur référence spéciale si vous les modifiez en utilisant des opérations scalaires normales. De plus, toute variable Perl 2 peut être un paradigme. L'opinion générale est que si vous vous trouvez en train de vérifier la classe d'un objet en Perl, vous faites quelque chose de mal.bless éditée dans une classe, la transformant en un objet de cette classe; le système de classes OO en Perl est quelque peu orthogonal au système de type primitif (ou sans type) décrit ci-dessus, bien qu'il soit également «faible» dans le sens de suivre le typage canard


1 En fait, le sigil désigne le type de la valeur à laquelle on accède, de sorte que par exemple le premier scalaire du tableau @fooest désigné $foo[0]. Voir perlfaq4 pour plus de détails.

2 Les objets en Perl sont (normalement) accédés par des références à eux, mais ce qui est réellement modifié blessest la variable (probablement anonyme) vers laquelle pointe la référence. Cependant, la bénédiction est en effet une propriété de la variable, pas de sa valeur, donc par exemple, attribuer la variable bénie réelle à une autre vous en donne juste une copie superficielle et non bénie. Voir perlobj pour plus de détails.

Ilmari Karonen
la source
19

En plus de ce qu'Eric a dit, considérez le code C suivant:

void f(void* x);

f(42);
f("hello");

Contrairement aux langages tels que Python, C #, Java ou autres, ce qui précède est faiblement typé car nous perdons des informations de type. Eric a correctement fait remarquer qu'en C #, nous pouvons contourner le compilateur en effectuant un casting, en lui disant effectivement «J'en sais plus sur le type de cette variable que vous».

Mais même dans ce cas, le runtime vérifiera toujours le type! Si le cast n'est pas valide, le système d'exécution l'attrapera et lèvera une exception.

Avec l'effacement de type, cela ne se produit pas - les informations de type sont jetées. Un casting void*en C fait exactement cela. À cet égard, ce qui précède est fondamentalement différent d'une déclaration de méthode C # telle que void f(Object x).

(Techniquement, C # permet également l'effacement de type via un code non sécurisé ou un marshalling.)

C'est aussi faiblement tapé que possible. Tout le reste est juste une question de vérification de type statique ou dynamique, c'est-à-dire du moment un type est vérifié.

Konrad Rudolph
la source
1
+1 Bon point, vous m'avez maintenant fait penser à l'effacement de caractères comme une fonctionnalité qui peut également impliquer une "faible frappe". Il existe également un effacement de type en Java, et au moment de l'exécution, le système de type vous permettra de violer des contraintes que le compilateur n'approuverait jamais. L'exemple C est excellent pour illustrer ce point.
Edwin Dalorzo
1
D'accord, il y a des couches à l'oignon ou à l'enfer. Cela semble une définition plus significative de la faiblesse de type.
Jodrell
1
@edalorzo Je ne pense pas que ce soit tout à fait la même chose car même si Java vous permet de contourner le compilateur, le système de type d'exécution détectera toujours la violation. Ainsi, le système de type d'exécution Java est fortement typé à cet égard (il existe des exceptions, par exemple où la réflexion peut être utilisée pour contourner le contrôle d'accès).
Konrad Rudolph
1
@edalorzo Vous ne pouvez contourner le compilateur que de cette façon, pas le système d'exécution. Il est important de réaliser que les langages tels que Java et C # (et dans une certaine mesure aussi C ++) ont un système de types qui est assuré deux fois: une fois à la compilation et une fois à l'exécution. void*franchit les deux contrôles de type. L'effacement de type générique ne le fait pas, il contourne seulement les contrôles à la compilation. C'est exactement comme les moulages explicites (mentionnés par Eric) à cet égard.
Konrad Rudolph
1
@edalorzo Re votre confusion: nous ne devrions pas. La distinction est courante. Et oui, l'effacement de type rend Java faiblement typé à cet égard. Mon point était que même avec l'effacement de type générique, vous ne pouvez toujours pas contourner les vérifications de type d'exécution à moins que vous n'utilisiez également la réflexion .
Konrad Rudolph
14

Un exemple parfait vient de l'article wikipedia de Strong Typing :

Un typage généralement fort implique que le langage de programmation impose des restrictions sévères sur le mélange qui est autorisé à se produire.

Faible frappe

a = 2
b = "2"

concatenate(a, b) # returns "22"
add(a, b) # returns 4

Typage fort

a = 2
b = "2"

concatenate(a, b) # Type Error
add(a, b) # Type Error
concatenate(str(a), b) #Returns "22"
add(a, int(b)) # Returns 4

Notez qu'un langage de frappe faible peut mélanger différents types sans erreurs. Un langage de type fort nécessite que les types d'entrée soient les types attendus. Dans un langage de type fort, un type peut être converti ( str(a)convertit un entier en chaîne) ou cast ( int(b)).

Tout dépend de l'interprétation de la frappe.

SaulBack
la source
3
Mais cela conduit aux exemples contradictoires fournis dans la question. Un langage fortement typé peut inclure une coercition implicite qui signifie que l'un ou l'autre de vos deux exemples "Erreur de type" est automatiquement converti en deux exemples pertinents, mais généralement ce langage est toujours fortement typé.
Mark Hurd
3
Vrai. Je suppose que vous pourriez dire qu'il existe différents degrés de frappe forte et de frappe faible. Une conversion implicite peut signifier que le langage est moins fortement typé qu'un langage qui n'effectue pas de conversion implicite.
SaulBack
4

Je voudrais contribuer à la discussion avec mes propres recherches sur le sujet, comme d'autres commentent et contribuent, j'ai lu leurs réponses et suivi leurs références et j'ai trouvé des informations intéressantes. Comme suggéré, il est probable que la plupart de ces questions seraient mieux discutées dans le forum des programmeurs, car cela semble être plus théorique que pratique.

D'un point de vue théorique, je pense que l'article de Luca Cardelli et Peter Wegner intitulé On Understanding Types, Data Abstraction and Polymorphism a l'un des meilleurs arguments que j'ai lus.

Un type peut être considéré comme un ensemble de vêtements (ou une armure) qui protège une représentation sous-jacente non typée d'une utilisation arbitraire ou non intentionnelle. Il fournit un revêtement protecteur qui masque la représentation sous-jacente et limite la façon dont les objets peuvent interagir avec d'autres objets. Dans un système non typé, les objets non typés sont nus en ce sens que la représentation sous-jacente est exposée à la vue de tous. La violation du système de typage implique de retirer l'ensemble de protection des vêtements et d'agir directement sur la représentation nue.

Cette déclaration semble suggérer qu'un typage faible nous permettrait d'accéder à la structure interne d'un type et de la manipuler comme s'il s'agissait de quelque chose d'autre (un autre type). Peut-être ce que nous pourrions faire avec du code non sécurisé (mentionné par Eric) ou avec des pointeurs effacés de type c mentionnés par Konrad.

L'article continue ...

Les langages dans lesquels toutes les expressions sont cohérentes de type sont appelés langages fortement typés. Si un langage est fortement typé, son compilateur peut garantir que les programmes qu'il accepte s'exécuteront sans erreurs de type. En général, nous devons nous efforcer d'obtenir un typage fort et adopter le typage statique chaque fois que possible. Notez que chaque langue typée statiquement est fortement typée mais l'inverse n'est pas nécessairement vrai.

En tant que tel, un typage fort signifie l'absence d'erreurs de type, je ne peux que supposer qu'un typage faible signifie le contraire: la présence probable d'erreurs de type. Au moment de l'exécution ou de la compilation? Cela ne semble pas pertinent ici.

Chose amusante, selon cette définition, un langage avec des coercitions de type puissantes comme Perl serait considéré comme fortement typé, car le système n'échoue pas, mais il traite les types en les contraignant à des équivalences appropriées et bien définies.

D'autre part, pourrais-je dire que l'allocation de ClassCastExceptionet ArrayStoreException(en Java) et InvalidCastException, ArrayTypeMismatchException(en C #) indiquerait un niveau de typage faible, au moins au moment de la compilation? La réponse d'Eric semble être d'accord avec cela.

Dans un deuxième article intitulé Typeful Programming fourni dans l'une des références fournies dans l'une des réponses à cette question, Luca Cardelli se penche sur le concept de violations de type:

La plupart des langages de programmation système autorisent des violations de type arbitraires, certaines sans discernement, d'autres seulement dans des parties restreintes d'un programme. Les opérations qui impliquent des violations de type sont appelées non fiables. Les violations de type appartiennent à plusieurs classes [parmi lesquelles nous pouvons mentionner]:

Coercitions de valeur de base : Celles-ci incluent les conversions entre les entiers, les booléens, les caractères, les ensembles, etc. Il n'y a pas besoin de violations de type ici, car des interfaces intégrées peuvent être fournies pour effectuer les coercitions d'une manière de type sonore.

En tant que telles, les coercitions de type comme celles fournies par les opérateurs pourraient être considérées comme des violations de type, mais à moins qu'elles ne rompent la cohérence du système de types, nous pourrions dire qu'elles ne conduisent pas à un système faiblement typé.

Sur cette base, ni Python, Perl, Java ou C # ne sont faiblement typés.

Cardelli mentionne deux vilations de type que je considère très bien comme des cas de typage vraiment faible:

Adresse arithmétique. Si nécessaire, il devrait y avoir une interface intégrée (défectueuse), fournissant les opérations adéquates sur les adresses et les conversions de type. Diverses situations impliquent des pointeurs dans le tas (très dangereux avec le déplacement des collecteurs), des pointeurs vers la pile, des pointeurs vers des zones statiques et des pointeurs vers d'autres espaces d'adressage. Parfois, l'indexation de tableau peut remplacer l'arithmétique d'adresse. Cartographie de la mémoire. Cela implique de considérer une zone de mémoire comme un tableau non structuré, bien qu'il contienne des données structurées. Ceci est typique des allocateurs et des collecteurs de mémoire.

Ce genre de choses possible dans des langages comme C (mentionné par Konrad) ou via du code unsafe dans .Net (mentionné par Eric) impliquerait vraiment de taper faiblement.

Je crois que la meilleure réponse à ce jour est celle d'Eric, car la définition de ces concepts est très théorique, et lorsqu'il s'agit d'un langage particulier, les interprétations de tous ces concepts peuvent conduire à des conclusions discutables différentes.

Edwin Dalorzo
la source
4

Un typage faible signifie en effet qu'un pourcentage élevé de types peut être implicitement contraint, en essayant de deviner ce que le codeur voulait.

Un typage fort signifie que les types ne sont pas forcés, ou du moins moins forcés.

Le typage statique signifie que les types de vos variables sont déterminés au moment de la compilation.

De nombreuses personnes ont récemment confondu «manifestement typé» avec «fortement typé». «Manifestement typé» signifie que vous déclarez explicitement les types de vos variables.

Python est principalement fortement typé, bien que vous puissiez utiliser presque tout dans un contexte booléen, et les booléens peuvent être utilisés dans un contexte entier, et vous pouvez utiliser un entier dans un contexte flottant. Il n'est pas manifestement typé, car vous n'avez pas besoin de déclarer vos types (sauf pour Cython, qui n'est pas entièrement python, bien qu'intéressant). Il n'est pas non plus typé statiquement.

C et C ++ sont manifestement typés, statiquement typés et quelque peu fortement typés, car vous déclarez vos types, les types sont déterminés au moment de la compilation, et vous pouvez mélanger des entiers et des pointeurs, ou des entiers et des doubles, ou même convertir un pointeur vers un type en un pointeur vers un autre type.

Haskell est un exemple intéressant, car il n'est pas manifestement typé, mais il est aussi statiquement et fortement typé.

user1277476
la source
+1 Parce que j'aime le terme inventé «manifestement typé», qui catégorise les langages comme Java et C # où vous devez déclarer explicitement les types et les distinguer des autres langages de type statique comme Haskell et Scala où l'inférence de type joue un rôle important et ceci généralement confond les gens, comme vous le dites, et leur fait croire que ces langues sont typées dynamiquement.
Edwin Dalorzo
3

Le typage fort <=> faible ne concerne pas seulement le continuum sur la quantité ou le peu de valeurs qui sont automatiquement forcées par la langue pour un type de données à un autre, mais la force ou la faiblesse des valeurs réelles typées. En Python et Java, et principalement en C #, les valeurs ont leurs types gravés dans la pierre. En Perl, pas tellement - il n'y a vraiment qu'une poignée de types de valeurs différents à stocker dans une variable.

Ouvrons les cas un par un.


Python

Dans l'exemple Python 1 + "1", l' +opérateur appelle le __add__type for inten lui donnant la chaîne "1"comme argument - cependant, cela se traduit par NotImplemented:

>>> (1).__add__('1')
NotImplemented

Ensuite, l'interpréteur essaie le __radd__of str:

>>> '1'.__radd__(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__radd__'

Lorsqu'il échoue, l' +opérateur échoue avec le résultat TypeError: unsupported operand type(s) for +: 'int' and 'str'. En tant que telle, l'exception ne dit pas grand-chose sur le typage fort, mais le fait que l'opérateur + ne contraint pas automatiquement ses arguments au même type, est un indicateur sur le fait que Python n'est pas le langage le plus faiblement typé du continuum.

D'autre part, en Python 'a' * 5 est implémenté:

>>> 'a' * 5
'aaaaa'

C'est,

>>> 'a'.__mul__(5)
'aaaaa'

Le fait que l'opération soit différente nécessite un typage fort - cependant le contraire de *forcer les valeurs à des nombres avant de multiplier encore ne rendrait pas nécessairement les valeurs faiblement typées.


Java

L'exemple Java String result = "1" + 1;ne fonctionne que parce que par commodité, l'opérateur +est surchargé pour les chaînes. L' +opérateur Java remplace la séquence par la création d'un StringBuilder(voir ceci ):

String result = a + b;
// becomes something like
String result = new StringBuilder().append(a).append(b).toString()

Ceci est plutôt un exemple de typage très statique, sans aucune contrainte réelle - StringBuildera une méthode append(Object)qui est spécifiquement utilisée ici. La documentation dit ce qui suit:

Ajoute la représentation sous forme de chaîne de l' Objectargument.

L'effet global est exactement comme si l'argument était converti en chaîne par la méthode String.valueOf(Object), et les caractères de cette chaîne étaient ensuite ajoutés à cette séquence de caractères.

String.valueOfalors

Renvoie la représentation sous forme de chaîne de l'argument Object. [Renvoie] si l'argument est null, alors une chaîne égale à "null"; sinon, la valeur de obj.toString()est renvoyée.

Il s'agit donc d'un cas d'absolument aucune contrainte de la part du langage - déléguant toute préoccupation aux objets eux-mêmes.


C #

Selon la réponse de Jon Skeet ici , l'opérateur +n'est même pas surchargé pour la stringclasse - à l'instar de Java, ce n'est que la commodité générée par le compilateur, grâce à la fois à un typage statique et fort.


Perl

Comme l' explique la perldata ,

Perl a trois types de données intégrés: les scalaires, les tableaux de scalaires et les tableaux associatifs de scalaires, appelés «hachages». Un scalaire est une chaîne unique (de n'importe quelle taille, limitée uniquement par la mémoire disponible), un nombre ou une référence à quelque chose (qui sera discuté dans perlref). Les tableaux normaux sont des listes ordonnées de scalaires indexées par nombre, commençant par 0. Les hachages sont des collections non ordonnées de valeurs scalaires indexées par leur clé de chaîne associée.

Cependant, Perl n'a pas de type de données séparé pour les nombres, les booléens, les chaînes, les valeurs nulles, les undefineds, les références à d'autres objets, etc. - il n'a qu'un seul type pour tous, le type scalaire; 0 est une valeur scalaire autant que "0". Une variable scalaire qui a été définie comme une chaîne peut vraiment se transformer en nombre, et à partir de là, se comporter différemment de "juste une chaîne", si elle est accédée dans un contexte numérique. Le scalaire peut contenir n'importe quoi en Perl, c'est autant l'objet qu'il existe dans le système. alors qu'en Python les noms font simplement référence aux objets, en Perl les valeurs scalaires dans les noms sont des objets modifiables. De plus, le système de type orienté objet est collé dessus: il n'y a que 3 types de données dans perl - scalaires, listes et hachages. Un objet défini par l'utilisateur en Perl est une référence (c'est-à-dire un pointeur vers l'une des 3 précédentes) blesséditée vers un paquet - vous pouvez prendre une telle valeur et la donner à n'importe quelle classe à tout moment.

Perl vous permet même de changer les classes de valeurs à volonté - ce n'est pas possible en Python où pour créer une valeur d'une classe, vous devez explicitement construire la valeur appartenant à cette classe avec object.__new__ou similaire. En Python, vous ne pouvez pas vraiment changer l'essence de l'objet après la création, en Perl vous pouvez faire beaucoup de choses:

package Foo;
package Bar;

my $val = 42;
# $val is now a scalar value set from double
bless \$val, Foo;
# all references to $val now belong to class Foo
my $obj = \$val;
# now $obj refers to the SV stored in $val
# thus this prints: Foo=SCALAR(0x1c7d8c8)
print \$val, "\n"; 
# all references to $val now belong to class Bar
bless \$val, Bar;
# thus this prints Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# we change the value stored in $val from number to a string
$val = 'abc';
# yet still the SV is blessed: Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# and on the course, the $obj now refers to a "Bar" even though
# at the time of copying it did refer to a "Foo".
print $obj, "\n";

ainsi l'identité de type est faiblement liée à la variable, et elle peut être modifiée par n'importe quelle référence à la volée. En fait, si vous faites

my $another = $val;

\$anothern'a pas l'identité de classe, même s'il \$valdonnera toujours la référence bénie.


TL; DR

Il y a beaucoup plus à propos du typage faible en Perl que de simples coercitions automatiques, et il s'agit davantage du fait que les types de valeurs eux-mêmes ne sont pas gravés dans la pierre, contrairement au Python qui est un langage dynamiquement mais très fortement typé. Ce python donne TypeErrorsur 1 + "1"est une indication que la langue est fortement typé, même si celui contraire de faire quelque chose d' utile, comme en Java ou C # ne les empêche pas d' être un langage fortement typé.

Antti Haapala
la source
C'est totalement confus. Le fait que les variables Perl 5 n'aient pas de types n'a aucune incidence sur les valeurs qui ont toujours un type.
Jim Balter
@JimBalter eh bien, oui, une valeur a un type en ce sens qu'il s'agit d'une chaîne ou d'un nombre, et elle peut se comporter différemment dans certains contextes selon que la variable scalaire contenait une chaîne ou un nombre; mais la valeur contenue dans une variable peut changer de type en accédant simplement à la variable, et puisque la valeur elle-même vit dans la variable, les valeurs elles-mêmes peuvent être considérées comme mutables entre les types.
Antti Haapala
Les valeurs ne changent pas de type - c'est incohérent; une valeur est toujours de type . La valeur qu'une variable contient peut changer. Un changement de 1 à "1" est tout autant un changement de valeur qu'un changement de 1 à 2.
Jim Balter
Un langage faiblement typé tel que Perl permet à l'ancien type de changement de valeur de se produire implicitement en fonction du contexte. Mais même C ++ autorise de telles conversions implicites via des définitions d'opérateurs. Le typage faible est une propriété très informelle et n'est vraiment pas un moyen utile de décrire les langues, comme l'a souligné Eric Lippert.
Jim Balter
PS On peut montrer que, même en Perl, <digits> et "<digits>" ont des valeurs différentes, pas seulement des types différents. Perl fait que <digits> et "<digits>" semblent avoir la même valeur dans la plupart des cas via des conversions implicites , mais l'illusion n'est pas complète; par exemple "12" | "34" vaut 36 alors que 12 | 34 est 46. Un autre exemple est que "00" est numériquement égal à 00 dans la plupart des contextes, mais pas dans un contexte booléen, où "00" est vrai mais 00 est faux.
Jim Balter
1

Comme beaucoup d'autres l'ont exprimé, toute la notion de typage "fort" vs "faible" pose problème.

En tant qu'archétype, Smalltalk est très fortement typé - il lèvera toujours une exception si une opération entre deux objets est incompatible. Cependant, je soupçonne que peu de personnes sur cette liste appelleraient Smalltalk un langage fortement typé, car il est typé dynamiquement.

Je trouve la notion de typage «statique» versus «dynamique» plus utile que «fort» par rapport à «faible». Un langage à typage statique a tous les types déterminés au moment de la compilation, et le programmeur doit déclarer explicitement le cas échéant.

Contraste avec un langage à typage dynamique, où la saisie est effectuée au moment de l'exécution. C'est généralement une exigence pour les langages polymorphes, de sorte que les décisions sur la légalité d'une opération entre deux objets ne doivent pas être décidées à l'avance par le programmeur.

Dans les langages polymorphes, typés dynamiquement (comme Smalltalk et Ruby), il est plus utile de penser à un «type» comme une «conformité au protocole». Si un objet obéit à un protocole de la même manière qu'un autre objet - même si les deux objets ne partagent aucun héritage ou mixins ou autre vaudou - ils sont considérés comme du même «type» par le système d'exécution. Plus correctement, un objet dans de tels systèmes est autonome et peut décider s'il est logique de répondre à un message particulier se référant à un argument particulier.

Vous voulez un objet qui peut apporter une réponse significative au message "+" avec un argument d'objet qui décrit la couleur bleue? Vous pouvez le faire dans des langues à typage dynamique, mais c'est une douleur dans les langues à typage statique.

Jan Steinman
la source
3
Je pense que le concept de typage dynamique vs statique n'est pas en discussion. Même si je dois dire que je ne crois pas que le polymorphisme soit de toute façon handicapé dans les langages de type statique. Finalement, le système de types vérifie si une opération donnée est applicable aux opérandes donnés, que ce soit à l'exécution ou à la compilation. En outre, d'autres formes de polymorphisme, comme les fonctions paramétriques et les classes, permettent de combiner des types dans des langages de type statique d'une manière que vous avez décrite comme très difficile par rapport à un type dynamiquement typé, encore plus agréable si l'inférence de type est fournie.
Edwin Dalorzo le
0

J'aime la réponse de @Eric Lippert , mais pour répondre à la question - les langages fortement typés ont généralement une connaissance explicite des types de variables à chaque point du programme. Les langages faiblement typés ne le font pas, ils peuvent donc tenter d'effectuer une opération qui peut ne pas être possible pour un type particulier. Il pense que le moyen le plus simple de voir cela est dans une fonction. C ++:

void func(string a) {...}

La variable aest connue pour être de type chaîne et toute opération incompatible sera interceptée au moment de la compilation.

Python:

def func(a)
  ...

La variable apeut être n'importe quoi et nous pouvons avoir du code qui appelle une méthode non valide, qui ne sera interceptée qu'au moment de l'exécution.

Lubo Antonov
la source
12
Je pense que vous pouvez confondre le typage dynamique et le typage statique avec un typage fort et un typage faible. Dans les deux versions de votre code, les systèmes de type runtime savent très bien que a est une chaîne. C'est juste que dans le premier cas, le compilateur peut vous dire que, dans le second, il ne peut pas. Mais cela ne rend aucun de ces langages faiblement typé.
Edwin Dalorzo