Les langues à typage dynamique sont uni-typées
En comparant les systèmes de types , il n'y a aucun avantage à taper dynamiquement. La frappe dynamique est un cas particulier de frappe statique - c'est un langage à frappe statique où chaque variable a le même type. Vous pouvez obtenir la même chose en Java (moins la concision) en faisant que chaque variable soit de type Object
et en ayant des valeurs "objet" de type Map<String, Object>
:
void makeItBark(Object dog) {
Map<String, Object> dogMap = (Map<String, Object>) dog;
Runnable bark = (Runnable) dogMap.get("bark");
bark.run();
}
Ainsi, même sans réflexion, vous pouvez obtenir le même effet dans à peu près n'importe quel langage de type statique, mis à part la commodité syntaxique. Vous n'obtenez aucun pouvoir expressif supplémentaire; au contraire, vous avez moins de pouvoir expressif car dans un langage typé dynamiquement, on vous refuse la possibilité de restreindre les variables à certains types.
Faire une écorce de canard dans une langue de type statique
De plus, un bon langage de type statique vous permettra d'écrire du code qui fonctionne avec n'importe quel type qui a une bark
opération. Dans Haskell, il s'agit d'une classe de type:
class Barkable a where
bark :: a -> unit
Cela exprime la contrainte que pour qu'un type a
soit considéré comme Barkable, il doit exister une bark
fonction qui prend une valeur de ce type et ne renvoie rien.
Vous pouvez ensuite écrire des fonctions génériques en termes de Barkable
contrainte:
makeItBark :: Barkable a => a -> unit
makeItBark barker = bark (barker)
Cela signifie que makeItBark
cela fonctionnera pour tout type satisfaisant Barkable
aux exigences. Cela peut sembler similaire à un interface
Java ou C # mais il a un gros avantage - les types n'ont pas à spécifier à l'avance les classes de types qu'ils satisfont. Je peux dire que ce type Duck
est Barkable
à tout moment, même s'il Duck
s'agit d'un type tiers que je n'ai pas écrit. En fait, peu importe que l'auteur de Duck
n'ait pas écrit de bark
fonction - je peux la fournir après coup quand je dis le langage qui Duck
satisfait Barkable
:
instance Barkable Duck where
bark d = quack (punch (d))
makeItBark (aDuck)
Cela signifie que les Duck
s peuvent aboyer, et leur fonction d'aboiement est implémentée en frappant le canard avant de le faire charlatan. Avec cela à l'écart, nous pouvons faire appel makeItBark
à des canards.
Standard ML
et OCaml
sont encore plus flexibles dans la mesure où vous pouvez satisfaire la même classe de type de plusieurs manières. Dans ces langues , je peux dire que les entiers peuvent être commandés en utilisant la commande classique, puis demi - tour et dire qu'ils sont également commandable par divisibilité (par exemple 10 > 5
parce que 10 est divisible par 5). Dans Haskell, vous ne pouvez instancier une classe de type qu'une seule fois. (Cela permet de savoir Haskell automatiquement qu'il est correct d'appeler bark
sur un canard, en SML ou OCaml , vous devez être explicite sur laquelle bark
la fonction que vous voulez, car il pourrait y avoir plus d'un.)
Concision
Bien sûr, il y a des différences syntaxiques. Le code Python que vous avez présenté est beaucoup plus concis que l'équivalent Java que j'ai écrit. Dans la pratique, cette concision est une grande partie de l'attrait des langages typés dynamiquement. Mais l'inférence de type vous permet d'écrire du code tout aussi concis dans les langages à typage statique, en vous évitant d'avoir à écrire explicitement les types de chaque variable. Un langage de type statique peut également fournir un support natif pour le typage dynamique, supprimant la verbosité de toutes les manipulations de transtypage et de mappage (par exemple, les C # dynamic
).
Programmes corrects mais mal typés
Pour être juste, la frappe statique exclut nécessairement certains programmes qui sont techniquement corrects même si le vérificateur de type ne peut pas le vérifier. Par exemple:
if this_variable_is_always_true:
return "some string"
else:
return 6
La plupart des langages de type statique rejetteraient cette if
déclaration, même si la branche else ne se produira jamais. Dans la pratique, il semble que personne n'utilise ce type de code - quelque chose de trop intelligent pour le vérificateur de type incitera probablement les futurs responsables de votre code à vous maudire, ainsi que vos proches. Exemple: quelqu'un a réussi à traduire 4 projets Python open source en Haskell, ce qui signifie qu'ils ne faisaient rien qu'un bon langage typé statiquement ne pouvait pas compiler. De plus, le compilateur a trouvé quelques bogues liés au type que les tests unitaires ne détectaient pas.
L'argument le plus fort que j'ai vu pour le typage dynamique est les macros de Lisp, car elles vous permettent d'étendre arbitrairement la syntaxe du langage. Cependant, Typed Racket est un dialecte de Lisp de type statique qui a des macros, il semble donc que la frappe statique et les macros ne s'excluent pas mutuellement, bien que peut-être plus difficile à implémenter simultanément.
Pommes et oranges
Enfin, n'oubliez pas qu'il existe de plus grandes différences dans les langues que simplement leur système de type. Avant Java 8, faire n'importe quel type de programmation fonctionnelle en Java était pratiquement impossible; un lambda simple nécessiterait 4 lignes de code de classe anonyme standard. Java ne prend pas non plus en charge les littéraux de collection (par exemple [1, 2, 3]
). Il peut également y avoir des différences dans la qualité et la disponibilité des outils (IDE, débogueurs), des bibliothèques et du support communautaire. Lorsque quelqu'un prétend être plus productif en Python ou Ruby qu'en Java, cette disparité de fonctionnalité doit être prise en compte. Il y a une différence entre comparer les langues avec toutes les batteries incluses , les cœurs de langue et les systèmes de type .
makeItBark(collections.namedtuple("Dog", "bark")(lambda x: "woof woof"))
. Cet argument n'est même pas une classe , c'est un tuple anonyme. Le typage de canard ("si ça craque comme un ...") vous permet de faire des interfaces ad hoc avec essentiellement aucune restriction et sans surcharge syntaxique. Vous pouvez le faire dans un langage comme Java, mais vous vous retrouvez avec beaucoup de réflexion désordonnée. Si une fonction en Java nécessite une ArrayList et que vous souhaitez lui donner un autre type de collection, vous êtes SOL. En python, ça ne peut même pas arriver.