Typed Racket es muy diferente de Haskell. Los sistemas de tipos en Lisp y Scheme, y de hecho los sistemas de tipos en ecosistemas de lenguaje tradicionalmente sin tipo en general, tienen un objetivo fundamental que otros sistemas de tipo no tienen: interoperar con el código sin tipo existente . Typed Racket, por ejemplo, introdujo reglas de escritura completamente nuevas para acomodar varios modismos de Racket. Considere esta función:
(define (first some-list)
(if (empty? some-list)
#f
(car some-list)))
Para listas no vacías, esto devuelve el primer elemento. Para listas vacías, esto devuelve falso. Esto es común en idiomas sin tipo; un lenguaje escrito usaría algún tipo de contenedor como Maybe
o arrojaría un error en el caso vacío. Si quisiéramos agregar un tipo a esta función, ¿qué tipo debería usarse? No lo es [a] -> a
(en notación Haskell), porque puede devolver falso. Tampoco lo es [a] -> Either a Boolean
, porque (1) siempre devuelve falso en el caso vacío, no es un booleano arbitrario y (2) un tipo Cualquiera de los dos encapsularía los elementos Left
y falsificaría Right
y requeriría que "desenvolviera los dos" para llegar al elemento real. En cambio, el valor devuelve una unión verdadera- no hay constructores de envoltura, simplemente devuelve un tipo en algunos casos y otro tipo en otros casos. En Typed Racket, esto se representa con el constructor de tipo de unión:
(: first (All (A) (-> (Listof A) (U A #f))))
(define (first some-list)
(if (empty? some-list)
#f
(car some-list)))
El tipo (U A #f)
indica que la función podría devolver un elemento de la lista o falso sin ninguna Either
instancia de ajuste . El verificador de tipos puede inferir que some-list
es de tipo (Pair A (Listof A))
o de la lista vacía, y además infiere que en las dos ramas de la declaración if se sabe cuál es el caso . El verificador de tipos sabe que en la (car some-list)
expresión, la lista debe tener el tipo (Pair A (Listof A))
porque la condición if lo garantiza. Esto se llama tipificación de ocurrencia y está diseñado para facilitar la transición del código sin tipo al código escrito.
El problema es la migración. Hay una tonelada de código de Racket sin tipo, y Typed Racket no puede forzarlo a abandonar todas sus bibliotecas sin tipo favoritas y pasar un mes agregando tipos a su base de código si desea usarlo. Este problema se aplica cada vez que agrega tipos gradualmente a una base de código existente, vea TypeScript y su tipo Any para obtener una aplicación javascript de estas ideas.
Un sistema de tipo gradual debe proporcionar herramientas para tratar modismos comunes sin tipo e interactuar con el código sin tipo existente. Usarlo será bastante doloroso de lo contrario, consulte "Por qué ya no usamos Core.typed" para ver un ejemplo de Clojure.