Il y a des problèmes importants qui, je pense, n'ont pas été résolus dans toutes les réponses existantes.
Un typage faible signifie permettre l'accès à la représentation sous-jacente. En C, je peux créer un pointeur sur des caractères, puis dire au compilateur que je veux l'utiliser comme pointeur sur des entiers:
char sz[] = "abcdefg";
int *i = (int *)sz;
Sur une plate-forme little-endian avec des entiers 32 bits, cela se i
transforme en un tableau des nombres 0x64636261
et 0x00676665
. En fait, vous pouvez même convertir des 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, je ne peux donc qu'écraser la mémoire de mon propre processus, mais il n'y a rien sur C lui-même qui offre une telle protection, comme toute personne qui a déjà codé, disons, Classic Mac OS ou Win16 peut vous le dire.
Le Lisp traditionnel permettait des types de piratage similaires; sur certaines plates-formes, les flottants à double mot et les contre-cellules étaient du même type, et vous pouviez simplement passer l'un à une fonction qui attendait l'autre et cela "fonctionnerait".
Aujourd'hui, la plupart des langages ne sont pas aussi faibles que C et Lisp, mais beaucoup d'entre eux sont encore quelque peu fuyants. Par exemple, tout langage OO qui a un "downcast" non contrôlé *, c'est une fuite de type: vous dites essentiellement au compilateur "Je sais que je ne vous ai pas donné suffisamment d'informations pour savoir que cela est sûr, mais je suis à peu près sûr c'est ", lorsque le point essentiel d'un système de type est que le compilateur a toujours suffisamment d'informations pour savoir ce qui est sûr.
* Un abaissé vérifié n'affaiblit pas le système de type de la langue simplement parce qu'il déplace la vérification vers l'exécution. Si c'était le cas, le polymorphisme de sous-type (alias appels de fonction virtuels ou entièrement dynamiques) serait la même violation du système de type, et je ne pense pas que quiconque veuille le dire.
Très peu de langages de "scripting" sont faibles dans ce sens. Même en Perl ou Tcl, vous ne pouvez pas prendre une chaîne et simplement interpréter ses octets comme un entier. * Mais il convient de noter qu'en CPython (et de même pour de nombreux autres interprètes pour de nombreuses langues), si vous êtes vraiment persistant, vous peut utiliser ctypes
pour charger libpython
, lancer un objet id
en un POINTER(Py_Object)
et forcer le système de saisie à fuir. Que cela rende le système de type faible ou non dépend de vos cas d'utilisation - si vous essayez d'implémenter un sandbox d'exécution restreinte en langage pour garantir la sécurité, vous devez faire face à ce genre d'évasions…
* Vous pouvez utiliser une fonction comme struct.unpack
pour lire les octets et construire un nouvel entier à partir de "comment C représenterait ces octets", mais ce n'est évidemment pas une fuite; même Haskell le permet.
Pendant ce temps, la conversion implicite est vraiment différente d'un système de type faible ou qui fuit.
Chaque langue, même Haskell, a des fonctions pour, par exemple, convertir un entier en chaîne ou en flottant. Mais certaines langues effectueront automatiquement certaines de ces conversions pour vous - par exemple, en C, si vous appelez une fonction qui veut un float
, et que vous la transmettez int
, elle est convertie pour vous. Cela peut certainement entraîner des bogues avec, par exemple, des débordements inattendus, mais ce ne sont pas les mêmes types de bogues que vous obtenez d'un système de type faible. Et C n'est pas vraiment plus faible ici; vous pouvez ajouter un int et un float dans Haskell, ou même concaténer un float à une chaîne, il vous suffit de le faire plus explicitement.
Et avec les langages dynamiques, c'est assez trouble. Il n'y a rien de tel qu'une "fonction qui veut un flottant" en Python ou Perl. Mais il y a des fonctions surchargées qui font différentes choses avec différents types, et il y a un fort sentiment intuitif que, par exemple, ajouter une chaîne à autre chose est "une fonction qui veut une chaîne". En 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
déclenche une exception, mais 1.0 + 1
vous donne 2.0
*). Il est juste difficile de mettre ce sens en termes formels - pourquoi ne devrait-il pas y avoir un +
qui prend une chaîne et un int, alors qu'il y a évidemment d'autres fonctions, comme l'indexation, qui le font?
* En fait, en Python moderne, cela peut être expliqué en termes de sous-typage OO, car isinstance(2, numbers.Real)
c'est vrai. Je ne pense pas qu'il y ait un sens dans lequel se 2
trouve une instance du type chaîne en Perl ou JavaScript… bien qu'en Tcl, ce soit le cas, puisque tout est une instance de chaîne.
Enfin, il existe une autre définition, complètement orthogonale, du typage «fort» contre «faible», où «fort» signifie puissant / flexible / expressif.
Par exemple, Haskell vous permet de définir un type qui est un nombre, une chaîne, une liste de ce type ou une mappe de chaînes à ce type, ce qui est parfaitement un moyen 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), vous pouvez donc écrire une fonction qui prend une liste de T et savoir que les éléments sont de type T; d'autres langages, comme Java, vous obligeaient à utiliser une liste d'objets et à abattre. Mais au moins Java vous permet de créer de nouveaux types avec leurs propres méthodes; C vous permet uniquement de créer des structures. Et BCPL n'en avait même pas. Et ainsi de suite jusqu'à l'assemblage, où les seuls types sont des longueurs de bits différentes.
Donc, dans ce sens, le système de type de Haskell est plus fort que le Java moderne, qui est plus fort que le Java précédent, qui est plus fort que le C, qui est plus fort que le BCPL.
Alors, où Python s'intègre-t-il dans ce spectre? C'est un peu délicat. Dans de nombreux cas, le typage canard vous permet de simuler tout ce que vous pouvez faire dans Haskell, et même certaines choses que vous ne pouvez pas; Bien sûr, les erreurs sont détectées lors de l'exécution au lieu de la compilation, mais elles sont toujours détectées. Cependant, il y a des cas où la frappe de canard n'est pas suffisante. Par exemple, dans Haskell, vous pouvez dire qu'une liste vide d'entiers est une liste d'entiers, vous pouvez donc décider que la réduction +
sur cette liste devrait retourner 0 *; en Python, une liste vide est une liste vide; il n'y a aucune information de type pour vous aider à décider ce que la réduction +
devrait faire.
* En fait, Haskell ne vous laisse pas faire cela; si vous appelez la fonction de réduction qui ne prend pas de valeur de départ dans une liste vide, vous obtenez une erreur. Mais son système de type est suffisamment puissant pour que vous puissiez faire fonctionner cela, contrairement à Python.