18

Dans tous les projets que j'ai démarrés dans des langages sans systèmes de type, j'ai fini par inventer un système de type runtime. Peut-être que le terme «système de type» est trop fort; à tout le moins, je crée un ensemble de validateurs de type/plage de valeurs lorsque je travaille avec des types de données complexes, et ensuite je ressens le besoin d'être paranoïaque quant à l'endroit où les types de données peuvent être créés et modifiés.Comment évite-t-on de créer un système de type ad-hoc dans des langages typés dynamiquement?

Je n'y avais pas réfléchi à deux fois jusqu'à maintenant. En tant que développeur indépendant, mes méthodes ont fonctionné dans la pratique sur un certain nombre de petits projets, et il n'y a aucune raison qu'ils cessent de travailler maintenant.

Néanmoins, cela doit être faux. Je me sens comme si je n'utilisais pas correctement les langues dynamiquement typées. Si je dois inventer un système de types et l'appliquer moi-même, je peux aussi bien utiliser un langage qui a des types pour commencer.

Alors, mes questions sont les suivantes:

  • Y at-il des paradigmes de programmation existants (pour les langues sans types) qui permettent d'éviter la nécessité d'utiliser ou d'inventer des systèmes de type? Y a-t-il des recommandations communes sur la façon de résoudre les problèmes que le typage statique résout dans les langages dynamiquement typés (sans réinventer pudiquement les types)?

Voici un exemple concret pour vous d'envisager. Je travaille avec datetimes et timezones en erlang (un langage dynamique et fortement typé). Ceci est un type commun, je travaille avec:

{{Y,M,D},{tztime, {time, HH,MM,SS}, Flag}} 

... où {Y,M,D} est un tuple représentant une date valide (toutes les entrées sont des nombres entiers), tztime et time sont des atomes, HH,MM,SS sont des nombres entiers représentant un sain d'esprit 24 h temps, et Flag est l'un des atomes u,d,z,s,w.

Ce type de données est généralement analysé à partir d'une entrée, donc pour garantir une entrée valide et un analyseur correct, les valeurs doivent être vérifiées pour l'exactitude du type, et pour les plages valides. Plus tard, les instances de ce type de données sont comparées entre elles, ce qui rend le type de leurs valeurs d'autant plus important que tous les termes se comparent. À partir de erlang reference manual

number < atom < reference < fun < port < pid < tuple < list < bit string 
+0

étant passé de beaucoup de java, à beaucoup de groovy, vous résolvez le problème avec des tests unitaires, et acceptez simplement le fait que vous ne savez pas jusqu'à l'exécution le vrai type d'un objet. En fait, le vrai type d'un objet n'a pas d'importance si vous tapez du canard. – dstarh

+2

Vous semblez être en train de fusionner dynamiquement typé et faiblement typé. Il existe une distinction entre typage fort typé et typage faible et typage statique versus typage dynamique. –

+1

Je serais intéressé de voir un exemple du type de code qui a conduit à cette question. –

Répondre

1

Parfois, les données doivent être validées. La validation de toutes les données reçues du réseau est presque toujours une bonne idée - en particulier les données provenant d'un réseau public. Être paranoïaque ici est seulement bon. Si quelque chose ressemblant à un système de type statique l'aide de la manière la moins douloureuse, qu'il en soit ainsi. Il y a une raison pour laquelle Erlang autorise les annotations de type. Même la correspondance de modèle peut être vue comme une sorte de vérification de type dynamique; néanmoins, c'est une caractéristique centrale de la langue. La structure même des données est son «type» à Erlang. La bonne chose est que vous pouvez personnaliser votre «système de type» à vos besoins, le rendre flexible et intelligent, tandis que les systèmes de type de langage OO ont généralement des fonctionnalités fixes. Lorsque les structures de données que vous utilisez sont immuables, une fois que vous avez validé une telle structure, vous pouvez supposer que vos structures sont conformes à vos restrictions, tout comme avec la saisie statique.

Il ne sert à rien d'être prêt à traiter n'importe quel type de données à n'importe quel point d'un programme, typé dynamiquement ou non. Un «type dynamique» est essentiellement une union de tous les types possibles; le limiter à un sous-ensemble utile est une manière valable de programmer.

2

Pour qu'un langage de type statique corresponde à la flexibilité d'un langage de type dynamique, je pense qu'il faudrait beaucoup, peut-être infiniment, de fonctionnalités.

Dans le monde Haskell, on entend beaucoup de sophistication, parfois au point de faire peur, la teminologie. Classes de types Polymorphisme paramétrique Types de données algébriques généralisées. Tapez les familles. Dépendances fonctionnelles Le Ωmega programming language le prend encore plus loin, avec le site Web énumérant des «fonctions de niveau de type» et «le polymorphisme de niveau», parmi d'autres.

Qu'est-ce que tout cela? Des fonctionnalités ajoutées au typage statique pour le rendre plus flexible. Ces caractéristiques peuvent être vraiment cool et ont tendance à être élégantes et époustouflantes, mais elles sont souvent difficiles à comprendre. En dehors de la courbe d'apprentissage, les systèmes de type échouent souvent à modéliser élégamment les problèmes du monde réel. Un bon exemple en est l'interaction avec d'autres langues (une motivation majeure pour C# 4's dynamic feature).

Les langages à typage dynamique vous permettent d'implémenter votre propre cadre de règles et de suppositions sur les données, plutôt que d'être contraints par le système de type statique toujours limité. Cependant, "votre propre framework" ne sera pas contrôlé par machine, ce qui signifie qu'il vous incombe de vous assurer que votre "système de type" est sûr et que votre code est bien "typé". Une chose que j'ai apprise en apprenant Haskell est que je peux porter les leçons apprises sur le typage fort et le raisonnement sonore sur des langages plus faibles, tels que C et même assembler, et faire moi-même le «contrôle de type». À savoir, je peux prouver que les sections de code sont correctes en soi, en gardant à l'esprit les règles que mes fonctions et valeurs sont censées suivre, et les hypothèses que je suis autorisé à faire sur d'autres fonctions et valeurs. Lors du débogage, je passe en revue et vérifie à nouveau les choses et réfléchis à la question de savoir si mon approche est bonne ou non.

La ligne de fond: la frappe dynamique met plus de flexibilité à portée de main. D'un autre côté, les langages statiques ont tendance à être plus efficaces (par ordre de grandeur), et les bons systèmes de type statique réduisent drastiquement le temps de débogage en laissant l'ordinateur en faire beaucoup pour vous. Si vous voulez les avantages des deux, installer un vérificateur de type statique dans votre cerveau en apprenant des langues décentes, fortement typées.

+3

Je suis d'accord avec le second point, mais pas le premier. Les classes de types, GADTs et FunDeps produisent tous quelque chose de * plus expressif * qu'un langage typique typé dynamiquement.Ils vous permettent essentiellement de manipuler des contextes de classe * indépendants * de valeurs typées individuelles.Vous pouvez non seulement faire cela avec des langages typés dynamiquement standard, – sclv

7

Mis à part le confsion de statique vs dynamique et forte par rapport à typage faible:

Ce que vous voulez mettre en œuvre dans votre exemple est pas vraiment résolu par la plupart des systèmes de typage statiques existants. Les contrôles de portée et les complications comme le 31 février et en particulier les entrées analysées sont généralement vérifiées pendant l'exécution, quel que soit le type de système que vous avez.

Votre exemple étant en Erlang J'ai quelques recommandations:

  • Utiliser les dossiers. En plus d'être utile et serviable pour tout un tas de raisons, la vous donner de la vérification du type d'exécution facile sans beaucoup d'efforts .: par exemple

    is_same_day(#datetime{year=Y1, month=M1, day=D1}, 
          #datetime{year=Y2, month=M2, day=D2}) -> ... 
    

    Effortless correspond seulement pour deux enregistrements datetime. Vous pouvez même ajouter des gardes pour vérifier les fourchettes si la source n'est pas fiable. Et il se conforme aux erlangs, il laisse tomber la méthode de gestion des erreurs: si aucune correspondance n'est trouvée, vous obtenez un badmatch, et vous pouvez gérer cela au niveau où il est approprié (généralement le niveau de superviseur).

  • En général écrire votre code qu'il se bloque lorsque les hypothèses ne sont pas valides

  • Si cela ne se sent pas assez statique vérifié: utiliser typer et dialyzer pour trouver le genre d'erreurs qui peut être trouvé statiquement, quelle que soit reste sera vérifié à l'exécution.

  • Ne soyez pas trop restrictive dans vos fonctions ce que « types » vous acceptez, parfois la fonctionnalité ajoutée de faire juste someting utile même pour les différentes entrées vaut plus que de vérifier les types et gammes sur toutes les fonctions. Si vous le faites habituellement, vous remarquerez l'erreur assez tôt pour que ce soit facilement réparable. Ceci est particulièrement vrai pour un langage fonctionnel où vous savez toujours d'où vient chaque valeur.

+0

Merci, ce sont de bons conseils, mais ma question est toujours sur votre deuxième point, bien souvent, mes suppositions ne correspondent pas bien aux expressions de garde, donc je finis wri vérifier les fonctions de vérification de type/gamme et s'assurer manuellement qu'elles sont appelées aux moments appropriés. Mon point est, je pense qu'il est nécessaire d'inventer ces constructions de contrôle de type/validation et de les appliquer moi-même dans les langues qui ne les ont pas. Ma question est de savoir si c'est la meilleure/seule façon, ou s'il y a d'autres modèles et paradigmes qui résolvent cet ensemble de problèmes de façon plus «naturelle» pour erlang et les langues similaires. – drfloob

3

Beaucoup de bonnes réponses, permettez-moi d'ajouter:

Y at-il des paradigmes de programmation existants (pour les langues sans types) qui permettent d'éviter la nécessité d'utiliser ou d'inventer des systèmes de type? Le paradigme le plus important, en particulier dans Erlang, est le suivant: Supposons que le type est correct, sinon laissez-le tomber en panne. N'écrivez pas trop de code paranoïaque, mais supposez que l'entrée que vous obtenez est du bon type ou du bon modèle. Ne pas écrire (il y a des exceptions à cette règle, mais en général)

foo({tag, ...}) -> do_something(..); 
foo({tag2, ...}) -> do_something_else(..); 
foo(Otherwise) -> 
    report_error(Otherwise), 
    try to fix problem here... 

Tuez la dernière clause et l'ont écrasé tout de suite. Laissez un superviseur et d'autres processus effectuer le nettoyage (vous pouvez utiliser monitors() pour les processus de conciergerie pour savoir quand un plantage s'est produit).

Ne être précis cependant. Ecrivez

bar(N) when is_integer(N) -> ... 

baz([]) -> ... 
baz(L) when is_list(L) -> ... 

si la fonction n'est connue que pour fonctionner avec des entiers ou des listes respectivement. Oui, c'est un contrôle d'exécution mais le but est de transmettre l'information au programmeur. En outre, HiPE tend à utiliser l'indice pour l'optimisation et à éliminer le contrôle de type si possible. Par conséquent, le prix peut être inférieur à ce que vous pensez qu'il est.

Vous choisissez une langue non typée/à typage dynamique. Le prix à payer est donc une vérification de type et des erreurs de collision se produiront au moment de l'exécution. Comme le suggèrent d'autres publications, une langue typée statiquement n'est pas exempte de certaines vérifications - le système de type est (habituellement) une approximation d'une preuve d'exactitude. Dans la plupart des langages statiques, vous obtenez souvent des informations auxquelles vous ne pouvez pas faire confiance. Cette entrée est transformée à la "bordure" de l'application puis convertie en un format interne. La conversion sert à marquer la confiance: à partir de maintenant, la chose a été validée et nous pouvons supposer certaines choses à son sujet. La puissance et l'exactitude de cette hypothèse sont directement liées à la signature de son type et à la qualité du programmeur lorsqu'il jongle avec les types statiques de la langue. Y a-t-il des recommandations communes sur la façon de résoudre les problèmes que le typage statique résout dans les langages typés dynamiquement (sans réinventer penaud les types)?

Erlang a le dialyzer qui peut être utilisé pour analyser de façon statique et déduire les types de vos programmes. Il ne produira pas autant d'erreurs de type qu'un vérificateur de type, par exemple, Ocaml, mais il ne va pas non plus "pleurer le loup": Une erreur du dialyseur est une erreur dans le programme.Et il ne rejettera pas un programme qui peut fonctionner correctement. Un exemple simple est:

and(true, true) -> true; 
and(true, _) -> false; 
and(false, _) -> false. 

L'invocation and(true, greatmistake) retournera false, mais un système de type statique rejettera le programme car il déduire de la première ligne que la signature de type prend une valeur booléenne() comme 2ème paramètre . Le dialyseur acceptera cette fonction en contraste et lui donnera la signature (boolean(), term()) -> boolean(). Il peut le faire, car il n'est pas nécessaire de protéger a priori pour une erreur. S'il y a une erreur, le système d'exécution a une vérification de type qui va le capturer.

+0

Pour clarifier, ce n'est pas que nous "assumons" des types en erlang, nous devons les affirmer explicitement. Le problème est que vos exemples sont tous très simples; il n'est pas aussi propre ou facile d'affirmer les types de données "complexes" que dans l'exemple donné. – drfloob

1

Un langage typé statiquement détecte les erreurs de type lors de la compilation. Un langage dynamiquement typé les détecte lors de l'exécution. Il existe des restrictions modestes sur ce que l'on peut écrire dans un langage typé statiquement, de sorte que toutes les erreurs de type peuvent être interceptées au moment de la compilation.

Mais oui, vous avez toujours des types même dans un langage dynamiquement typé, et c'est une bonne chose. Le problème est que vous vous promenez dans beaucoup de contrôles d'exécution pour vous assurer que vous avez les types que vous faites, puisque le compilateur n'a pas pris soin de cela pour vous.

Erlang a un très bel outil pour spécifier et vérifier statiquement beaucoup de types - dialyseur: Erlang type system, pour les références.

Donc, ne réinventez pas les types, utilisez les outils de typage déjà fournis par Erlang pour gérer les types qui existent déjà dans votre programme (mais que vous n'avez pas encore spécifié).

Et cela à lui seul n'élimine pas les contrôles de portée, malheureusement. Sans beaucoup de sauce spéciale, vous devez vraiment le faire vous-même par convention (et les constructeurs intelligents, etc. pour vous aider), ou vous rabattre sur les contrôles d'exécution, ou les deux.

+0

Si je dois inventer des constructeurs et des mutateurs, et appliquer des conventions standard autour de leur utilisation moi-même (cela a été le cas pour moi plusieurs fois maintenant), je ne peux pas justifier l'utilisation d'un langage qui n'a pas ces déjà. J'adore travailler en erlang, mais quel avantage y a-t-il si de gros morceaux de temps et de code réinventent et appliquent ce que beaucoup d'autres langues vous donnent gratuitement? – drfloob

+0

@drfloop - Si dialyzer n'est pas suffisant pour vos besoins, alors oui, je suis absolument d'accord! Sur Haskell! :-) – sclv

Questions connexes