J'utilise Python depuis quelques jours maintenant et je pense comprendre la différence entre le typage dynamique et statique. Ce que je ne comprends pas, c’est dans quelles circonstances on le préférerait. Il est flexible et lisible, mais au détriment de davantage de contrôles d’exécution et de tests unitaires supplémentaires requis.
Outre les critères non fonctionnels tels que la flexibilité et la lisibilité, quelles sont les raisons pour choisir le typage dynamique? Que puis-je faire avec la frappe dynamique qui n'est pas possible autrement? Quel exemple de code spécifique pouvez-vous imaginer qui illustre un avantage concret du typage dynamique?
dynamic-typing
static-typing
Justin984
la source
la source
Réponses:
Puisque vous avez demandé un exemple spécifique, je vais vous en donner un.
Massive ORM de Rob Conery est de 400 lignes de code. C'est aussi petit parce que Rob est capable de mapper des tables SQL et de fournir des résultats d'objet sans nécessiter beaucoup de types statiques pour mettre en miroir les tables SQL. Ceci est accompli en utilisant le
dynamic
type de données en C #. La page Web de Rob décrit ce processus en détail, mais il semble clair que, dans ce cas d'utilisation particulier, le typage dynamique est en grande partie responsable de la brièveté du code.Comparez avec Dapper de Sam Saffron , qui utilise des types statiques; la
SQLMapper
classe à elle seule est constituée de 3000 lignes de code.Notez que les exclusions habituelles s'appliquent et que votre kilométrage peut varier. Dapper a des objectifs différents de ceux de Massive. Je souligne juste ceci comme un exemple de quelque chose que vous pouvez faire dans 400 lignes de code, ce qui ne serait probablement pas possible sans frappe dynamique.
Le typage dynamique vous permet de reporter vos décisions de type à l'exécution. C'est tout.
Que vous utilisiez un langage à typage dynamique ou un langage à typage statique, vos choix de type doivent toujours être judicieux. Vous n'allez pas ajouter deux chaînes ensemble et attendre une réponse numérique à moins que les chaînes ne contiennent des données numériques, et si elles ne le font pas, vous obtiendrez des résultats inattendus. Un langage typé statiquement ne vous laissera pas faire cela en premier lieu.
Les partisans des langages de type statique indiquent que le compilateur peut effectuer une quantité substantielle de "vérification de l'intégrité" de votre code au moment de la compilation, avant qu'une seule ligne ne s'exécute. C'est une bonne chose ™.
C # a le
dynamic
mot - clé, ce qui vous permet de reporter la décision de type à l'exécution sans perdre les avantages de la sécurité de type statique dans le reste de votre code. L'inférence de type (var
) élimine une grande partie de la douleur liée à l'écriture dans un langage de type statique en supprimant la nécessité de toujours déclarer explicitement les types.Les langages dynamiques semblent favoriser une approche plus interactive et immédiate de la programmation. Personne ne s'attend à ce que vous deviez écrire une classe et effectuer un cycle de compilation pour taper un peu de code Lisp et le regarder s'exécuter. Pourtant, c'est exactement ce que je suis censé faire en C #.
la source
Des expressions telles que "typage statique" et "typage dynamique" sont très répandues et les gens ont tendance à utiliser des définitions légèrement différentes, alors commençons par clarifier ce que nous voulons dire.
Prenons un langage dont les types statiques sont vérifiés au moment de la compilation. Mais disons qu'une erreur de type ne génère qu'un avertissement non fatal et qu'au moment de l'exécution, tout est tapé. Ces types statiques sont uniquement destinés au programmeur et n'affectent pas le codegen. Cela montre que le typage statique n’impose pas en lui-même de limitation et n’exclut pas le typage dynamique. (Objective-C est un peu comme ça.)
Mais la plupart des systèmes de types statiques ne se comportent pas de cette façon. Deux propriétés communes des systèmes de types statiques peuvent imposer des limitations:
Le compilateur peut rejeter un programme contenant une erreur de type statique.
Ceci est une limitation car de nombreux programmes de type sécurisé contiennent nécessairement une erreur de type statique.
Par exemple, j'ai un script Python qui doit fonctionner à la fois en Python 2 et en Python 3. Certaines fonctions ont changé leurs types de paramètres entre Python 2 et 3, j'ai donc un code comme celui-ci:
Un vérificateur de type statique Python 2 rejetterait le code Python 3 (et vice versa), même s'il ne serait jamais exécuté. Mon programme de type sécurisé contient une erreur de type statique.
Comme autre exemple, considérons un programme Mac qui veut fonctionner sur OS X 10.6, mais tire parti des nouvelles fonctionnalités de 10.7. Les méthodes 10.7 peuvent ou non exister au moment de l'exécution, et c'est à moi, le programmeur, de les détecter. Un vérificateur de type statique est forcé de rejeter mon programme pour garantir la sécurité du type ou de l'accepter, ainsi que de la possibilité de générer une erreur de type (fonction manquante) au moment de l'exécution.
La vérification de type statique suppose que l'environnement d'exécution est correctement décrit par les informations de compilation. Mais prédire l'avenir est périlleux!
Voici une autre limitation:
Le compilateur peut générer du code qui suppose que le type à l'exécution est le type statique.
En supposant que les types statiques soient "corrects", il existe de nombreuses possibilités d'optimisation, mais ces optimisations peuvent être limitantes. Un bon exemple est celui des objets proxy, tels que la communication à distance. Supposons que vous souhaitiez avoir un objet proxy local qui transfère les appels de méthode à un objet réel dans un autre processus. Ce serait bien si le proxy était générique (afin qu'il puisse se faire passer pour n'importe quel objet) et transparent (afin que le code existant n'ait pas besoin de savoir qu'il parle à un proxy). Mais pour ce faire, le compilateur ne peut pas générer de code supposant que les types statiques sont corrects, par exemple, en appliquant statiquement des appels de méthode, car cela échouera si l’objet est réellement un proxy.
NSXPCConnection d’ ObjC ou TransparentProxy de C # (dont la mise en œuvre a nécessité quelques pessimisations au cours de l’exécution - voir la discussion ici pour une discussion).
Lorsque codegen ne dépend pas des types statiques et que vous disposez d'installations telles que le transfert de messages, vous pouvez effectuer de nombreuses tâches intéressantes avec des objets proxy, le débogage, etc.
C’est donc un exemple de ce que vous pouvez faire si vous n’êtes pas tenu de satisfaire un vérificateur de type. Les limitations ne sont pas imposées par des types statiques, mais par une vérification de type statique appliquée.
la source
A Python 2 static type checker would reject the Python 3 code (and vice versa), even though it would never be executed. My type safe program contains a static type error.
Dans n'importe quel langage statique raisonnable, vous pouvez le faire avec uneIFDEF
instruction de préprocesseur de type, tout en maintenant la sécurité de type dans les deux cas.Les variables de type canard sont la première chose à laquelle tout le monde pense, mais dans la plupart des cas, vous pouvez obtenir les mêmes avantages grâce à l'inférence de type statique.
Cependant, il est difficile de taper des canards dans des collections créées de manière dynamique:
Alors, quel type
JSON.parse
revient? Un dictionnaire de tableaux de nombres entiers-ou-dictionnaires-de-chaînes? Non, même cela n'est pas assez général.JSON.parse
doit renvoyer une sorte de "valeur de variante" pouvant être null, bool, float, string, un tableau de l'un de ces types récursivement ou un dictionnaire de chaîne à l'un de ces types récursivement. Les principaux atouts du typage dynamique proviennent de l'existence de tels types de variantes.Jusqu'à présent, il s'agit d'un avantage des types dynamiques , et non des langages à typage dynamique. Un langage statique décent peut parfaitement simuler un tel type. (Et même les "mauvais" langages peuvent souvent les simuler en cassant la sécurité de type sous le capot et / ou en exigeant une syntaxe d'accès maladroite.)
L'avantage des langages à typage dynamique est que de tels types ne peuvent pas être déduits par des systèmes d'inférence de type statique. Vous devez écrire le type explicitement. Mais dans de nombreux cas, y compris cette fois-ci, le code pour décrire le type est exactement aussi compliqué que le code pour analyser / construire les objets sans décrire le type, de sorte que ce n'est toujours pas un avantage.
la source
Tous les systèmes de types statiques pratiques à distance étant très limités par rapport au langage de programmation qui les concerne, il ne peut pas exprimer tous les invariants que le code pourrait vérifier au moment de l'exécution. Afin de ne pas contourner les garanties qu'un système de types tente de donner, il choisit donc d'être conservateur et d'interdire les cas d'utilisation permettant de passer ces contrôles, mais ne peut pas (dans le système de types) être prouvé.
Je vais faire un exemple. Supposons que vous implémentiez un modèle de données simple pour décrire des objets de données, leurs collections, etc., typés statiquement en ce sens que, si le modèle indique que l'attribut
x
d'objet de type Foo contient un entier, il doit toujours contenir un entier. Comme il s'agit d'une construction à l'exécution, vous ne pouvez pas la taper de manière statique. Supposons que vous stockiez les données décrites dans les fichiers YAML. Vous créez une carte de hachage (à remettre à une bibliothèque YAML ultérieurement), obtenez l'x
attribut, stockez-la dans la carte, obtenez cet autre attribut qui se trouve être une chaîne, ... vous en attendez une seconde? Quel est le type dethe_map[some_key]
maintenant? Eh bien, nous savons quesome_key
c'est le cas'x'
et le résultat doit donc être un entier, mais le système de types ne peut même pas commencer à raisonner à ce sujet.Certains systèmes de types activement recherchés peuvent fonctionner pour cet exemple spécifique, mais ils sont extrêmement compliqués (à la fois pour les écrivains du compilateur à implémenter et pour le raisonnement du programmeur), en particulier pour quelque chose d'aussi "simple" (je veux dire, je viens de l'expliquer dans un paragraphe).
Bien sûr, la solution actuelle consiste à tout mettre en boîte, puis à lancer (ou à utiliser un tas de méthodes surchargées, dont la plupart soulèvent des exceptions "non implémentées"). Mais ceci n’est pas typé de manière statique, c’est un hack autour du système de types pour effectuer les vérifications de types au moment de l’exécution.
la source
Avec le typage dynamique, vous ne pouvez rien faire avec le typage statique, car vous pouvez implémenter le typage dynamique au-dessus d'un langage à typage statique.
Un petit exemple à Haskell:
Avec suffisamment de cas, vous pouvez implémenter n'importe quel système de type dynamique.
Inversement, vous pouvez également traduire tout programme de type statique en un programme dynamique équivalent. Bien entendu, vous perdriez toutes les assurances de correction que fournit le langage de type statique au moment de la compilation.
Edit: Je voulais garder cela simple, mais voici plus de détails sur un modèle d'objet
Une fonction prend une liste de données en tant qu'arguments, effectue des calculs avec des effets secondaires dans ImplMonad et renvoie une donnée.
DMember
est soit une valeur membre, soit une fonction.Étendez-vous
Data
pour inclure des objets et des fonctions. Les objets sont des listes de membres nommés.Ces types statiques sont suffisants pour implémenter tous les systèmes d'objets typés dynamiquement avec lesquels je suis familier.
la source
Data
.+
opérateur qui combine deuxData
valeurs dans une autreData
valeur.Data
représente les valeurs standard dans le système de types dynamiques.Membranes :
Chaque type est enveloppé par un type ayant la même interface, mais qui intercepte les messages et encapsule les valeurs au fur et à mesure qu'ils traversent la membrane. Quel est le type de la fonction de bouclage dans votre langage favori statiquement typé? Peut-être que Haskell a un type pour ces fonctions, mais la plupart des langages statiquement typés ne le font pas ou finissent par utiliser Object → Object, ce qui leur permet de s'affranchir de leur responsabilité en tant que vérificateurs de type.
la source
class Foo a where ...
data Wrapper = forall a. Foo a => Wrapper a
String
puisqu'il s'agit d'un type concret en Java. Smalltalk n'a pas ce problème car il ne tente pas de taper#doesNotUnderstand
.Comme quelqu'un l'a mentionné, en théorie, vous ne pouvez pas faire grand chose avec le typage dynamique, contrairement au typage statique si vous implémentiez vous-même certains mécanismes. La plupart des langues fournissent les mécanismes de relaxation de type pour prendre en charge la flexibilité de type, comme les pointeurs vides et le type d'objet racine ou une interface vide.
La meilleure question est de savoir pourquoi le typage dynamique est plus approprié et plus approprié dans certaines situations et certains problèmes.
Tout d'abord, permet de définir
Entity - J'aurais besoin d'une notion générale d'une entité dans le code. Cela peut être n'importe quoi, du nombre primitif aux données complexes.
Comportement - disons que notre entité a un certain état et un ensemble de méthodes qui permettent au monde extérieur de lui demander certaines réactions. Permet d'appeler l'état + interface de cette entité son comportement. Une même entité peut avoir plus d'un comportement combiné d'une certaine manière par les outils fournis par le langage.
Définitions des entités et de leurs comportements - chaque langage fournit des moyens d’abstractions permettant de définir les comportements (ensemble de méthodes + état interne) de certaines entités du programme. Vous pouvez attribuer un nom à ces comportements et indiquer que toutes les instances ayant ce comportement sont d'un certain type .
C'est probablement quelque chose qui n'est pas si inconnu. Et comme vous l'avez dit, vous avez compris la différence, mais quand même. Explication probablement pas complète et très précise, mais j'espère être suffisamment amusante pour apporter un peu de valeur :)
Saisie statique - le comportement de toutes les entités de votre programme est examiné au moment de la compilation, avant que le code ne soit lancé. Cela signifie que si vous voulez, par exemple, que votre entité de type Personne ait un comportement (se comporte comme) un magicien, vous devez définir une entité comme MagicianPerson et lui attribuer le comportement d'un magicien tel que throwMagic (). Si, dans votre code, vous indiquez à tort au compilateur ordinaire de Person.throwMagic (), il vous le dira.
"Error >>> hell, this Person has no this behavior, dunno throwing magics, no run!".
Typage dynamique - Dans les environnements de typage dynamiques, les comportements disponibles des entités ne sont vérifiés que lorsque vous essayez réellement de faire quelque chose avec certaines entités. L'exécution du code Ruby qui demande à Person.throwMagic () ne sera pas interceptée tant que votre code n'y arrivera pas vraiment. Cela semble frustrant, n'est-ce pas. Mais cela semble aussi révélateur. Sur la base de cette propriété, vous pouvez faire des choses intéressantes. Par exemple, disons que vous concevez un jeu où tout peut tourner à Magician et que vous ne savez pas vraiment à qui il appartiendra, jusqu'à ce que vous arriviez à la limite du code. Et puis la grenouille vient et vous dites
HeyYouConcreteInstanceOfFrog.include Magic
et à partir de là, cette grenouille devient une grenouille particulière dotée de pouvoirs magiques. D'autres grenouilles, toujours pas. Vous voyez, dans les langages de typage statiques, vous devez définir cette relation par un moyen standard de combinaison de comportements (comme l'implémentation d'interface). Dans un langage de frappe dynamique, vous pouvez le faire en mode d'exécution et personne ne s'en souciera.La plupart des langages de frappe dynamiques ont des mécanismes pour fournir un comportement générique qui capture tout message transmis à leur interface. Par exemple, Ruby
method_missing
et PHP__call
si mes souvenirs sont bons. Cela signifie que vous pouvez faire toutes sortes de choses intéressantes dans l'exécution du programme et prendre une décision de type en fonction de l'état actuel du programme. Cela apporte des outils pour la modélisation d'un problème beaucoup plus souples que dans un langage de programmation statique conservateur comme Java.la source