Il y a des questions importantes que, selon moi, toutes les réponses existantes ont négligées.
Le typage faible permet d'accéder à la représentation sous-jacente. En C, je peux créer un pointeur sur des caractères, puis indiquer au compilateur que je veux l'utiliser comme un pointeur sur des entiers :
char sz[] = "abcdefg";
int *i = (int *)sz;
Sur une plate-forme little-endian avec des entiers de 32 bits, cela donne i
dans un tableau de nombres 0x64636261
et 0x00676665
. En fait, vous pouvez même convertir les pointeurs eux-mêmes en entiers (de la taille appropriée) :
intptr_t i = (intptr_t)&sz;
Et bien sûr, cela signifie que je peux écraser la mémoire n'importe où dans le système. *
char *spam = (char *)0x12345678
spam[0] = 0;
* Bien sûr, les systèmes d'exploitation modernes utilisent la mémoire virtuelle et la protection des pages, de sorte que je ne peux écraser que la mémoire de mon propre processus, mais il n'y a rien dans le C lui-même qui offre une telle protection, comme toute personne qui a déjà codé sur, disons, Classic Mac OS ou Win16 peut vous le dire.
Le Lisp traditionnel permettait des piratages similaires ; sur certaines plates-formes, les flottants à double mot et les cellules de type cons étaient du même type, et vous pouviez simplement passer l'un à une fonction qui attendait l'autre et cela "fonctionnait".
La plupart des langages actuels ne sont pas aussi faibles que C et Lisp l'étaient, mais beaucoup d'entre eux sont encore quelque peu perméables. Par exemple, tout langage OO qui possède un "downcast" non coché*, est une fuite de type : vous dites essentiellement au compilateur "Je sais que je ne vous ai pas donné assez d'informations pour savoir que ceci est sûr, mais je suis presque sûr que ça l'est", alors que le but d'un système de types est que le compilateur ait toujours assez d'informations pour savoir ce qui est sûr.
* Un downcast vérifié ne rend pas le système de types du langage plus faible simplement parce qu'il déplace la vérification au moment de l'exécution. Si c'était le cas, alors le polymorphisme de sous-type (c'est-à-dire les appels de fonctions virtuelles ou entièrement dynamiques) serait la même violation du système de types, et je ne pense pas que quiconque veuille dire cela.
Très peu de langages de "script" sont faibles dans ce sens. Même en Perl ou en Tcl, vous ne pouvez pas prendre une chaîne de caractères et interpréter ses octets comme un nombre entier.* Mais il est intéressant de noter que dans CPython (et de la même manière pour de nombreux autres interpréteurs pour de nombreux langages), si vous êtes vraiment persévérant, vous pouvez utiliser la fonction ctypes
pour charger libpython
l'image de l'objet id
à un POINTER(Py_Object)
et forcer le système de type à fuir. Que cela rende le système de types faible ou non dépend de vos cas d'utilisation - si vous essayez d'implémenter un bac à sable d'exécution restreinte dans le langage pour assurer la sécurité, vous devez faire face à ce genre d'échappatoires
* Vous pouvez utiliser une fonction comme <code>struct.unpack</code> pour lire les octets et construire un nouveau int à partir de "comment C représenterait ces octets", mais ce n'est évidemment pas une fuite ; même Haskell le permet.
Entre-temps, la conversion implicite est vraiment différente d'un système de type faible ou peu fiable.
Chaque langage, même Haskell, possède des fonctions permettant, par exemple, de convertir un entier en chaîne de caractères ou en flottant. Mais certains langages effectuent automatiquement certaines de ces conversions pour vous - par exemple, en C, si vous appelez une fonction qui veut une valeur de float
et vous le passez dans int
il est converti pour vous. Cela peut certainement conduire à des bogues avec, par exemple, des débordements inattendus, mais ce ne sont pas les mêmes types de bogues que l'on obtient avec un système de types faible. Et le C n'est pas vraiment plus faible ici ; vous pouvez ajouter un int et un float en Haskell, ou même concaténer un float à une chaîne, vous devez juste le faire plus explicitement.
Et avec les langages dynamiques, c'est assez obscur. Il n'existe pas de "fonction qui veut un flottant" en Python ou en Perl. Mais il existe des fonctions surchargées qui font différentes choses avec différents types, et il y a un sens intuitif fort que, par exemple, l'ajout d'une chaîne à quelque chose d'autre est "une fonction qui veut une chaîne". Dans ce sens, Perl, Tcl et JavaScript semblent faire beaucoup de conversions implicites ( "a" + 1
vous donne "a1"
), tandis que Python en fait beaucoup moins ( "a" + 1
lève une exception, mais 1.0 + 1
vous donne 2.0
*). Il est difficile d'exprimer ce sens en termes formels - pourquoi n'y aurait-il pas un système d'échange de données ? +
qui prend une chaîne et un int, alors qu'il existe manifestement d'autres fonctions, comme l'indexation, qui le font ?
* En fait, dans le Python moderne, cela peut s'expliquer en termes de sous-typage OO, car <code>isinstance(2, numbers.Real)</code> est vrai. Je ne pense pas qu'il y ait un sens dans lequel <code>2</code> est une instance du type chaîne en Perl ou en JavaScript bien qu'en Tcl, c'est en fait le cas, puisque <em>tout </em>est une instance de chaîne de caractères.
Enfin, il existe une autre définition, totalement orthogonale, de la typographie "forte" par rapport à la typographie "faible", où "forte" signifie puissante/flexible/expressive.
Par exemple, Haskell vous permet de définir un type qui est un nombre, une chaîne de caractères, une liste de ce type, ou une carte de chaînes de caractères vers ce type, ce qui est une façon parfaite de représenter tout ce qui peut être décodé à partir de JSON. Il n'y a aucun moyen de définir un tel type en Java. Mais au moins Java a des types paramétriques (génériques), donc vous pouvez écrire une fonction qui prend une liste de T et savoir que les éléments sont de type T ; d'autres langages, comme les premiers Java, vous ont forcé à utiliser une liste d'objets et à baisser les bras. Mais au moins, Java vous permet de créer de nouveaux types avec leurs propres méthodes ; le C ne vous permet que de créer des structures. Et BCPL n'avait même pas ça. Et ainsi de suite jusqu'à l'assemblage, où les seuls types sont des longueurs de bits différentes.
Ainsi, dans ce sens, le système de types de Haskell est plus fort que celui de Java moderne, qui est plus fort que celui de Java antérieur, qui est plus fort que celui de C, qui est plus fort que celui de BCPL.
Alors, où se situe Python dans ce spectre ? C'est un peu délicat. Dans de nombreux cas, le duck typing vous permet de simuler tout ce que vous pouvez faire en Haskell, et même certaines choses que vous ne pouvez pas faire ; bien sûr, les erreurs sont attrapées au moment de l'exécution plutôt qu'au moment de la compilation, mais elles sont quand même attrapées. Cependant, il existe des cas où le typage en canard n'est pas suffisant. Par exemple, en Haskell, vous pouvez dire qu'une liste vide d'ints est une liste d'ints, et vous pouvez donc décider que la réduction d'une liste d'ints à une liste d'ints n'est pas suffisante. +
sur cette liste devrait retourner 0* ; en Python, une liste vide est une liste vide ; il n'y a pas d'information de type pour vous aider à décider ce qui réduit +
sur ce qu'il devrait faire.
* En fait, Haskell ne vous permet pas de le faire ; si vous appelez la fonction reduce qui ne prend pas de valeur de départ sur une liste vide, vous obtenez une erreur. Mais son système de types est suffisamment puissant pour que vous <em>pourrait </em>pour que ça marche, et celle de Python ne l'est pas.