Pourquoi les langages typés dynamiquement ne permettent pas au développeur de spécifier le type?

14

Les langages typés dynamiquement que je connais ne permettent jamais aux développeurs de spécifier les types de variables, ou du moins ont un support très limité pour cela.

JavaScript, par exemple, ne fournit aucun mécanisme pour appliquer les types de variables lorsque cela est pratique. PHP vous permet de spécifier certains types d'arguments de méthode, mais il n'y a aucun moyen d'utiliser les types natifs ( int, string, etc.) pour les arguments, et il n'y a pas moyen de faire respecter les types pour autre chose que des arguments.

Dans le même temps, il serait pratique d'avoir le choix de spécifier dans certains cas le type d'une variable dans un langage typé dynamiquement, au lieu de faire la vérification de type manuellement.

Pourquoi existe-t-il une telle limitation? Est-ce pour des raisons techniques / de performances (je suppose que c'est dans le cas de JavaScript), ou uniquement pour des raisons politiques (ce qui est, je crois, le cas de PHP)? Est-ce un cas pour d'autres langues typées dynamiquement que je ne connais pas?


Edit: en suivant les réponses et les commentaires, voici un exemple de clarification: disons que nous avons la méthode suivante en PHP simple:

public function CreateProduct($name, $description, $price, $quantity)
{
    // Check the arguments.
    if (!is_string($name)) throw new Exception('The name argument is expected to be a string.');
    if (!is_string($description)) throw new Exception('The description argument is expected to be a string.');
    if (!is_float($price) || is_double($price)) throw new Exception('The price argument is expected to be a float or a double.');
    if (!is_int($quantity)) throw new Exception('The quantity argument is expected to be an integer.');

    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Avec quelques efforts, cela peut être réécrit comme (voir aussi Programmation par contrats en PHP ):

public function CreateProduct($name, $description, $price, $quantity)
{
    Component::CheckArguments(__FILE__, __LINE__, array(
        'name' => array('value' => $name, 'type' => VTYPE_STRING),
        'description' => array('value' => $description, 'type' => VTYPE_STRING),
        'price' => array('value' => $price, 'type' => VTYPE_FLOAT_OR_DOUBLE),
        'quantity' => array('value' => $quantity, 'type' => VTYPE_INT)
    ));

    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Mais la même méthode serait écrite comme suit si PHP acceptait éventuellement des types natifs pour les arguments:

public function CreateProduct(string $name, string $description, double $price, int $quantity)
{
    // Check the arguments.
    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Lequel est le plus court à écrire? Lequel est le plus facile à lire?

Arseni Mourzenko
la source
1
Vous pouvez éventuellement spécifier des types dans certaines langues à typage dynamique - par exemple, en Common Lisp.
SK-logic
Beaucoup de langages typés dynamiquement utilisent des transtypages pour forcer un type ...
Trezoid
Certains le font. Objective-C, par exemple, est typé dynamiquement, mais vous pouvez déclarer un type pour les variables et le compilateur émettra des avertissements si vous n'obtenez pas le type que vous attendez.
mipadi
1
Clojure est un exemple de langage qui est normalement typé dynamiquement mais vous pouvez éventuellement donner des types de variables via des "conseils de type" (cela n'est généralement fait que lorsque cela est nécessaire pour obtenir les avantages de performance des informations de type au moment de la compilation)
mikera
1
Groovy est un autre exemple de langage typé dynamiquement qui permet de spécifier un type.
Eric Wilson

Réponses:

17

Le point d'avoir un typage statique est la capacité de prouver statiquement que votre programme est correct en ce qui concerne les types (note: pas complètement correct dans tous les sens). Si vous disposez d'un système de type statique, vous pouvez détecter la plupart du temps des erreurs de type.

Si vous ne disposez que d'informations de type partielles, vous ne pouvez vérifier que les petits morceaux d'un graphique d'appel où les informations de type sont complètes. Mais vous avez passé du temps et des efforts à spécifier des informations de type pour les pièces incomplètes, où cela ne peut pas vous aider mais pourrait donner un faux sentiment de sécurité.

Pour exprimer des informations de type, vous avez besoin d'une partie du langage qui ne peut pas être excessivement simple. Bientôt, vous découvrirez que des informations comme intne suffisent pas; vous voudrez quelque chose comme List<Pair<Int, String>>, puis des types paramétriques, etc. Cela peut être assez déroutant même dans le cas assez simple de Java.

Ensuite, vous devrez gérer ces informations pendant la phase de traduction et la phase d'exécution, car il est idiot de ne vérifier que les erreurs statiques; l'utilisateur va s'attendre à ce que les contraintes de type tiennent toujours si elles sont spécifiées. Les langages dynamiques ne sont pas trop rapides comme ils le sont, et de tels contrôles ralentiront encore plus les performances. Un langage statique peut demander beaucoup d'efforts pour vérifier les types car il ne le fait qu'une seule fois; une langue dynamique ne peut pas.

Imaginez maintenant que vous ajoutiez et mainteniez tout cela afin que les gens utilisent parfois ces fonctionnalités en option , ne détectant qu'une petite fraction des erreurs de type. Je ne pense pas que cela en vaille la peine.

L'intérêt des langages dynamiques est d'avoir un cadre très petit et très malléable, dans lequel vous pouvez facilement faire des choses qui sont beaucoup plus impliquées quand elles sont faites dans un langage statique: diverses formes de patch de singe qui sont utilisées pour la métaprogrammation, la simulation et tests, remplacement dynamique de code, etc. Smalltalk et Lisp, tous deux très dynamiques, l'ont poussé à l'extrême au point d'envoyer des images d'environnement au lieu de construire à partir de la source. Mais lorsque vous voulez vous assurer que des chemins de données particuliers sont de type sécurisé, ajoutez des assertions et écrivez d'autres tests unitaires.

9000
la source
1
+1, bien que les tests ne peuvent montrer que des erreurs ne se produisent pas dans certaines situations. Ils remplacent mal une preuve que les erreurs (de type) sont impossibles.
Ingo
1
@Ingo: sûrement. Mais les langages dynamiques sont parfaits pour le bricolage et le prototypage rapide, où vous exprimez très rapidement des idées relativement simples. Si vous voulez du code de production à l'épreuve des balles, vous pouvez vous tourner vers un langage statique après avoir extrait certains composants de base stables.
9000
1
@ 9000, je ne doute pas qu'ils soient géniaux. Je voulais juste souligner que l'écriture de 3 ou 4 tests boiteux n'est pas et ne peut pas garantir la sécurité du type .
Ingo
2
@ 9000, c'est vrai, et la mauvaise nouvelle est que même alors, c'est pratiquement impossible. Même le code Haskell ou Agda repose sur des hypothèses, comme, par exemple, que la bibliothèque utilisée lors de l'exécution est correcte. Cela étant dit, dans un projet avec quelque 1000 LOC répartis sur une douzaine de fichiers de code source, c'est tellement cool quand on peut changer quelque chose et que l'on sait que le compilateur pointera sur chaque ligne où le changement a un impact.
Ingo
4
Des tests mal écrits ne remplacent pas la vérification de type statique: ils sont inférieurs. Des tests bien écrits ne remplacent pas non plus la vérification de type statique: ils sont supérieurs.
Rein Henrichs
8

Dans la plupart des langages dynamiques, vous pouvez au moins tester dynamiquement le type d'un objet ou d'une valeur.

Et il existe des inférenceurs de type statique, des vérificateurs et / ou des exécuteurs pour certains langages dynamiques: par exemple

Et Perl 6 prendra en charge un système de type optionnel avec une saisie statique.


Mais je suppose que l'essentiel est que beaucoup de gens utilisent des langages dynamiquement parce qu'ils sont typés dynamiquement, et pour eux le typage statique optionnel est très "ho hum". Et beaucoup d'autres personnes les utilisent parce qu'elles sont "faciles à utiliser pour les non-programmeurs", en grande partie en raison de la typographie dynamique de nature indulgente. Pour eux, la saisie facultative est quelque chose qu'ils ne comprendront pas ou ne seront pas gênés d'utiliser.

Si vous étiez cynique, vous pourriez dire que la saisie statique facultative offre le pire des deux mondes. Pour un fanatique de type statique, cela n'empêche pas toutes les défaillances de type dynamique. Pour un fan de type dynamique, c'est toujours une veste droite ... quoique avec les bretelles pas bien serrées.

Stephen C
la source
2
Il convient de noter que la vérification des types vous-même est désapprouvée par la plupart des communautés dans la plupart des circonstances. Utilisez le polymorphisme (en particulier, "typage du canard") lorsque vous traitez avec des hiérarchies d'objets, contraignez le type attendu si possible / sensé. Il reste quelques cas où il n'est tout simplement pas logique d'autoriser n'importe quel type, mais dans de nombreuses langues, vous obtenez une exception dans la plupart de ces cas, de sorte que la vérification de type est rarement utile.
4
"les gens utilisent généralement des langues dynamiquement parce qu'ils sont typés dynamiquement" : JavaScript est utilisé car c'est la seule langue prise en charge par la plupart des navigateurs. PHP est utilisé car il est populaire.
Arseni Mourzenko
2

Javascript prévoyait d'inclure un typage statique facultatif, et il semble que de nombreuses langues dynamiques matures se dirigent dans cette direction-

La raison en est que lorsque vous codez pour la première fois, vous voulez être saisi rapidement et dynamiquement. Une fois que votre code est solide, fonctionne et a de nombreux usages, vous souhaitez verrouiller la conception pour réduire les erreurs. (cela est avantageux pour les utilisateurs et les développeurs, car les premiers obtiendront une vérification d'erreur sur leurs appels et les seconds ne casseront pas les choses accidentellement.

Cela a du sens pour moi, car je trouve généralement trop de vérification de type au début d'un projet, trop peu à la fin de sa durée de vie, quelle que soit la langue que j'utilise;).

Macke
la source
Je ne connais pas ces plans pour inclure la saisie statique facultative dans JavaScript; mais j'espère qu'ils n'étaient pas aussi atroces que cela dans ActiveScript. le pire de JavaScript et de Java.
Javier
Il était prévu pour JS 4 (ou ECMAscript 4) mais cette version a été abandonnée en raison de la controverse. Je suis sûr que quelque chose de similaire apparaîtra à l'avenir, dans une langue. (En Python, vous pouvez en quelque sorte le faire avec des décorateurs, au fait.)
Macke
1
Les décorateurs ajoutent une vérification de type dynamique , une sorte d'assertions. Vous ne pouvez pas obtenir une vérification complète du type statique en Python, aussi difficile que vous essayiez, en raison de l'extrême dynamisme du langage.
9000
@ 9000: C'est exact. Cependant, je ne pense pas que la vérification de type dynamique soit mauvaise (mais je préférerais les comparaisons de type canard ala JS4), en particulier lorsqu'elle est combinée avec des tests unitaires, et la typification des décorateurs pourrait être plus utile pour les IDE de support / lint-checkers s'ils le faisaient standardisé.
Macke
bien sûr ce n'est pas mal! Ce n'est pas une question de morale. À un moment donné, le type doit être "vérifié" d'une manière ou d'une autre. Si vous écrivez * ((double *) 0x98765E) en C, le CPU le fera et vérifiera si 0x98765E est bien un pointeur vers un double.
Ingo
2

Objets Python faire ont un type.

Vous spécifiez le type lorsque vous créez l'objet.

Dans le même temps, il serait pratique d'avoir le choix de spécifier dans certains cas le type d'une variable dans un langage typé dynamiquement, au lieu de faire la vérification de type manuellement.

En fait, une vérification de type manuelle en Python est presque toujours une perte de temps et de code.

C'est simplement une mauvaise pratique d'écrire du code de vérification de type en Python.

Si un type inapproprié a été utilisé par un sociopathe malveillant, les méthodes ordinaires de Python lèveront une exception ordinaire lorsque le type ne convient pas.

Vous n'écrivez aucun code, votre programme échoue toujours avec un TypeError.

Il existe de très rares cas où vous devez déterminer le type au moment de l'exécution.

Pourquoi existe-t-il une telle limitation?

Puisqu'il ne s'agit pas d'une "limitation", la question n'est pas une vraie question.

S.Lott
la source
2
"Les objets Python ont un type." - Oh vraiment? Tout comme les objets Perl, les objets PHP et tous les autres éléments de données dans le monde. La différence entre le typage statique et dynamique n'est que lorsque le type va être vérifié, c'est-à-dire lorsque des erreurs de type se manifestent. S'ils apparaissent comme des erreurs de compilation, c'est du typage statique, s'ils apparaissent comme des erreurs d'exécution, c'est dynamique.
Ingo
@Ingo: Merci pour la clarification. Le problème est que les objets C ++ et Java peuvent être convertis d'un type à un autre, ce qui rend le type d'un objet quelque peu trouble, et donc la "vérification de type" dans ces compilateurs un peu trouble également. Lorsque la vérification de type Python - même si elle est exécutée - est beaucoup moins trouble. En outre, la question se rapproche de dire que les langues typées dynamiquement n'ont pas de types. La bonne nouvelle est que cela ne fait pas cette erreur courante.
S.Lott
1
Vous avez raison, les conversions de type (contrairement aux conversions de type, c'est-à-dire ((double) 42)) inversent le typage statique. Ils sont nécessaires lorsque le système de saisie n'est pas assez puissant. Avant Java 5, Java n'avait pas de types paramétrés, vous ne pouviez donc pas vous passer de transtypages de types. Aujourd'hui, c'est beaucoup mieux, mais le système de types manque encore de types plus élevés, sans parler de polymorphisme de rang supérieur. Je pense qu'il est fort possible que les langages typés dynamiquement apprécient autant d'abonnés précisément parce qu'ils en libèrent un de systèmes de types trop étroits.
Ingo
2

La plupart du temps, vous n'en avez pas besoin, du moins pas au niveau de détail que vous proposez. En PHP, les opérateurs que vous utilisez indiquent parfaitement ce que vous attendez des arguments; c'est un peu une erreur de conception bien que PHP convertisse vos valeurs si possible, même lorsque vous passez un tableau à une opération qui attend une chaîne, et parce que la conversion n'est pas toujours significative, vous obtenez parfois des résultats étranges ( et c'est exactement là que les vérifications de type sont utiles). En dehors de cela, peu importe si vous ajoutez des entiers 1et 5ou des chaînes "1"et "5"- le simple fait que vous utilisez le+L'opérateur signale à PHP que vous voulez traiter les arguments comme des nombres, et PHP obéira. Une situation intéressante est lorsque vous recevez des résultats de requête de MySQL: de nombreuses valeurs numériques sont simplement renvoyées sous forme de chaînes, mais vous ne le remarquerez pas car PHP les convertit pour vous chaque fois que vous les traitez comme des nombres.

Python est un peu plus strict sur ses types, mais contrairement à PHP, Python a eu des exceptions depuis le début et l'utilise de manière cohérente. Le paradigme «plus facile de demander pardon que permission» suggère d'effectuer simplement l'opération sans vérification de type, et de compter sur une exception levée lorsque les types n'ont pas de sens. Le seul inconvénient auquel je peux penser, c'est que parfois, vous constaterez que quelque part un type ne correspond pas à ce que vous attendez, mais trouver la raison peut être fastidieux.

Et il y a une autre raison à considérer: les langages dynamiques n'ont pas d' étape de compilation. Même si vous avez des contraintes de type, elles ne peuvent se déclencher qu'au moment de l'exécution, simplement parce qu'il n'y a pas de temps de compilation . Si vos vérifications entraînent de toute façon des erreurs d'exécution, il est beaucoup plus facile de les modéliser en conséquence: sous forme de vérifications explicites (comme is_XXX()en PHP ou typeofen javascript), ou en lançant des exceptions (comme Python). Fonctionnellement, vous avez le même effet (une erreur est signalée au moment de l'exécution lorsqu'un contrôle de type échoue), mais il s'intègre mieux avec le reste de la sémantique du langage. Il n'est tout simplement pas logique de traiter les erreurs de type fondamentalement différentes des autres erreurs d'exécution dans un langage dynamique.

tdammers
la source
0

Vous pouvez être intéressé par Haskell - son système de types déduit les types du code, et vous pouvez également spécifier des types.

daven11
la source
5
Haskell est une excellente langue. C'est en quelque sorte l'opposé des langages dynamiques: vous passez beaucoup de temps à décrire les types, et généralement une fois que vous avez déterminé vos types, le programme fonctionne :)
9000
@ 9000: En effet. Une fois compilé, il fonctionne généralement. :)
Macke
@Macke - pour différentes valeurs de ordinaire , bien sûr. :-) Pour moi, le plus grand avantage du système de type et du paradigme fonctionnel est, comme je l'ai souligné ailleurs, que l'on n'a pas à se soucier si un changement quelque part affecte silencieusement un code dépendant ailleurs - le compilateur signalera les erreurs de type et l'état mutable n'existe tout simplement pas.
Ingo
0

Comme les autres réponses ont fait allusion, il existe deux approches pour taper lors de l'implémentation d'un langage de programmation.

  1. Demandez au programmeur de vous dire ce que toutes les variables et fonctions utilisent pour les types. Idéalement, vous vérifiez également que les spécifications de type sont exactes. Ensuite, comme vous savez quel type de chose sera à chaque endroit, vous pouvez écrire du code qui suppose que la chose appropriée sera là et utilise la structure de données que vous utilisez pour implémenter directement ce type.
  2. Attachez un indicateur de type aux valeurs qui seront stockées dans des variables et transmises et renvoyées par les fonctions. Cela signifie que le programmeur n'aura pas à spécifier de types pour les variables ou les fonctions, car les types appartiennent en fait aux objets auxquels les variables et les fonctions se réfèrent.

Les deux approches sont valides et celles à utiliser dépendent en partie de considérations techniques comme les performances et en partie de raisons politiques comme le marché cible de la langue.

Larry Coleman
la source
0

Tout d'abord, des langages dynamiques ont été créés principalement pour la facilité d'utilisation. Comme vous l'avez mentionné, il est vraiment bon de prendre la conversion de type automatiquement et de nous fournir moins de frais généraux. Mais en même temps, il manque de problèmes de performances.

Vous pouvez vous en tenir aux langages dynamiques, au cas où vous ne vous souciez pas des performances. Supposons, par exemple, que JavaScript s'exécute plus lentement lorsqu'il doit effectuer plusieurs conversions de types dans votre programme, mais cela aide à réduire le nombre de lignes dans votre code.

Et pour être mentionné, il existe même d'autres langages dynamiques qui permettent au programmeur de spécifier le type. Par exemple Groovy est l'un des fameux langages dynamiques qui s'exécutent sur JVM. Et il est même devenu très célèbre ces derniers jours. Notez que les performances de Groovy sont identiques à celles de Java.

J'espère que cela vous aide.

Fourmis
la source
-1

Cela n'a tout simplement aucun sens.

Pourquoi?

Parce que le système de types de DTL est précisément tel que les types ne peuvent pas être déterminés au moment de la compilation. Par conséquent, le compilateur n'a même pas pu vérifier que le type spécifié aurait du sens.

Ingo
la source
1
Pourquoi? Il est parfaitement logique d'indiquer à un compilateur les types à attendre. Il ne contredira aucune des contraintes du système de type.
SK-logic
1
SK-logic: Si typé dynamiquement signifie que chaque fonction / méthode / opération prend des objets de type "Dynamic", "Any" ou autre et retourne "Dynamic", "Any" que ce soit, il n'y a en général aucun moyen de dire qu'une certaine valeur sera toujours un entier, par exemple. Par conséquent, le code d'exécution doit de toute façon rechercher des non-entiers, tout comme si le type était "Dynamic" en premier lieu. C'est exactement ce que fait un système de type statique: il permet de prouver qu'un certain retour de variable, de champ ou de méthode sera toujours d'un certain type.
Ingo
@Ingo, non, il y a un moyen. Voyez comment il est implémenté en Common Lisp, par exemple. Il est particulièrement utile pour les variables locales - vous pouvez augmenter considérablement les performances en introduisant tous ces conseils de frappe.
SK-logic
@ SK-logic: Peut-être pouvez-vous me dire quand et comment les erreurs de type sont détectées dans CL? Quoi qu'il en soit, @MainMa a très bien résumé le statu quo dans sa question: c'est exactement ce que l'on peut attendre de langages "purement" dynamiques.
Ingo
@Ingo, qu'est-ce qui vous fait penser que les types ne sont utiles que pour prouver statiquement l'exactitude? Ce n'est pas vrai même pour les langages comme C, où vous avez un casting de type non contrôlé. Les annotations de type dans les langages dynamiques sont principalement utiles en tant que conseils de compilation qui améliorent les performances ou spécifient une représentation numérique concrète. Je suis d'accord que dans la plupart des cas, les annotations ne devraient pas changer la sémantique du code.
SK-logic
-1

Jetez un œil à Go, en surface, il est typé statiquement, mais ces types peuvent être des interfaces qui sont essentiellement dynamiques.

dan_waterworth
la source