Nivel 5 · El cubo-λ
λω̲: constructores de tipos
Con se tiene la capacidad de que los términos dependan de los tipos, dando el
mecanismo del polimorfismo. Ahora bien, no se tiene forma de crear constructores de
tipos genéricos, como un tipo lista List que pueda ser aplicado a nat para crear la
lista de los naturales List nat. Para lograr esto se necesita que los tipos puedan
depender de otros tipos. Esta es la siguiente abstracción que será necesario deducir.
Este es el sentido del Sistema : introduce una nueva jerarquía que permite definir operadores que transforman tipos en otros tipos. La presentación sigue a Nederpelt y Geuvers (2014) .
La jerarquía de kinds: los tipos de los tipos
Si los términos tienen tipos, ¿qué tienen los tipos? La respuesta en es que los tipos tienen kinds. Los kinds son los «tipos de los tipos», que permiten clasificar los tipos y los constructores de tipos. La jerarquía es la siguiente:
- El kind : es el kind de todos los tipos que pueden ser habitados por términos. Por ejemplo, , y son todos de kind :
- Kinds funcionales (por ejemplo ): es el kind de los constructores de tipos que toman un tipo (de kind ) y devuelven otro tipo (de kind ). Un constructor que toma un tipo y devuelve el tipo de las funciones de a sería : De igual manera, un constructor como (el constructor de tipo producto, que toma dos tipos y y genera el tipo producto , cuyos términos son pares ordenados con y ) tendría el kind .
- El sort : así como los términos tienen tipos y los tipos tienen kinds, los kinds también deben tener un súper tipo que los clasifique. Este nivel superior es el súper tipo :
En general, la jerarquía (términos : tipos : kinds : ) es la que da a
su estructura. Esta jerarquía tiene una conexión directa con
Lean: usando #check, Lean revela el tipo de cualquier construcción, permitiendo verla en
acción.
-
Tipos de kind . En la teoría, y son tipos habitados por términos y por lo tanto tienen kind . En Lean, el kind se representa como
Type:-- Se pide a Lean el "tipo" del tipo Nat#check Nat -- Lean responde: Nat : Type-- Lo mismo para tipos funcionales#check Nat -> Bool -- Lean responde: Nat -> Bool : TypeEsto confirma que
NatyNat -> Boolson, en el lenguaje de Lean, de tipoType(el análogo a ). -
Constructores de tipos de kind . En la teoría,
Listtendría el kind . Para que Lean muestre esta versión simplificada (ignorando niveles superiores), se le pide que verifique aListcomo si fuera unType -> Type:-- Se le pide a Lean que considere a List como un constructor de Nivel 0.#check (List : Type -> Type)-- Lean responde: List : Type -> TypeLa respuesta
Type -> Typees la notación de Lean para el kind . Cuando se le da un argumento (comoNat), produce un nuevo tipo habitable:-- Aplicamos el constructor List al tipo Nat#check List Nat -- Lean responde: List Nat : TypeDe esta forma,
List : Type -> TypetomaNat : Typey devuelveList Nat : Type. -
El súper tipo y los universos de Lean. Finalmente, si los tipos tienen
Typecomo su «tipo», ¿cuál es el «tipo» deType? Aquí es donde Lean implementa el concepto de a través de una jerarquía de universos, para evitar paradojas lógicas:-- ¿Cuál es el "tipo" de "Type"?#check Type -- Lean responde: Type : Type 1#check Type 1 -- Lean responde: Type 1 : Type 2-- y así sucesivamente...Type 1es el análogo en Lean del súper tipo paraType(que en este contexto es ). Esta jerarquía (Type : Type 1 : Type 2 ...) es la manera en que los asistentes de pruebas implementan los niveles de los «tipos de tipos».
Constructores y niveles
- Si y , entonces es un constructor.
- Si , y , entonces es un constructor propio.
- El conjunto de sorts es .
Un constructor propio es, en esencia, un constructor de tipos que requiere al menos un
argumento para formar un tipo completo (de kind ). Por ejemplo, el constructor List
tiene el kind ; es un constructor propio porque necesita un tipo (como Nat) para
formar el tipo List Nat (que sí tiene kind ). En cambio, Nat es un constructor, pero
no es propio.
Se distinguen cuatro niveles en :
- Nivel 1: los términos ().
- Nivel 2: los constructores (tipos y constructores propios).
- Nivel 3: los kinds ( y funciones entre ellos, como ).
- Nivel 4: el súper tipo .
En los juicios pueden incluir múltiples niveles. Por ejemplo, combina los niveles 1 a 4 en una cadena de juicios (nótese que un término solo habita tipos, de kind ); una cadena de los niveles 2 a 4 es
Ejemplos de kinds:
Un constructor como tiene kind , mientras que tiene kind .
Reglas fundamentales de derivación
El juicio establece que pertenece a , el nivel superior de clasificación.
Aquí representa un sort (). Esta regla permite extender el contexto con una declaración , siempre que sea derivable en el contexto original.
La regla de variable tiene dos roles fundamentales, dependiendo del sort al que pertenece :
- Introducir variables de término (cuando ): si ya se ha establecido que es un tipo (es decir, ), la regla permite añadir una nueva variable de término de ese tipo. Por ejemplo, si se tiene , se puede extender el contexto a .
- Introducir variables de tipo (cuando ): si se ha establecido que es un kind (es decir, ), la regla permite añadir una nueva variable de tipo de ese kind. El caso más importante es usar el juicio (de la regla de sort) para extender el contexto con una variable de tipo genérica, como en .
Recordatorio. Se usan letras romanas para variables de término (reservando cuando el tipo es ) y letras griegas para variables de tipo (con ). Asimismo, denotan tipos o constructores. La restricción simplemente asegura que todas las variables declaradas en el contexto sean únicas.
Demostrar .
En formato de árbol, encadenando (sort) y dos usos de (variable):
También con el formato de banderas, donde cada bandera introduce una suposición del contexto:
| 1 | (sort) | |
| introducción de α | ||
| 2 | (variable) en 1 | |
| introducción de x | ||
| 3 | (variable) en 2 |
La regla de variable, por sí sola, no permite derivar juicios como
Este juicio es intuitivamente válido: si se sabe que es un tipo en un contexto, debería seguir siéndolo si se añaden más suposiciones.
Esta regla permite extender el contexto con una nueva declaración al final, siempre que sea derivable en y tenga un sort bien formado (). Esencialmente, establece que añadir suposiciones válidas no invalida conclusiones anteriores. Con ella se derivan, por ejemplo, los juicios , y .
A diferencia de sistemas más simples, donde la sintaxis de los tipos se define de forma externa, aquí la construcción de tipos funcionales es una regla de derivación en sí misma, y funciona en múltiples niveles debido a la variable de sort :
- Formación de tipos (cuando ): si y son tipos (tienen kind ), la regla permite construir un nuevo tipo función , que también tendrá kind . Es el uso más común, para crear tipos como .
- Formación de kinds (cuando ): si y son kinds (tienen sort ), la regla permite formar un nuevo kind funcional . Por ejemplo, a partir de y , que pertenecen a , se deriva que el kind también pertenece a .
Es importante notar que en no existen los tipos de , ya que los términos no dependen de tipos.
(aplicación) Permite aplicar una función a un argumento :
(abstracción) Permite construir una función :
La segunda premisa de (abstracción) garantiza explícitamente que el tipo funcional resultante sea bien formado, es decir, que pertenezca a un sort válido.
Ambas reglas tienen un doble rol, ya que el sort puede ser o :
- Cuando , el tipo es un tipo de nivel 2 (como ) y las reglas operan sobre términos para producir términos.
- Cuando , el tipo es un kind de nivel 3 (como ) y las reglas operan sobre constructores para producir nuevos constructores.
La reducción se extiende de forma natural a este sistema. Por ejemplo, la aplicación de un constructor de tipos a un tipo concreto sigue la misma lógica:
Una derivación trabajada
Las derivaciones en suelen incluir pasos que, aunque necesarios para la formalidad, son obvios o repetitivos, como aplicaciones de (sort), (variable) y (debilitamiento). Se adoptan derivaciones abreviadas para omitir estos pasos y destacar las aplicaciones esenciales de reglas como (abstracción), (aplicación) y (conversión).
Se deriva, en el contexto , el juicio . La derivación formal completa, en notación de banderas, es:
| (a) | ||
| 1 | (debilitamiento) de (sort) | |
| (b) | ||
| 2 | (variable) de 1 | |
| 3 | (formación) de 2, 2 | |
| 4 | (formación) de 1, 1 | |
| 5 | (abstracción) de 3, 4 | |
| 6 | (variable) de (sort) | |
| 7 | (aplicación) de 5, 6 |
A continuación se detalla cada paso de la derivación formal:
- Paso 1: . Dentro de la hipótesis (a), se aplica (debilitamiento) sobre , de la regla (sort).
- Paso 2: . Con el juicio del paso 1 como premisa, se introduce una nueva variable de tipo usando (variable).
- Paso 3: . Se aplica (formación) de tipos función. La regla requiere dos premisas, ambas satisfechas por el juicio del paso 2.
- Paso 4: . Se aplica la formación de kinds: dado que (paso 1), se puede construir el kind funcional , que también pertenece a .
- Paso 5: . Se aplica (abstracción) sobre el juicio del paso 3: se abstrae la variable de tipo del constructor para crear un constructor de tipos.
- Paso 6: . Por (variable), la hipótesis (a) es un juicio válido en su propio contexto.
- Paso 7: . Se aplica (aplicación): se toma el constructor de tipos del paso 5 y se le aplica el tipo del paso 6, resultando en un nuevo tipo de kind .
La versión abreviada se enfoca en los pasos de construcción más relevantes:
| (a) | ||
| (b) | ||
| 1 | (formación) de (b), (b) | |
| 2 | (abstracción) de 1 | |
| 3 | (aplicación) de 2 y (a) |
En esta versión los pasos se renumeran y se omiten los juicios considerados triviales: los pasos 1, 2, 4 y 6 de la derivación completa (aplicaciones de (sort), (variable), (debilitamiento) y formación de kinds) se asumen implícitamente; el paso 1 abreviado corresponde al paso 3 original, el 2 al 5 y el 3 al 7. El resultado es una demostración mucho más limpia que resalta las tres operaciones clave: la formación del tipo función, la abstracción para crear un constructor y la aplicación de ese constructor.
Regla de conversión
La segunda premisa garantiza que sea un tipo o kind bien formado; esto no se deduce automáticamente de la equivalencia de la conversión entre y .
Dado el contexto , se quiere mostrar que tiene el tipo :
En formato de banderas, la derivación abreviada sería:
| (a) | ||
| 19 | (variable) | |
| 20 | (conversión) sobre 19, ya que (λα:*. α → α) β =β β → β |
Ahora bien, en Lean se pueden definir constructores de tipos propios, como el ejemplo teórico , y verificar su tipo:
-- Definimos un constructor que toma un tipo T y devuelve el tipo T -> Tdef mapeo_interno (T : Type) : Type := T -> T
-- Verificamos su "kind"#check mapeo_interno -- Lean responde: mapeo_interno : Type -> TypeLa regla de (conversión) dice que tipos equivalentes son intercambiables. Con
#reduce, que en Lean sirve para hacer todas las reducciones posibles a un término, se ve
que mapeo_interno Nat es equivalente a Nat -> Nat:
-- ¿A qué es equivalente mapeo_interno Nat?#reduce mapeo_interno Nat -- Lean muestra el resultado: Nat -> NatResumen del sistema de reglas de λω̲
Las reglas de derivación en generalizan las de sistemas anteriores para operar en todos los niveles de la jerarquía.
| Regla | Derivación |
|---|---|
| (sort) | |
| (variable) | |
| (debilitamiento) | |
| (formación) | |
| (aplicación) | |
| (abstracción) | |
| (conversión) |
Esta tabla resume el sistema . Como se puede observar, las reglas de abstracción y formación utilizan exclusivamente el conector no dependiente , lo que distingue a de otros sistemas más expresivos del cubo lambda.
Con el Sistema se añade la posibilidad de crear operadores que manipulan y construyen tipos. Combinado con , da un sistema, (sin subrayado), con polimorfismo y constructores de tipos. Es la base de muchos lenguajes de programación funcional avanzados: por ejemplo, el lenguaje Haskell se apoya en una extensión de este sistema para su sistema de tipos (Hudak et al., 2007) .
Quedan cubiertas dos de las tres dependencias nuevas. La página siguiente introduce la tercera y más importante para la matemática: tipos que dependen de términos.