Types en Lisp et Scheme


10

Je vois maintenant que Racket a des types. À première vue, il semble être presque identique à la frappe Haskell. Mais le CLOS de Lisp couvre-t-il une partie de l'espace couvert par les types Haskell? La création d'un type Haskell très strict et d'un objet dans n'importe quel langage OO semble vaguement similaire. C'est juste que j'ai bu du kool-aid Haskell et je suis totalement paranoïaque que si je descends la route de Lisp, je serai foutu à cause de la frappe dynamique.

Réponses:


6

Le système de type CL est plus expressif que celui de Haskell, par exemple, vous pouvez avoir un type (or (integer 1 10) (integer 20 30))pour une valeur 1,2,...9,10,20,21,...,30.

Cependant, les compilateurs Lisp ne forcent pas leur compréhension de la sécurité des types dans votre gorge, vous pouvez donc ignorer leurs "notes" - à vos risques et périls .

Cela signifie que vous pouvez écrire Haskell en Lisp (pour ainsi dire) en déclarant tous les types de valeur et en vous assurant soigneusement que tous les types nécessaires sont déduits, mais il est ensuite plus facile d'utiliser Haskell en premier lieu.

Fondamentalement, si vous voulez un typage statique fort, utilisez Haskell ou OCaml, si vous voulez un typage dynamique fort, utilisez Lisp. Si vous voulez un typage statique faible, utilisez C, si vous voulez un typage dynamique faible, utilisez Perl / Python. Chaque chemin a ses avantages (et ses fanatiques) et ses inconvénients (et ses détracteurs), vous bénéficierez donc de tous les apprendre.


19
Quiconque utilise des termes comme «forcer la sécurité de type dans la gorge» ne comprend pas ce qu'est la sécurité de type ni pourquoi elle est utile.
Mason Wheeler

11
@MasonWheeler: quiconque tire une conclusion radicale à partir d'une seule phrase se trompe plus souvent qu'autrement. Comme, par exemple, dans ce cas.
sds

4
Le sujet étant des langues, le terme «au fond de la gorge» est une imagerie appropriée et appropriée.
luser droog

1
@KChaloux: Je voulais dire "expressif", comme l'explique l'exemple.
sds

4
Vous avez tout à l'envers. La frappe dynamique est un cas particulier de frappe statique, où vous forcez le programmeur à utiliser 1 type pour tout. Je peux faire la même chose (verbalement) dans la plupart des langages à typage statique en déclarant chaque variable comme type Objectou quelle que soit la racine de l'arbre de type. C'est moins expressif, car vous êtes privé de la possibilité de dire que certaines variables ne peuvent contenir que certaines valeurs.
Doval

5

La raquette typée est très différente de Haskell. Les systèmes de types en Lisp et Scheme, et en fait les systèmes de types dans les écosystèmes langagiers traditionnellement non typés en général, ont un objectif fondamental que les autres systèmes de types n'interagissent pas avec le code non typé existant . La raquette typée, par exemple, a introduit de toutes nouvelles règles de frappe pour s'adapter à divers idiomes de raquette. Considérez cette fonction:

(define (first some-list)
  (if (empty? some-list)
      #f
      (car some-list)))

Pour les listes non vides, cela renvoie le premier élément. Pour les listes vides, cela renvoie false. Ceci est courant dans les langues non typées; un langage tapé utiliserait un type de wrapper comme Maybeou lancerait une erreur dans le cas vide. Si nous voulions ajouter un type à cette fonction, quel type utiliser? Ce n'est pas le cas [a] -> a(en notation Haskell), car il peut retourner faux. Ce n'est pas non plus le cas [a] -> Either a Boolean, car (1) il renvoie toujours false dans le cas vide, pas un booléen arbitraire et (2) un type Soit encapsule les éléments dans Leftet false Rightet vous oblige à "déballer l'un ou l'autre" pour accéder à l'élément réel. Au lieu de cela, la valeur renvoie une véritable union- il n'y a pas de constructeur enveloppant, il retourne simplement un type dans certains cas et un autre type dans d'autres cas. Dans Typed Racket, cela est représenté par le constructeur de type d'union:

(: first (All (A) (-> (Listof A) (U A #f))))
(define (first some-list)
  (if (empty? some-list)
      #f
      (car some-list)))

Le type (U A #f)indique que la fonction peut renvoyer un élément de la liste ou false sans aucune Eitheroccurrence d' habillage . Le vérificateur de type peut déduire qu'il some-lists'agit du type (Pair A (Listof A))ou de la liste vide, et en outre il déduit que dans les deux branches de l'instruction if, on sait lequel de ces cas est connu . Le vérificateur de type sait que dans l' (car some-list)expression, la liste doit avoir le type (Pair A (Listof A))car la condition if le garantit. Cela s'appelle le typage d'occurrence et est conçu pour faciliter la transition du code non typé au code typé.

Le problème est la migration. Il y a une tonne de code Racket non typé, et Typet Racket ne peut pas simplement vous forcer à abandonner toutes vos bibliothèques préférées non typées et à passer un mois à ajouter des types à votre base de code si vous souhaitez l'utiliser. Ce problème s'applique chaque fois que vous ajoutez progressivement des types à une base de code existante, voir TypeScript et son type Any pour une application javascript de ces idées.

Un système de type progressif doit fournir des outils pour gérer les idiomes non typés courants et interagir avec le code non typé existant. L'utiliser sera assez douloureux sinon, voir "Pourquoi nous n'utilisons plus Core.typed" pour un exemple de Clojure.


1
En économie, c'est ce qu'on appelle la loi de Gresham : la mauvaise monnaie chasse la bonne. Il a également tendance à trouver des applications en ingénierie. Lorsque vous avez deux sous-systèmes théoriquement équivalents au sein de votre système, trop souvent le pire causera trop de problèmes pour que le meilleur en vaille la peine, comme nous le voyons ici.
Mason Wheeler

"un exemple de conception d'un système de type": cette phrase n'est pas complète
coredump

@MasonWheeler Le pire est, laissez-moi deviner, la vérification de type dynamique. Donc, dès que vous l'introduisez, cela ne vaut pas la peine d'effectuer une analyse statique?
coredump

1
@coredump - La saisie graduelle en vaut la peine, sauf si vous interagissez mal avec du code non typé. Le type Any dans TypeScript, par exemple, indique simplement au vérificateur de types d'abandonner, ce qui met essentiellement à profit les avantages du système de type car il peut provoquer une mauvaise valeur de se propager à travers de grandes quantités de code vérifié statiquement. La raquette typée utilise des contrats et des limites de module pour éviter ce problème.
Jack

1
@coredump Lisez l'article de Clojure. La typographie dynamique, en particulier avec tout le code tiers qui n'avait pas de types statiques disponibles, a vraiment gâché les choses pour leurs tentatives d'améliorer leur base de code avec des types statiques.
Mason Wheeler
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.