J'ai lu une question connexe. Existe-t-il des modèles de conception inutiles dans les langages dynamiques comme Python? et je me suis souvenu de cette citation sur Wikiquote.org
La chose merveilleuse à propos de la frappe dynamique est qu'elle vous permet d'exprimer tout ce qui est calculable. Et les systèmes de type ne le font pas - les systèmes de type sont généralement décidables, et ils vous limitent à un sous-ensemble. Les gens qui préfèrent les systèmes de type statique disent: «ça va, c'est assez bien; tous les programmes intéressants que vous voulez écrire fonctionneront comme des types ». Mais c'est ridicule - une fois que vous avez un système de type, vous ne savez même pas quels programmes intéressants existent.
--- Radio Génie Logiciel Episode 140: Newspeak et types enfichables avec Gilad Bracha
Je me demande, y a-t-il des modèles ou stratégies de conception utiles qui, en utilisant la formulation de la citation, "ne fonctionnent pas comme des types"?
Réponses:
Types de première classe
La frappe dynamique signifie que vous avez des types de première classe: vous pouvez inspecter, créer et stocker des types au moment de l'exécution, y compris les propres types du langage. Cela signifie également que les valeurs sont typées, pas des variables .
Le langage à typage statique peut produire du code qui s'appuie également sur des types dynamiques, comme la répartition des méthodes, les classes de type, etc., mais d'une manière généralement invisible pour le runtime. Au mieux, ils vous donnent un moyen d'effectuer une introspection. Vous pouvez également simuler des types sous forme de valeurs, mais vous disposez alors d'un système de type dynamique ad hoc.
Cependant, les systèmes de types dynamiques n'ont que rarement des types de première classe. Vous pouvez avoir des symboles de première classe, des packages de première classe, de première classe ... tout. Ceci contraste avec la séparation stricte entre le langage du compilateur et le langage d'exécution dans les langages typés statiquement. Ce que le compilateur ou l'interpréteur peut faire, le runtime peut le faire aussi.
Maintenant, convenons que l'inférence de type est une bonne chose et que j'aime faire vérifier mon code avant de l'exécuter. Cependant, j'aime aussi pouvoir produire et compiler du code lors de l'exécution. Et j'aime aussi précalculer les choses au moment de la compilation. Dans une langue typée dynamiquement, cela se fait avec la même langue. Dans OCaml, vous avez le système de type module / fonctor, qui est différent du système de type principal, qui est différent du langage du préprocesseur. En C ++, vous avez le langage de modèle qui n'a rien à voir avec le langage principal, qui ignore généralement les types lors de l'exécution. Et c'est très bien dans ces langues, car ils ne veulent pas en fournir plus.
En fin de compte, cela ne change pas vraiment ce type de logiciel que vous pouvez développer, mais l'expressivité changements que la façon dont vous les développer et s'il est difficile ou non.
Patterns
Les modèles qui s'appuient sur des types dynamiques sont des modèles qui impliquent des environnements dynamiques: classes ouvertes, répartition, bases de données d'objets en mémoire, sérialisation, etc. Des choses simples comme les conteneurs génériques fonctionnent parce qu'un vecteur n'oublie pas au moment de l'exécution le type d'objets qu'il contient (pas besoin de types paramétriques).
J'ai essayé d'introduire les nombreuses façons dont le code est évalué en Common Lisp ainsi que des exemples d'analyses statiques possibles (c'est SBCL). L'exemple sandbox compile un minuscule sous-ensemble de code Lisp extrait d'un fichier séparé. Afin d'être raisonnablement sûr, je change la table de lecture, n'autorise qu'un sous-ensemble de symboles standard et encapsule les choses avec un délai d'attente.
Rien ci-dessus n'est "impossible" à faire avec d'autres langues. L'approche du plug-in dans Blender, dans les logiciels de musique ou les IDE pour les langages compilés statiquement qui effectuent une recompilation à la volée, etc. Au lieu des outils externes, les langages dynamiques privilégient les outils qui utilisent des informations déjà présentes. Tous les appelants connus de FOO? toutes les sous-classes de BAR? toutes les méthodes spécialisées par classe ZOT? ce sont des données internalisées. Les types ne sont qu'un autre aspect de cela.
(voir aussi: CFFI )
la source
Réponse courte: non, car équivalence de Turing.
Réponse longue: Ce mec est un troll. S'il est vrai que les systèmes de types "vous limitent à un sous-ensemble", les éléments extérieurs à ce sous-ensemble sont, par définition, des éléments qui ne fonctionnent pas.
Tout ce que vous êtes capable de faire dans n'importe quel langage de programmation complet de Turing (qui est un langage conçu pour la programmation à usage général, et beaucoup d'autres qui ne le sont pas; c'est une barre assez basse à effacer et il existe plusieurs exemples d'un système qui devient Turing- terminé involontairement) que vous pouvez faire dans n'importe quel autre langage de programmation Turing-complete. C'est ce qu'on appelle «l'équivalence de Turing» et cela ne signifie exactement que ce qu'elle dit. Surtout, cela ne signifie pas que vous pouvez faire l'autre chose tout aussi facilement dans l'autre langage - certains diront que c'est tout l'intérêt de créer un nouveau langage de programmation en premier lieu: pour vous donner une meilleure façon de faire certains des choses que les langues existantes sucent.
Un système de type dynamique, par exemple, peut être émulé au-dessus d'un système de type OO statique en déclarant simplement toutes les variables, paramètres et valeurs de retour comme
Object
type de base , puis en utilisant la réflexion pour accéder aux données spécifiques à l'intérieur, donc quand vous vous rendez compte vous voyez qu'il n'y a littéralement rien que vous puissiez faire dans un langage dynamique que vous ne pouvez pas faire dans un langage statique. Mais le faire de cette façon serait un énorme gâchis, bien sûr.Le gars de la citation a raison: les types statiques limitent ce que vous pouvez faire, mais c'est une fonctionnalité importante, pas un problème. Les lignes sur la route restreignent ce que vous pouvez faire dans votre voiture, mais les trouvez-vous restrictives ou utiles? (Je sais que je ne voudrais pas conduire sur une route très fréquentée et complexe où rien ne dit aux voitures qui vont dans la direction opposée de rester à leurs côtés et de ne pas venir où je conduis!) En établissant des règles qui définissent clairement ce qui est considéré comme un comportement invalide et en veillant à ce qu'il ne se produise pas, vous réduisez considérablement les risques de survenue d'un crash indésirable.
En outre, il dénature l'autre côté. Ce n'est pas que "tous les programmes intéressants que vous voulez écrire fonctionneront comme des types", mais plutôt "tous les programmes intéressants que vous voulez écrire nécessiteront des types". Une fois que vous avez dépassé un certain niveau de complexité, il devient très difficile de maintenir la base de code sans un système de type pour vous maintenir en ligne, pour deux raisons.
Premièrement, parce que le code sans annotations de type est difficile à lire. Considérez le Python suivant:
À quoi pensez-vous que les données ressembleront que le système à l'autre extrémité de la connexion recevra? Et s'il reçoit quelque chose qui semble complètement faux, comment déterminez-vous ce qui se passe?
Tout dépend de la structure de
value.someProperty
. Mais à quoi ça ressemble? Bonne question! Qu'est-ce qui appellesendData()
? Qu'est-ce que ça passe? À quoi ressemble cette variable? D'où vient-il? Si ce n'est pas local, vous devez retracer l'historique complet devalue
pour retrouver ce qui se passe. Peut-être que vous passez quelque chose d'autre qui a également unesomeProperty
propriété, mais il ne fait pas ce que vous pensez qu'il fait?Maintenant, regardons-le avec des annotations de type, comme vous pouvez le voir dans le langage Boo, qui utilise une syntaxe très similaire mais qui est typé statiquement:
S'il y a quelque chose qui ne va pas, tout à coup, votre travail de débogage est devenu plus simple: recherchez la définition de
MyDataType
! De plus, la possibilité d'obtenir un mauvais comportement parce que vous avez passé un type incompatible qui a également une propriété avec le même nom passe soudainement à zéro, car le système de type ne vous laissera pas faire cette erreur.La deuxième raison s'appuie sur la première: dans un projet vaste et complexe, vous avez très probablement plusieurs contributeurs. (Et sinon, vous le construisez vous-même sur une longue période, ce qui est essentiellement la même chose. Essayez de lire le code que vous avez écrit il y a 3 ans si vous ne me croyez pas!) Cela signifie que vous ne savez pas ce qui était passer par la tête de la personne qui a écrit presque n'importe quelle partie du code au moment où ils l'ont écrit, parce que vous n'étiez pas là, ou ne vous souvenez pas si c'était votre propre code il y a longtemps. Avoir des déclarations de type vous aide vraiment à comprendre quelle était l'intention du code!
Les gens comme le gars dans la citation décrivent fréquemment les avantages du typage statique comme étant "d'aider le compilateur" ou "tout sur l'efficacité" dans un monde où les ressources matérielles presque illimitées rendent cela de moins en moins pertinent chaque année. Mais comme je l'ai montré, bien que ces avantages existent certainement, le principal avantage réside dans les facteurs humains, en particulier la lisibilité et la maintenabilité du code. (L'efficacité supplémentaire est certainement un bon bonus!)
la source
Je vais laisser de côté la partie «modèle» parce que je pense que cela dépend de la définition de ce qui est ou non un modèle et je me suis depuis longtemps désintéressé de ce débat. Ce que je dirai, c'est qu'il y a des choses que vous pouvez faire dans certaines langues que vous ne pouvez pas faire dans d'autres. Soyons clairs, je ne dis pas qu'il y a des problèmes que vous pouvez résoudre dans une langue que vous ne pouvez pas résoudre dans une autre. Mason a déjà souligné l'exhaustivité de Turing.
Par exemple, j'ai écrit une classe en python qui prend encapsule un élément DOM XML et en fait un objet de première classe. Autrement dit, vous pouvez écrire le code:
et vous avez le contenu de ce chemin à partir d'un objet XML analysé. sorte de propre et bien rangé, OMI. Et s'il n'y a pas de nœud principal, il renvoie simplement des objets factices qui ne contiennent que des objets factices (des tortues tout le long). Il n'y a pas de véritable moyen de le faire, disons, en Java. Il faudrait avoir compilé à l'avance une classe basée sur une certaine connaissance de la structure du XML. Mettant de côté si c'est une bonne idée, ce genre de chose change vraiment la façon dont vous résolvez les problèmes dans un langage dynamique. Je ne dis pas que cela change d'une manière qui est nécessairement toujours meilleure, cependant. Il y a des coûts précis pour les approches dynamiques et la réponse de Mason donne un aperçu décent. Leur choix dépend de nombreux facteurs.
En passant, vous pouvez le faire en Java car vous pouvez créer un interpréteur python en Java . Le fait que résoudre un problème spécifique dans une langue donnée peut signifier la construction d'un interprète ou quelque chose de similaire est souvent ignoré lorsque les gens parlent de l'intégralité de Turing.
la source
IDynamicMetaObjectProvider
, et c'est très simple dans Boo. ( Voici une implémentation en moins de 100 lignes, incluse dans le cadre de l'arborescence source standard sur GitHub, car c'est aussi simple que cela!)"IDynamicMetaObjectProvider"
? Est-ce lié audynamic
mot-clé de C # ? ... qui claque efficacement sur la frappe dynamique en C #? Je ne suis pas sûr que votre argument soit valable si j'ai raison.dynamic
atteint en C #. Les «recherches de réflexion et de dictionnaire» se produisent au moment de l'exécution, pas lors de la compilation. Je ne sais vraiment pas comment vous pouvez faire valoir qu'il n'ajoute pas de frappe dynamique à la langue. Mon point est que le dernier paragraphe de Jimmy couvre cela.La citation est correcte, mais aussi vraiment mensongère. Décomposons-le pour voir pourquoi:
Enfin, pas tout à fait. Une langue avec une frappe dynamique vous permet d'exprimer n'importe quoi tant que Turing est complet , ce que la plupart sont. Le système de type lui-même ne vous permet pas de tout exprimer. Donnons-lui le bénéfice du doute ici cependant.
C'est vrai, mais notez que nous parlons maintenant fermement de ce que permet le système de types , et non de ce que permet le langage qui utilise un système de types. Bien qu'il soit possible d'utiliser un système de type pour calculer des éléments au moment de la compilation, cela n'est généralement pas Turing complet (car le système de type est généralement décidable), mais presque tous les langages typés statiquement sont également Turing complet dans son exécution (les langages typés dépendants sont non, mais je ne pense pas que nous en parlions ici).
Le problème est que les langages de types dynamiques ont un type statique. Parfois, tout est une chaîne, et le plus souvent, il existe une union étiquetée où chaque chose est soit un sac de propriétés, soit une valeur comme un entier ou un double. Le problème est que les langages statiques peuvent également le faire, historiquement c'était un peu plus maladroit pour le faire, mais les langages modernes typés statiquement rendent cela à peu près aussi facile à faire que l'utilisation d'un langage de types dynamiques, alors comment peut-il y avoir une différence dans ce que le programmeur peut voir comme un programme intéressant? Les langages statiques ont exactement les mêmes unions étiquetées ainsi que d'autres types.
Pour répondre à la question dans le titre: Non, il n'y a pas de modèles de conception qui ne peuvent pas être implémentés dans un langage typé statiquement, car vous pouvez toujours implémenter suffisamment de système dynamique pour les obtenir. Il peut y avoir des modèles que vous obtenez gratuitement dans une langue dynamique; cela peut ou non valoir la peine de supporter les inconvénients de ces langues pour YMMV .
la source
Il y a sûrement des choses que vous ne pouvez faire que dans des langues typées dynamiquement. Mais ils ne seraient pas nécessairement de bonne conception.
Vous pouvez attribuer d'abord un entier 5 puis une chaîne
'five'
ou unCat
objet à la même variable. Mais vous ne faites que rendre plus difficile pour un lecteur de votre code de comprendre ce qui se passe, quel est le but de chaque variable.Vous pouvez ajouter une nouvelle méthode à une classe Ruby de bibliothèque et accéder à ses champs privés. Il peut y avoir des cas où un tel hack peut être utile mais ce serait une violation de l'encapsulation. (Cela ne me dérange pas d'ajouter des méthodes uniquement en se basant sur l'interface publique, mais ce n'est rien que les méthodes d'extension C # typées statiquement ne peuvent pas faire.)
Vous pouvez ajouter un nouveau champ à un objet de la classe de quelqu'un d'autre pour lui transmettre des données supplémentaires. Mais il est préférable de simplement créer une nouvelle structure ou d'étendre le type d'origine.
En règle générale, plus vous voulez que votre code reste organisé, moins vous devriez tirer parti de la possibilité de modifier dynamiquement les définitions de type ou d'affecter des valeurs de différents types à la même variable. Mais votre code n'est pas différent de ce que vous pourriez obtenir dans un langage typé statiquement.
Les langages dynamiques sont bons dans le sucre syntaxique. Par exemple, lorsque vous lisez un objet JSON désérialisé, vous pouvez faire référence à une valeur imbriquée simplement comme
obj.data.article[0].content
- beaucoup plus propre que disonsobj.getJSONObject("data").getJSONArray("article").getJSONObject(0).getString("content")
.Les développeurs Ruby en particulier pourraient parler longuement de la magie qui peut être obtenue en implémentant
method_missing
, qui est une méthode vous permettant de gérer les appels tentés vers des méthodes non déclarées. Par exemple, ActiveRecord ORM l'utilise pour que vous puissiez effectuer un appelUser.find_by_email('[email protected]')
sans jamais déclarer defind_by_email
méthode. Bien sûr, ce n'est rien qui ne puisse pas être réalisé commeUserRepository.FindBy("email", "[email protected]")
dans un langage typé, mais vous ne pouvez pas lui nier sa netteté.la source
Le modèle de proxy dynamique est un raccourci pour implémenter des objets proxy sans avoir besoin d'une classe par type à proxy.
En utilisant cela,
Proxy(someObject)
crée un nouvel objet qui se comporte de la même manière quesomeObject
. Évidemment, vous voudrez également ajouter des fonctionnalités supplémentaires, mais c'est une base utile pour commencer. Dans un langage statique complet, vous devez soit écrire une classe Proxy par type que vous souhaitez proxy, soit utiliser la génération de code dynamique (qui, il est vrai, est incluse dans la bibliothèque standard de nombreux langages statiques, principalement parce que leurs concepteurs connaissent les problèmes ne pouvant pas faire cette cause).Un autre cas d'utilisation des langages dynamiques est le "patch de singe". À bien des égards, il s'agit d'un anti-motif plutôt que d'un motif, mais il peut être utilisé de manière utile s'il est fait avec soin. Et bien qu'il n'y ait aucune raison théorique pour que le patch de singe ne puisse pas être implémenté dans un langage statique, je n'en ai jamais vu un qui le possède.
la source
Oui , il existe de nombreux modèles et techniques qui ne sont possibles que dans un langage typé dynamiquement.
La correction de singe est une technique où des propriétés ou des méthodes sont ajoutées aux objets ou aux classes lors de l'exécution. Cette technique n'est pas possible dans un langage typé car cela signifie que les types et les opérations ne peuvent pas être vérifiés au moment de la compilation. Ou pour le dire autrement, si une langue prend en charge le patch de singe, c'est par définition une langue dynamique.
Il peut être prouvé que si un langage prend en charge la correction de singe (ou des techniques similaires pour modifier les types lors de l'exécution), il ne peut pas être vérifié statiquement. Il ne s'agit donc pas seulement d'une limitation dans les langues existantes, c'est une limitation fondamentale du typage statique.
La citation est donc tout à fait correcte - plus de choses sont possibles dans un langage dynamique que dans un langage typé statiquement. D'un autre côté, certains types d' analyses ne sont possibles que dans un langage typé statiquement. Par exemple, vous savez toujours quelles opérations sont autorisées sur un type donné, ce qui vous permet de détecter les opérations illégales au type de compilation. Aucune vérification de ce type n'est possible dans un langage dynamique lorsque des opérations peuvent être ajoutées ou supprimées au moment de l'exécution.
C'est pourquoi il n'y a pas de "meilleur" évident dans le conflit entre les langages statiques et dynamiques. Les langages statiques abandonnent une certaine puissance au moment de l'exécution en échange d'une puissance différente au moment de la compilation, ce qui, selon eux, réduit le nombre de bogues et facilite le développement. Certains croient que le compromis en vaut la peine, d'autres non.
D'autres réponses ont fait valoir que l'équivalence de Turing signifie que tout ce qui est possible dans une langue est possible dans toutes les langues. Mais cela ne suit pas. Pour prendre en charge quelque chose comme le patch de singe dans un langage statique, vous devez essentiellement implémenter un sous-langage dynamique à l'intérieur du langage statique. C'est bien sûr possible, mais je dirais que vous programmez alors dans un langage dynamique intégré, car vous perdez également la vérification de type statique qui existe dans le langage hôte.
C # depuis la version 4 prend en charge les objets typés dynamiquement. Il est clair que les concepteurs de langage voient l'avantage d'avoir les deux types de frappe disponibles. Mais cela montre également que vous ne pouvez pas avoir votre gâteau et manger moi aussi: lorsque vous utilisez des objets dynamiques en C #, vous gagnez la capacité de faire quelque chose comme le patch de singe, mais vous perdez également la vérification de type statique pour l'interaction avec ces objets.
la source
Oui et non.
Il existe des situations dans lesquelles le programmeur connaît le type d'une variable avec plus de précision qu'un compilateur. Le compilateur peut savoir que quelque chose est un objet, mais le programmeur saura (en raison des invariants du programme) qu'il s'agit en fait d'une chaîne.
Permettez-moi d'en montrer quelques exemples:
Je sais que
someMap.get(T.class)
cela retournera unFunction<T, String>
, à cause de la façon dont j'ai construit someMap. Mais Java est seulement sûr que j'ai une fonction.Un autre exemple:
Je sais que data.properties.rowCount sera une référence valide et un entier, car j'ai validé les données par rapport à un schéma. Si ce champ était manquant, une exception aurait été levée. Mais un compilateur ne saurait que lancer une exception ou renvoyer une sorte de JSONValue générique.
Un autre exemple:
Les "II6" définissent la façon dont les données codent pour trois variables. Depuis que j'ai spécifié le format, je sais quels types seront retournés. Un compilateur sait seulement qu'il renvoie un tuple.
Le thème unificateur de tous ces exemples est que le programmeur connaît le type, mais un système de type de niveau Java ne pourra pas refléter cela. Le compilateur ne connaîtra pas les types, et donc un langage typé statiquement ne me permettra pas de les appeler, alors qu'un langage typé dynamiquement le fera.
C'est à cela que la citation originale aboutit:
Lorsque j'utilise la frappe dynamique, je peux utiliser le type le plus dérivé que je connaisse, pas simplement le type le plus dérivé que le système de type de ma langue connaît. Dans tous les cas ci-dessus, j'ai un code sémantiquement correct, mais qui sera rejeté par un système de typage statique.
Cependant, pour revenir à votre question:
N'importe lequel des exemples ci-dessus, et en fait tout exemple de typage dynamique peut être rendu valide en typage statique en ajoutant des transtypages appropriés. Si vous connaissez un type que votre compilateur ne connaît pas, dites simplement au compilateur en transtypant la valeur. Donc, à un certain niveau, vous n'obtiendrez aucun motif supplémentaire en utilisant la frappe dynamique. Vous devrez peut-être simplement convertir plus pour obtenir du code tapé de manière statique.
L'avantage de la frappe dynamique est que vous pouvez simplement utiliser ces modèles sans vous inquiéter du fait qu'il est difficile de convaincre votre système de saisie de leur validité. Cela ne change pas les modèles disponibles, cela les rend peut-être plus faciles à implémenter, car vous n'avez pas à comprendre comment faire en sorte que votre système de types reconnaisse le modèle ou ajouter des transtypages pour renverser le système de types.
la source
data = parseJSON<SomeSchema>(someJson); print(data.properties.rowCount);
et si l'on n'a pas de classe à désérialiser, nous pouvons revenir àdata = parseJSON(someJson); print(data["properties.rowCount"]);
- qui est toujours tapé et exprime la même intention.data.properties
c'était un objet et je savais quedata.properties.rowCount
c'était un entier et je pouvais simplement écrire du code qui les utilisait. Votre propositiondata["properties.rowCount"]
ne fournit pas la même chose.Voici quelques exemples d'Objective-C (typés dynamiquement) qui ne sont pas possibles en C ++ (typés statiquement):
Mettre des objets de plusieurs classes distinctes dans le même conteneur.
Bien sûr, cela nécessite une inspection de type d'exécution pour interpréter ultérieurement le contenu du conteneur, et la plupart des amis de la frappe statique objecteront que vous ne devriez pas faire cela en premier lieu. Mais j'ai trouvé qu'au-delà des débats religieux, cela peut être utile.
Extension d'une classe sans sous-classement.
Dans Objective-C, vous pouvez définir de nouvelles fonctions membres pour les classes existantes, y compris celles définies par le langage comme
NSString
. Par exemple, vous pouvez ajouter une méthodestripPrefixIfPresent:
, pour que vous puissiez dire[@"foo/bar/baz" stripPrefixIfPresent:@"foo/"]
(notez l'utilisation desNSSring
littéraux@""
).Utilisation de rappels orientés objet.
Dans les langages typés statiquement comme Java et C ++, vous devez aller très loin pour permettre à une bibliothèque d'appeler un membre arbitraire d'un objet fourni par l'utilisateur. En Java, la solution de contournement est la paire interface / adaptateur plus une classe anonyme, en C ++, la solution de contournement est généralement basée sur un modèle, ce qui implique que le code de bibliothèque doit être exposé au code utilisateur. Dans Objective-C, vous passez simplement la référence d'objet plus le sélecteur de la méthode à la bibliothèque, et la bibliothèque peut simplement et directement appeler le rappel.
la source
void*
seul n'est pas une frappe dynamique, c'est un manque de frappe. Mais oui, dynamic_cast, tables virtuelles, etc. rendent le C ++ non purement statique. Est-ce mauvais?void*
en un type d'objet spécifique. Le premier produit une erreur d'exécution si vous vous trompez, le dernier entraîne un comportement indéfini.