Nivel 5 · El cubo-λ
λ2: polimorfismo
El cálculo lambda simplemente tipado garantiza que los cálculos terminan, pero paga un precio de expresividad. Para escribir la función identidad hay que dar una versión para , , otra para , , y así sucesivamente. Los detalles que se perdieron fueron las funciones genéricas o polimórficas, que operan sobre una familia entera de tipos. Todo el material de este módulo sigue a Nederpelt y Geuvers (2014) .
El Sistema (también conocido como Cálculo Lambda Polimórfico o Sistema ) garantiza que los términos dependan de los tipos. En resumen, el propósito de es extender los conceptos de abstracción y aplicación: en operan solamente sobre términos, mientras que ahora operan también sobre tipos, con dos construcciones nuevas.
- Abstracción de tipos: se crea un nuevo tipo de función, , que toma un tipo como argumento y devuelve un término . Se usa la mayúscula para distinguirla visualmente de la abstracción de términos .
- Aplicación de tipos: se puede tomar una de estas funciones polimórficas y aplicarla a un tipo concreto , escribiendo .
En se puede capturar la identidad en un único término polimórfico:
Este término es una función que primero espera un tipo () y, cuando lo recibe, devuelve la función identidad para ese tipo específico.
- : si se quiere la identidad para números naturales, se aplica al tipo . La -reducción de segundo orden da el resultado esperado: Por lo tanto, se tiene la identidad para naturales a partir del término genérico.
- Tipos funcionales: la generalidad de va más allá. Si se necesita una identidad para funciones de tipo , el proceso es idéntico:
En general, eso es lo que busca el polimorfismo: con una sola definición se captura un patrón infinito.
Los tipos Π
Ahora bien, cuando se intenta asignar un tipo a la identidad polimórfica se topa con un problema. Dado que tiene tipo , una primera conjetura para el tipo de podría ser:
donde representa el «tipo de todos los tipos». Pero aplicando la -equivalencia, donde los nombres de las variables ligadas no importan, se tendría:
Si se usara el tipo tentativo, sus tipos serían:
Dos términos idénticos tendrían tipos diferentes; por definición de la -equivalencia, esto no es posible. La variable de tipo ( o ) debería estar ligada en el término y en el tipo.
Para resolver esta inconsistencia se introduce el conector de tipo producto cuantificado universalmente o tipo . En lugar de la flecha simple se escribe:
Este tipo representa a las funciones que, para todo tipo , devuelven un término de tipo . La variable está ahora ligada por el cuantificador , resolviendo el problema. Ahora se puede asignar un tipo único a la identidad polimórfica:
Por la ligadura de , la -equivalencia se preserva como se debe:
El sistema formal λ2
Con la intuición y la maquinaria de los tipos establecidas, se puede definir formalmente el Sistema .
Usando notación gramatical, como en el módulo del cálculo lambda, se tiene:
- El conjunto de tipos, , se extiende para incluir los tipos : donde es el conjunto de las variables de tipo: , etc.
- El conjunto de términos pre-tipados, , se extiende para incluir abstracción y aplicación de tipos:
Las reglas de derivación de son las mismas de más tres nuevas reglas: una de formación de tipos y dos para manejar la abstracción y aplicación de tipos, que se detallan a continuación. Los contextos de se amplían en consecuencia: además de declaraciones de variables de término con , admiten declaraciones de variables de tipo , y toda variable de tipo debe declararse antes de su primer uso.
(abstracción) Si, asumiendo que es un tipo, se puede demostrar que el término tiene tipo , entonces se concluye que tiene tipo :
(aplicación) Si se tiene un término polimórfico de tipo y un tipo bien formado , se puede aplicar a . El tipo resultante es , pero con todas las ocurrencias de reemplazadas por :
(formación) si y todas las variables de tipo libres de están declaradas en . Esta regla es la que deriva la segunda premisa de (aplicación) para tipos compuestos como o .
Las reglas (variable), (aplicación) y (abstracción) de se mantienen sin cambios.
Demostrar el juicio:
A probar:
- El objetivo es derivar el cuerpo más interno de la expresión, que es la variable . Para esto se asume un contexto donde tanto el tipo como la variable con dicho tipo están declarados. Sea . Se aplica la regla (variable), ya que la condición es un miembro del contexto :
- El objetivo es demostrar . Usando el juicio (1) como premisa, se aplica la regla de (abstracción). La regla abstrae la variable de término , elimina del contexto y construye un tipo flecha ():
- Para demostrar el juicio original se usa el juicio (2) como premisa y se aplica la regla de abstracción de tipo, denotada (abst). Esta regla abstrae la variable de tipo , elimina del contexto y construye un tipo universal (cuantificado) :
En resumen, se tiene el siguiente árbol de derivación:
Ejemplo en Lean: la identidad polimórfica
La parte teórica de la identidad polimórfica,
, puede ser bastante
abstracta. Sin embargo, usando Lean la implementación es directa y permite manipular las
expresiones como en un lenguaje de programación. La sintaxis {α : Type} introduce una
variable de tipo implícita, equivalente a la abstracción de tipo .
def identidad_polimorfica {α : Type} (x : α) : α := xSe comprueba el tipo que Lean le asigna:
#check identidad_polimorfica-- identidad_polimorfica {α : Type} (x : α) : αUnos ejemplos rápidos de uso; se utilizan los -- para mostrar los resultados:
#eval identidad_polimorfica 42 -- 42#eval identidad_polimorfica true -- true#eval identidad_polimorfica "hola" -- "hola"En resumen, Lean permite el polimorfismo porque la función se adapta al argumento. Esto muestra cómo se puede reutilizar código, usando definiciones anteriores y exhibiendo la utilidad del polimorfismo. El Sistema hereda las mismas propiedades de , incluyendo la confluencia (Church–Rosser) y la normalización fuerte para términos bien tipados, lo que garantiza que cualquier cómputo polimórfico bien formado terminará. La -reducción se extiende incluyendo la aplicación de tipos: . La propiedad de la reducción del sujeto también se mantiene: si un término tiene un cierto tipo, cualquier término al que se reduzca también tendrá ese mismo tipo. Los detalles y demostraciones se pueden encontrar en Nederpelt y Geuvers (2014) .
Con los términos dependen de los tipos. La página siguiente añade la dependencia opuesta dentro del mundo de los tipos: operadores que construyen tipos a partir de otros tipos.