Tipos en Lisp y Scheme

10

Ahora veo que Racket tiene tipos. A primera vista, parece ser casi idéntico a la escritura de Haskell. ¿Pero el CLOS de Lisp está cubriendo parte del espacio que cubren los tipos de Haskell? Crear un tipo de Haskell muy estricto y un objeto en cualquier lenguaje OO parece vagamente similar. Es solo que he bebido un poco del Haskell kool-aid y estoy totalmente paranoico de que si voy por el camino de Lisp, me fastidiarán debido a la escritura dinámica.

usuario2054900
fuente

Respuestas:

6

El sistema de tipo CL es más expresivo que el de Haskell, por ejemplo, puede tener un tipo (or (integer 1 10) (integer 20 30))para un valor 1,2,...9,10,20,21,...,30.

Sin embargo, los compiladores de Lisp no obligan a su comprensión de la seguridad de tipos a su garganta, por lo que puede ignorar sus "notas", bajo su propio riesgo .

Esto significa que puede escribir Haskell en Lisp (por así decirlo) declarando todos los tipos de valores y asegurándose cuidadosamente de que se infieren todos los tipos necesarios, pero luego es más fácil usar Haskell en primer lugar.

Básicamente, si desea una escritura estática fuerte, use Haskell u OCaml, si desea una escritura dinámica fuerte, use Lisp. Si desea una escritura estática débil, use C, si desea una escritura dinámica débil, use Perl / Python. Cada camino tiene sus ventajas (y fanáticos) y desventajas (y detractores), por lo que se beneficiará al aprenderlos todos.

sds
fuente
19
Cualquiera que use términos como "forzar la seguridad del tipo por la garganta" no entiende qué es la seguridad del tipo o por qué es útil.
Mason Wheeler
11
@MasonWheeler: cualquiera que llegue a una conclusión arrolladora de una sola frase se encontrará mal más a menudo que de otra manera. Como, por ejemplo, en este caso.
sds
44
Dado que el tema es idiomas, el término "bajo la garganta" es apropiado e imaginario.
luser droog
1
@ KChaloux: quise decir "expresivo", como se aclara en el ejemplo.
sds
44
Lo tienes todo al revés. La escritura dinámica es un caso especial de escritura estática, donde obliga al programador a usar 1 tipo para todo. Puedo hacer lo mismo (en detalle) en la mayoría de los lenguajes de tipo estático declarando cada variable como tipo Objecto cualquiera que sea la raíz del árbol de tipos. Esto es menos expresiva, porque se ven privados de la opción de decir que ciertas variables sólo pueden contener ciertos valores.
Doval
5

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 Maybeo 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 Lefty falsificaría Righty 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 Eitherinstancia de ajuste . El verificador de tipos puede inferir que some-listes 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.

Jack
fuente
1
En economía, esto se conoce como la Ley de Gresham : el dinero malo expulsa al bien. También tiende a encontrar aplicaciones en ingeniería. Cuando tiene dos subsistemas teóricamente equivalentes dentro de su sistema, con demasiada frecuencia el peor causará demasiados problemas para hacer que valga la pena usar uno, como estamos viendo aquí.
Mason Wheeler
"un ejemplo de diseño de un sistema de tipos que": esta oración no está completa
coredump
@MasonWheeler La peor es, déjame adivinar, la verificación dinámica de tipos. Entonces, tan pronto como lo presente, ¿no vale la pena realizar un análisis estático?
coredump
1
@coredump: la escritura gradual vale la pena, a menos que interactúe mal con el código sin tipo. Cualquier tipo en TypeScript, por ejemplo, solo le dice al typechecker que se rinda, básicamente desechando los beneficios del sistema de tipos porque puede causar que un valor incorrecto se propague a través de grandes cantidades de código estáticamente verificado. Typed Racket usa contratos y límites de módulos para evitar este problema.
Jack
1
@coredump Lea el artículo de Clojure. Tener una escritura dinámica, particularmente con todo el código de terceros que no tenía tipos estáticos disponibles, realmente arruinó las cosas por sus intentos de mejorar su base de código con tipos estáticos.
Mason Wheeler