Les langages dynamiques présentent-ils un réel avantage? [fermé]


29

Je veux d'abord dire que Java est le seul langage que j'ai jamais utilisé, alors veuillez excuser mon ignorance à ce sujet.

Les langages typés dynamiquement vous permettent de mettre n'importe quelle valeur dans n'importe quelle variable. Ainsi, par exemple, vous pouvez écrire la fonction suivante (psuedocode):

void makeItBark(dog){
    dog.bark();
}

Et vous pouvez y passer n'importe quelle valeur. Tant que la valeur a une bark()méthode, le code s'exécutera. Sinon, une exception d'exécution ou quelque chose de similaire est levée. (Veuillez me corriger si je me trompe).

Apparemment, cela vous donne de la flexibilité.

Cependant, j'ai fait de la lecture sur les langages dynamiques, et ce que les gens disent, c'est que lorsque vous concevez ou écrivez du code dans un langage dynamique, vous pensez aux types et en tenez compte, tout comme vous le feriez dans un langage typé.

Ainsi, par exemple, lors de l'écriture de la makeItBark()fonction, vous avez l'intention de n'accepter que des `` choses qui peuvent aboyer '', et vous devez toujours vous assurer de ne passer que ce genre de choses. La seule différence est que maintenant le compilateur ne vous dira pas quand vous avez fait une erreur.

Bien sûr, il y a un avantage à cette approche, c'est que dans les langages statiques, pour obtenir la fonction «cette fonction accepte tout ce qui peut aboyer», vous devez implémenter une Barkerinterface explicite . Pourtant, cela semble être un avantage mineur.

Suis-je en train de manquer quelque chose? Qu'est-ce que je gagne réellement en utilisant une langue typée dynamiquement?


6
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.
Phoshi

2
Ce genre de question a déjà été posée: ici , ici et ici . Plus précisément, le premier exemple semble répondre à votre question. Peut-être pouvez-vous reformuler le vôtre pour le rendre distinct?
logc

3
Notez que par exemple en C ++, vous pouvez avoir une fonction de modèle qui fonctionne avec tout type T qui a une bark()méthode, le compilateur se plaignant lorsque vous passez quelque chose de mal mais sans avoir à déclarer une interface contenant bark ().
Wilbert

2
@Phoshi L'argument en Python doit toujours être d'un type particulier - par exemple, il ne peut pas être un nombre. Si vous avez votre propre implémentation ad hoc d'objets, qui récupère ses membres via une getMemberfonction personnalisée , makeItBarkexplose parce que vous avez appelé à la dog.barkplace de dog.getMember("bark"). Ce qui fait fonctionner le code, c'est que tout le monde accepte implicitement d'utiliser le type d'objet natif de Python.
Doval

2
@Phoshi Just because I wrote makeItBark with my own types in mind doesn't mean you can't use yours, wheras in a static language it probably /does/ mean that.Comme indiqué dans ma réponse, ce n'est pas le cas en général . C'est le cas pour Java et C #, mais ces langages ont des systèmes de type et de module paralysés, ils ne sont donc pas représentatifs de ce que le typage statique peut faire. Je peux écrire un makeItBarklangage parfaitement générique dans plusieurs langages de type statique, même ceux non fonctionnels comme C ++ ou D.
Doval

Réponses:


35

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 Objectet 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 barkopé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 asoit considéré comme Barkable, il doit exister une barkfonction qui prend une valeur de ce type et ne renvoie rien.

Vous pouvez ensuite écrire des fonctions génériques en termes de Barkablecontrainte:

makeItBark :: Barkable a => a -> unit
makeItBark barker = bark (barker)

Cela signifie que makeItBarkcela fonctionnera pour tout type satisfaisant Barkableaux exigences. Cela peut sembler similaire à un interfaceJava 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 Duckest Barkableà tout moment, même s'il Ducks'agit d'un type tiers que je n'ai pas écrit. En fait, peu importe que l'auteur de Duckn'ait pas écrit de barkfonction - je peux la fournir après coup quand je dis le langage qui Ducksatisfait Barkable:

instance Barkable Duck where
    bark d = quack (punch (d))

makeItBark (aDuck)

Cela signifie que les Ducks 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 MLet OCamlsont 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 > 5parce 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 barksur 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 ifdé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 .


2
Vous avez oublié d'attribuer votre source pour le premier paragraphe - existentialtype.wordpress.com/2011/03/19/…

2
@Matt Re: 1, je n'ai pas supposé que ce n'était pas important; Je l'ai abordé sous Concision. Re: 2, bien que je ne l'ai jamais dit explicitement, par "bon" je veux dire "a une inférence de type approfondie" et "a un système de modules qui vous permet de faire correspondre le code pour taper des signatures après le fait ", pas d'avance comme Java / Interfaces de C #. Concernant 3, la charge de la preuve incombe à vous pour m'expliquer comment, étant donné deux langues avec une syntaxe et des fonctionnalités équivalentes, l'une dynamiquement typée et l'autre avec l'inférence de type complet, vous ne seriez pas en mesure d'écrire du code de longueur égale dans les deux .
Doval

3
@MattFenwick Je l'ai déjà justifié - étant donné deux langues avec les mêmes fonctionnalités, l'une dynamiquement typée et l'autre statiquement typée, la principale différence entre elles sera la présence d'annotations de type, et l'inférence de type enlève cela. Toutes les autres différences de syntaxe sont superficielles et toute différence de fonctionnalités transforme la comparaison en pommes vs oranges. C'est à vous de montrer comment cette logique est fausse.
Doval

1
Vous devriez jeter un œil à Boo. Il est typé statiquement avec une inférence de type et possède des macros qui permettent d'étendre la syntaxe du langage.
Mason Wheeler

1
@Doval: Vrai. BTW, la notation lambda n'est pas utilisée exclusivement dans la programmation fonctionnelle: pour autant que je sache, Smalltalk a des blocs anonymes, et Smalltalk est aussi orienté objet qu'il peut l'être. Ainsi, la solution consiste souvent à faire circuler un bloc de code anonyme avec certains paramètres, qu'il s'agisse d'une fonction anonyme ou d'un objet anonyme avec exactement une méthode anonyme. Je pense que ces deux constructions expriment essentiellement la même idée de deux perspectives différentes (celle fonctionnelle et celle orientée objet).
Giorgio

11

C'est une question difficile et assez subjective. (Et votre question peut être fermée en fonction de l'opinion, mais cela ne signifie pas que c'est une mauvaise question - au contraire, même penser à de telles questions en méta-langage est un bon signe - ce n'est tout simplement pas bien adapté au format Q&A de ce forum.)

Voici mon point de vue: le but des langages de haut niveau est de restreindre ce qu'un programmeur peut faire avec l'ordinateur. Cela est surprenant pour beaucoup de gens, car ils pensent que le but est de donner aux utilisateurs plus de puissance et de faire plus . Mais comme tout ce que vous écrivez en Prolog, C ++ ou List est finalement exécuté en tant que code machine, il est en fait impossible de donner au programmeur plus de puissance que le langage d'assemblage ne le fournit déjà.

Le but d'un langage de haut niveau est d' aider le programmeur à mieux comprendre le code qu'il a lui-même créé et à le rendre plus efficace pour faire la même chose. Un nom de sous-programme est plus facile à retenir qu'une adresse hexadécimale. Un compteur d'arguments automatique est plus facile à utiliser qu'une séquence d'appels ici, vous devez obtenir le nombre d'arguments exactement par vous-même, sans aide. Un système de types va plus loin et limite le type d'arguments que vous pouvez fournir à un endroit donné.

C'est là que la perception des gens diffère. Certaines personnes (j'en fais partie) pensent que tant que votre routine de vérification de mot de passe attendra de toute façon exactement deux arguments, et toujours une chaîne suivie d'un identifiant numérique, il est utile de le déclarer dans le code et d'être automatiquement rappelé si vous oubliez plus tard de suivre cette règle. L'externalisation d'une telle comptabilité à petite échelle vers le compilateur permet de libérer votre esprit pour des problèmes de niveau supérieur et vous permet de mieux concevoir et architecturer votre système. Par conséquent, les systèmes de type sont une victoire nette: ils laissent l'ordinateur faire ce qu'il est bon et les humains font ce qu'ils sont bons.

D'autres voient tout à fait différemment. Ils n'aiment pas qu'on leur dise quoi faire. Ils n'aiment pas l'effort supplémentaire de départ pour décider de la déclaration de type et la taper. Ils préfèrent un style de programmation exploratoire où vous écrivez le code d'entreprise réel sans avoir de plan qui vous dirait exactement quels types et arguments utiliser. Et pour le style de programmation qu'ils utilisent, cela peut être tout à fait vrai.

Je simplifie excessivement ici, bien sûr. La vérification de type n'est pas strictement liée à des déclarations de type explicites; il existe également une inférence de type. La programmation avec des routines qui prennent en fait des arguments de types différents permet des choses assez différentes et très puissantes qui seraient autrement impossibles, c'est juste que beaucoup de gens ne sont pas suffisamment attentifs et cohérents pour utiliser une telle latitude avec succès.

En fin de compte, le fait que ces langages différents soient à la fois très populaires et ne montrent aucun signe de disparition montre que les gens ont une programmation très différente. Je pense que les fonctionnalités du langage de programmation sont largement liées aux facteurs humains - ce qui soutient mieux le processus de prise de décision humaine - et tant que les gens travailleront très différemment, le marché fournira des solutions très différentes simultanément.


3
Merci d'avoir répondu. Vous avez dit que certaines personnes n'aiment pas que le compilateur leur dise quoi faire. [..] Ils préfèrent un style de programmation exploratoire où vous écrivez le code d'entreprise réel sans avoir un plan qui vous dirait exactement quels types et arguments utiliser où. C'est ce que je ne comprends pas: la programmation n'est pas comme l'improvisation musicale. En musique, si vous frappez une mauvaise note, cela peut sembler cool. En programmation, si vous passez quelque chose dans une fonction qui n'est pas censée être là, vous obtiendrez très probablement des bugs désagréables. (continue dans le commentaire suivant).
Aviv Cohn

3
Je suis d'accord, mais beaucoup de gens ne sont pas d' accord. Et les gens sont assez possessifs quant à leurs idées préconçues mentales, d'autant plus qu'ils les ignorent souvent. C'est pourquoi les débats sur le style de programmation dégénèrent généralement en arguments ou en combats, et il est rarement utile de les lancer avec des inconnus aléatoires sur Internet.
Kilian Foth du

1
C'est pourquoi - à en juger par ce que je lis - les personnes utilisant des langages dynamiques prennent en compte les types autant de personnes utilisant des langages statiques. Parce que lorsque vous écrivez une fonction, elle est censée prendre des arguments d'un type spécifique. Peu importe que le compilateur applique cela ou non. Cela revient donc à la saisie statique pour vous aider, contrairement à la saisie dynamique. Dans les deux cas, une fonction doit prendre un type d'entrée spécifique. Je ne vois donc pas quel est l'avantage de la frappe dynamique. Même si vous préférez un «style de programmation exploratoire», vous ne pouvez toujours pas passer ce que vous voulez dans une fonction.
Aviv Cohn

1
Les gens parlent souvent de types de projets très différents (notamment en ce qui concerne la taille). La logique métier d'un site Web sera très simple par rapport à un système ERP complet. Il y a moins de risques que vous vous trompiez et l'avantage de pouvoir réutiliser très simplement du code est plus pertinent. Disons que j'ai du code qui génère un Pdf (ou du HTML) à partir d'une structure de données. Maintenant, j'ai une source de données différente (d'abord JSON à partir d'une API REST, maintenant c'est un importateur Excel). Dans un langage comme Ruby, il peut être très facile de «simuler» la première structure, de la «faire aboyer» et de réutiliser le code Pdf.
thorsten müller

@Prog: Le véritable avantage des langages dynamiques est quand il s'agit de décrire des choses qui sont vraiment difficiles avec un système de type statique. Une fonction en python, par exemple, pourrait être une référence de fonction, un lambda, un objet de fonction, ou Dieu sait quoi et tout fonctionnera de la même manière. Vous pouvez créer un objet qui enveloppe un autre objet et distribue automatiquement des méthodes sans surcharge syntaxique, et chaque fonction a essentiellement comme par magie des types paramétrés. Les langages dynamiques sont incroyables pour faire rapidement des choses.
Phoshi

5

Le code écrit à l'aide de langages dynamiques n'est pas couplé à un système de type statique. Par conséquent, ce manque de couplage est un avantage par rapport aux systèmes de type statique pauvres / inadéquats (bien qu'il puisse s'agir d'un lavage ou d'un inconvénient par rapport à un grand système de type statique).

De plus, pour un langage dynamique, un système de type statique n'a pas besoin d'être conçu, implémenté, testé et maintenu. Cela pourrait rendre l'implémentation plus simple par rapport à un langage avec un système de type statique.


2
Les gens n'ont-ils pas tendance à réimplémenter éventuellement un système de type statique de base avec leurs tests unitaires (lorsqu'ils visent une bonne couverture de test)?
Den

Aussi, que voulez-vous dire par «couplage» ici? Comment cela se manifesterait-il dans une architecture de micro-services, par exemple?
Den

@Den 1) bonne question, cependant, je pense que cela sort du cadre du PO et de ma réponse. 2) Je veux dire le couplage dans ce sens ; brièvement, différents systèmes de types imposent des contraintes différentes (incompatibles) au code écrit dans ce langage. Désolé, je ne peux pas répondre à la dernière question - je ne comprends pas la particularité des micro-services à cet égard.

2
@Den: Très bon point: j'observe souvent que les tests unitaires que j'écris en Python couvrent les erreurs qui seraient détectées par un compilateur dans un langage de type statique.
Giorgio

@MattFenwick: Vous avez écrit que c'est un avantage que "... pour un langage dynamique, un système de type statique n'a pas à être conçu, implémenté, testé et maintenu." et Den a observé que vous devez souvent concevoir et tester vos types directement dans votre code. Ainsi, l'effort n'est pas supprimé mais déplacé de la conception du langage vers le code de l'application.
Giorgio
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.