web-dev-qa-db-fra.com

Existe-t-il des modèles de conception qui ne sont possibles que dans des langages typés dynamiquement comme Python?

J'ai lu une question connexe Y a-t-il des modèles de conception inutiles dans des 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 sont 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 statiques disent "c'est bien, 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, existe-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"?

30
user7610

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 saisies, et non les 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 qui est 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 ont rarement uniquement 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 bien dans ces langues, car ils ne veulent pas en fournir plus.

En fin de compte, cela ne change pas vraiment quel type de logiciel vous pouvez développer, mais l'expressivité change comment vous les développez et que ce soit difficile ou non.

Motifs

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é dans Common LISP ainsi que des exemples d'analyses statiques possibles (c'est SBCL). L'exemple de bac à sable 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.

;;
;; Fetching systems, installing them, etc. 
;; ASDF and QL provide provide resp. a Make-like facility 
;; and system management inside the runtime: those are
;; not distinct programs.
;; Reflexivity allows to develop dedicated tools: for example,
;; being able to find the transitive reduction of dependencies
;; to parallelize builds. 
;; https://gitlab.common-LISP.net/xcvb/asdf-dependency-grovel
;;
(ql:quickload 'trivial-timeout)

;;
;; Readtables are part of the runtime.
;; See also NAMED-READTABLES.
;;
(defparameter *safe-readtable* (copy-readtable *readtable*))
(set-macro-character #\# nil t *safe-readtable*)
(set-macro-character #\: (lambda (&rest args)
                           (declare (ignore args))
                           (error "Colon character disabled."))
                     nil
                     *safe-readtable*)

;; eval-when is necessary when compiling the whole file.
;; This makes the result of the form available in the compile-time
;; environment. 
(eval-when (:compile-toplevel :load-toplevel :execute)
  (defvar +WHITELISTED-LISP-SYMBOLS+ 
    '(+ - * / lambda labels mod rem expt round 
      truncate floor ceiling values multiple-value-bind)))

;;
;; Read-time evaluation #.+WHITELISTED-LISP-SYMBOLS+
;; The same language is used to control the reader.
;;
(defpackage :sandbox
  (:import-from
   :common-LISP . #.+WHITELISTED-LISP-SYMBOLS+)
  (:export . #.+WHITELISTED-LISP-SYMBOLS+))

(declaim (inline read-sandbox))

(defun read-sandbox (stream &key (timeout 3))
  (declare (type (integer 0 10) timeout))
  (trivial-timeout:with-timeout (timeout)
    (let ((*read-eval* nil)
          (*readtable* *safe-readtable*)
          ;;
          ;; Packages are first-class: no possible name collision.
          ;;
          (package (make-package (gensym "SANDBOX") :use '(:sandbox))))
      (unwind-protect
           (let ((*package* package))
             (loop
                with stop = (gensym)
                for read = (read stream nil stop)
                until (eq read stop)
                ;;
                ;; Eval at runtime
                ;;
                for value = (eval read)
                ;;
                ;; Type checking
                ;;
                unless (functionp value)
                do (error "Not a function")
                ;; 
                ;; Compile at run-time
                ;;
                collect (compile nil value)))
        (delete-package package)))))

;;
;; Static type checking.
;; warning: Constant 50 conflicts with its asserted type (MOD 11)
;;
(defun read-sandbox-file (file)
  (with-open-file (in file)
    (read-sandbox in :timeout 50)))

;; get it right, this time
(defun read-sandbox-file (file)
  (with-open-file (in file)
    (read-sandbox in)))

#| /tmp/plugin.LISP
(lambda (x) (+ (* 3 x) 100))
(lambda (a b c) (* a b))
|#

(read-sandbox-file #P"/tmp/plugin.LISP")

;; 
;; caught COMMON-LISP:STYLE-WARNING:
;;   The variable C is defined but never used.
;;

(#<FUNCTION (LAMBDA (#:X)) {10068B008B}>
 #<FUNCTION (LAMBDA (#:A #:B #:C)) {10068D484B}>)

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. 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 )

4
coredump

Réponse courte: non, car équivalence de Turing.

Réponse longue: Ce mec est un troll. Bien qu'il soit vrai que les systèmes de types "vous limitent à un sous-ensemble", les éléments extérieurs à ce sous-ensemble sont, par définition, é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 statique OO en déclarant simplement toutes les variables, paramètres et valeurs de retour comme type de base Object et puis en utilisant la réflexion pour accéder aux données spécifiques à l'intérieur, donc quand vous réalisez cela, 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écessitent 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:

def sendData(self, value):
   self.connection.send(serialize(value.someProperty))

À quoi vous attendez-vous que les données ressemblent à ce que le système à l'autre extrémité de la connexion reçoit? 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! Comment appelle sendData()? 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 de value pour suivre ce qui se passe. Peut-être que vous passez quelque chose d'autre qui a également une propriété someProperty, 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:

def SendData(value as MyDataType):
   self.Connection.Send(Serialize(value.SomeProperty))

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 chance 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, cependant!)

39
Mason Wheeler

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 pouvez ' t résoudre dans un 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 XML DOM et en fait un objet de première classe. Autrement dit, vous pouvez écrire le code:

doc.header.status.text()

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 faire cela dans Java parce que vous pouvez construire un interprète python en Java . Le fait que la résolution 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.

27
JimmyJames

La citation est correcte, mais aussi vraiment mensongère. Décomposons-le pour voir pourquoi:

La chose merveilleuse à propos de la frappe dynamique est qu'elle vous permet d'exprimer tout ce qui est calculable.

Enfin, pas tout à fait. Une langue avec une frappe dynamique vous permet d'exprimer n'importe quoi tant que c'est Turing 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.

Et les systèmes de type ne sont pas - les systèmes de type sont généralement décidables, et ils vous limitent à un sous-ensemble.

C'est vrai, mais notez que nous parlons maintenant fermement de ce que le système de type permet, et non de ce que la langue qui utilise un système de type permet. 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).

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.

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 y a 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 typés statiquement modernes 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 .

10
jk.

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 bons design.

Vous pouvez d'abord affecter un entier 5 puis une chaîne 'five' Ou un objet Cat à 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 bibliothèque Ruby class 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. (Je ne ' t l'esprit 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 souhaitez 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 disons obj.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 tentatives d'appels à des méthodes non déclarées. Par exemple, ActiveRecord ORM l'utilise pour que vous puissiez effectuer un appel User.find_by_email('[email protected]') sans jamais déclarer la méthode find_by_email. Bien sûr, il n'y a rien qui ne puisse pas être réalisé en tant que UserRepository.FindBy("email", "[email protected]") dans un langage typé statiquement, mais vous ne pouvez pas lui nier sa netteté.

4
kamilk

Le modèle de proxy dynamique est un raccourci pour implémenter des objets proxy sans avoir besoin d'une classe par type que vous devez proxy.

class Proxy(object):
    def __init__(self, obj):
        self.__target = obj

    def __getattr__(self, attr):
        return getattr(self.__target, attr)

En utilisant ceci, Proxy(someObject) crée un nouvel objet qui se comporte de la même manière que someObject. É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, c'est un anti-modèle plutôt qu'un modèle, mais il peut être utilisé de manière utile s'il est fait avec soin. Et bien qu'il n'y ait pas théorique raison pour laquelle le patch de singe n'a pas pu être implémenté dans un langage statique, je n'en ai jamais vu un qui le possède réellement.

4
Jules

Oui, il existe de nombreux modèles et techniques qui ne sont possibles que dans un langage typé dynamiquement.

Monkey patching 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 à typage statique 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 un langage prend en charge le patch de singe, il est par définition un langage 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. En revanche, certains types d'analyse 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 des opérations illégales au type de compilation. Une telle vérification n'est pas 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 lors de l'exécution en échange d'un autre type de puissance 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 dactylographie disponibles. Mais cela montre aussi 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 la correction de singe, mais vous perdez également la vérification de type statique pour l'interaction avec ces objets.

3
JacquesB

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"?

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:

Map<Class<?>, Function<?, String>> someMap;
someMap.get(object.getClass()).apply(object);

Je sais que someMap.get(T.class) renverra un Function<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:

data = parseJSON(someJson)
validate(data, someJsonSchema);
print(data.properties.rowCount);

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:

x, y, z = struct.unpack("II6s", data)

Les "II6" définissent la façon dont les données codent 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 Java ne pourra pas le refléter. Le compilateur ne connaîtra pas les types, et donc une langue typée statiquement ne me permettra pas de les appeler, alors qu'une langue typée dynamiquement le fera.

C'est à cela que la citation originale aboutissait:

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 sont pas - les systèmes de type sont généralement décidables, et ils vous limitent à un sous-ensemble.

Lors de l'utilisation de 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:

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"?

N'importe lequel des exemples ci-dessus, et en effet 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, informez-le simplement en convertissant la valeur. Donc, à un certain niveau, vous n'obtiendrez aucun modèle supplémentaire en utilisant la frappe dynamique. Vous aurez peut-être besoin de 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.

2
Winston Ewert

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 saisie 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éthode stripPrefixIfPresent:, pour que vous puissiez dire [@"foo/bar/baz" stripPrefixIfPresent:@"foo/"] (notez l'utilisation des NSSring 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, le 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 la bibliothèque doit être exposé au code utilisateur. Dans Objective-C, vous passez simplement la référence d'objet plus le sélecteur pour le à la bibliothèque, et la bibliothèque peut simplement et directement appeler le rappel.