Nivel 5 · El cubo-λ

λω̲: constructores de tipos

Con λ2\lambda 2 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 λω\lambda\underline{\omega}: 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 λω\lambda\underline{\omega} 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:

  1. El kind *: es el kind de todos los tipos que pueden ser habitados por términos. Por ejemplo, nat\texttt{nat}, bool\texttt{bool} y natnat\texttt{nat} \rightarrow \texttt{nat} son todos de kind *: nat:,(natnat):.\texttt{nat} : *, \qquad (\texttt{nat} \rightarrow \texttt{nat}) : *.
  2. Kinds funcionales (por ejemplo * \to *): 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 α\alpha y devuelve el tipo de las funciones de α\alpha a α\alpha sería T:=λα:.(αα)T := \lambda \alpha{:}*.\, (\alpha \rightarrow \alpha): (λα:.αα):().(\lambda \alpha{:}*.\, \alpha \rightarrow \alpha) : (* \rightarrow *). De igual manera, un constructor como Pair\texttt{Pair} (el constructor de tipo producto, que toma dos tipos AA y BB y genera el tipo producto A×BA \times B, cuyos términos son pares ordenados (a,b)(a, b) con a:Aa : A y b:Bb : B) tendría el kind * \rightarrow * \rightarrow *.
  3. El sort \square: 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 \square: :,():.* : \square, \qquad (* \rightarrow *) : \square.

En general, la jerarquía (términos : tipos : kinds : \square) es la que da a λω\lambda\underline{\omega} 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.

  1. Tipos de kind *. En la teoría, nat\texttt{nat} y bool\texttt{bool} 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 : Type

    Esto confirma que Nat y Nat -> Bool son, en el lenguaje de Lean, de tipo Type (el análogo a *).

  2. Constructores de tipos de kind * \to *. En la teoría, List tendría el kind * \to *. Para que Lean muestre esta versión simplificada (ignorando niveles superiores), se le pide que verifique a List como si fuera un Type -> Type:

    -- Se le pide a Lean que considere a List como un constructor de Nivel 0.
    #check (List : Type -> Type)
    -- Lean responde: List : Type -> Type

    La respuesta Type -> Type es la notación de Lean para el kind * \to *. Cuando se le da un argumento (como Nat), produce un nuevo tipo habitable:

    -- Aplicamos el constructor List al tipo Nat
    #check List Nat -- Lean responde: List Nat : Type

    De esta forma, List : Type -> Type toma Nat : Type y devuelve List Nat : Type.

  3. El súper tipo \square y los universos de Lean. Finalmente, si los tipos tienen Type como su «tipo», ¿cuál es el «tipo» de Type? Aquí es donde Lean implementa el concepto de \square 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 1 es el análogo en Lean del súper tipo \square para Type (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

Definición 8.6 (Constructor, constructor propio y sort)
  • Si κ:\kappa : \square y M:κM : \kappa, entonces MM es un constructor.
  • Si κ:\kappa : \square, M:κM : \kappa y κ\kappa \neq *, entonces MM es un constructor propio.
  • El conjunto de sorts es {,}\{*, \square\}.

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 * \to *; 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.

Definición 8.7 (Niveles en λω̲)

Se distinguen cuatro niveles en λω\lambda\underline{\omega}:

  • Nivel 1: los términos (tt).
  • Nivel 2: los constructores (tipos y constructores propios).
  • Nivel 3: los kinds (* y funciones entre ellos, como * \rightarrow *).
  • Nivel 4: el súper tipo \square.
Nota 8.8 (Cadenas de juicios entre niveles)

En λω\lambda\underline{\omega} los juicios pueden incluir múltiples niveles. Por ejemplo, t:σ::t : \sigma : * : \square 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

λα:.αα  :    :  .\lambda\alpha{:}*.\, \alpha \rightarrow \alpha \;:\; * \rightarrow * \;:\; \square.
Ejemplo 8.9 (Kinds y constructores)

Ejemplos de kinds:

,,,(),(),().\begin{aligned} & *, \quad * \rightarrow *, \quad * \rightarrow * \rightarrow *, \quad (* \rightarrow *) \rightarrow *, \\ & (* \rightarrow *) \rightarrow * \rightarrow *, \quad * \rightarrow (* \rightarrow *) \rightarrow *. \end{aligned}

Un constructor como λα:.αα\lambda \alpha{:}*.\, \alpha \rightarrow \alpha tiene kind * \rightarrow *, mientras que αα\alpha \rightarrow \alpha tiene kind *.

Reglas fundamentales de derivación

Definición 8.10 (Regla de sort)
(sort):.\text{(sort)} \quad \varnothing \vdash * : \square.

El juicio establece que * pertenece a \square, el nivel superior de clasificación.

Definición 8.11 (Regla de variable)
(variable)ΓA:sΓ, x:Ax:Asi xΓ.\text{(variable)} \quad \frac{\Gamma \vdash A : s}{\Gamma,\ x : A \vdash x : A} \quad \text{si } x \notin \Gamma.

Aquí ss representa un sort (s{,}s \in \{*, \square\}). Esta regla permite extender el contexto con una declaración x:Ax : A, siempre que AA sea derivable en el contexto original.

Nota 8.12 (Los dos roles de la regla de variable)

La regla de variable tiene dos roles fundamentales, dependiendo del sort al que pertenece AA:

  • Introducir variables de término (cuando ss \equiv *): si ya se ha establecido que AA es un tipo (es decir, ΓA:\Gamma \vdash A : *), la regla permite añadir una nueva variable de término xx de ese tipo. Por ejemplo, si se tiene ΓNat:\Gamma \vdash \texttt{Nat} : *, se puede extender el contexto a Γ, n:Nat\Gamma,\ n : \texttt{Nat}.
  • Introducir variables de tipo (cuando ss \equiv \square): si se ha establecido que AA es un kind (es decir, ΓA:\Gamma \vdash A : \square), la regla permite añadir una nueva variable de tipo xx de ese kind. El caso más importante es usar el juicio :\vdash * : \square (de la regla de sort) para extender el contexto con una variable de tipo genérica, como en α:\alpha : *.

Recordatorio. Se usan letras romanas x,y,zx, y, z para variables de término (reservando n,mn, m cuando el tipo es Nat\texttt{Nat}) y letras griegas α,β,γ\alpha, \beta, \gamma para variables de tipo (con :* : \square). Asimismo, A,BA, B denotan tipos o constructores. La restricción xΓx \notin \Gamma simplemente asegura que todas las variables declaradas en el contexto sean únicas.

Ejemplo 8.13 (Primera derivación con sort y variable)

Demostrar α:, x:αx:α\alpha{:}*,\ x{:}\alpha \vdash x : \alpha.

Demostración.

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 :* : \square (sort)
α:\alpha : * introducción de α
2 α:\alpha : * (variable) en 1
x:αx : \alpha introducción de x
3 x:αx : \alpha (variable) en 2

La regla de variable, por sí sola, no permite derivar juicios como

α:, x:αα:.\alpha{:}*,\ x{:}\alpha \vdash \alpha : *.

Este juicio es intuitivamente válido: si se sabe que α\alpha es un tipo en un contexto, debería seguir siéndolo si se añaden más suposiciones.

Definición 8.14 (Regla de debilitamiento)
(debilitamiento)ΓA:BΓC:sΓ, x:CA:Bsi xΓ.\text{(debilitamiento)} \quad \frac{\Gamma \vdash A : B \quad \Gamma \vdash C : s}{\Gamma,\ x : C \vdash A : B} \quad \text{si } x \notin \Gamma.

Esta regla permite extender el contexto Γ\Gamma con una nueva declaración x:Cx : C al final, siempre que CC sea derivable en Γ\Gamma y tenga un sort bien formado (s{,}s \in \{*, \square\}). Esencialmente, establece que añadir suposiciones válidas no invalida conclusiones anteriores. Con ella se derivan, por ejemplo, los juicios α:, x:αα:\alpha{:}*,\ x{:}\alpha \vdash \alpha : *,  α:, β:α:\ \alpha{:}*,\ \beta{:}* \vdash \alpha : * y α::\alpha{:}* \vdash * : \square.

Definición 8.16 (Regla de formación)
(formacioˊn)ΓA:sΓB:sΓAB:s.\text{(formación)} \quad \frac{\Gamma \vdash A : s \quad \Gamma \vdash B : s}{\Gamma \vdash A \rightarrow B : s}.
Nota 8.17 (La formación opera en dos niveles)

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 ss:

  • Formación de tipos (cuando ss \equiv *): si AA y BB son tipos (tienen kind *), la regla permite construir un nuevo tipo función ABA \rightarrow B, que también tendrá kind *. Es el uso más común, para crear tipos como NatBool\texttt{Nat} \rightarrow \texttt{Bool}.
  • Formación de kinds (cuando ss \equiv \square): si AA y BB son kinds (tienen sort \square), la regla permite formar un nuevo kind funcional ABA \rightarrow B. Por ejemplo, a partir de * y *, que pertenecen a \square, se deriva que el kind * \rightarrow * también pertenece a \square.

Es importante notar que en λω\lambda\underline{\omega} no existen los tipos Π\Pi de λ2\lambda 2, ya que los términos no dependen de tipos.

Definición 8.18 (Reglas de aplicación y abstracción en λω̲)

(aplicación) Permite aplicar una función MM a un argumento NN:

ΓM:ABΓN:AΓMN:B.\frac{\Gamma \vdash M : A \rightarrow B \quad \Gamma \vdash N : A}{\Gamma \vdash M\, N : B}.

(abstracción) Permite construir una función λx:A.M\lambda x{:}A.\, M:

Γ, x:AM:BΓAB:sΓλx:A.M:AB.\frac{\Gamma,\ x : A \vdash M : B \quad \Gamma \vdash A \rightarrow B : s}{\Gamma \vdash \lambda x{:}A.\, M : A \rightarrow B}.

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.

Nota 8.19 (Doble rol y β-reducción)

Ambas reglas tienen un doble rol, ya que el sort ss puede ser * o \square:

  • Cuando ss \equiv *, el tipo ABA \rightarrow B es un tipo de nivel 2 (como (αβ)γ(\alpha \rightarrow \beta) \rightarrow \gamma) y las reglas operan sobre términos para producir términos.
  • Cuando ss \equiv \square, el tipo ABA \rightarrow B es un kind de nivel 3 (como ()(* \rightarrow *) \rightarrow *) y las reglas operan sobre constructores para producir nuevos constructores.

La reducción β\beta 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:

(λα:.αα)ββββ.(\lambda \alpha{:}*.\, \alpha \rightarrow \alpha)\, \beta \quad \rightarrow_\beta \quad \beta \rightarrow \beta.

Una derivación trabajada

Las derivaciones en λω\lambda\underline{\omega} 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).

Ejemplo 8.20 (Derivación abreviada)

Se deriva, en el contexto β:\beta : *, el juicio (λα:.αα)β:(\lambda \alpha{:}*.\, \alpha \rightarrow \alpha)\, \beta : *. La derivación formal completa, en notación de banderas, es:

(a) β:\beta : *
1 :* : \square (debilitamiento) de (sort)
(b) α:\alpha : *
2 α:\alpha : * (variable) de 1
3 αα:\alpha \rightarrow \alpha : * (formación) de 2, 2
4 :* \rightarrow * : \square (formación) de 1, 1
5 λα:.αα  :  \lambda \alpha{:}*.\, \alpha \rightarrow \alpha \;:\; * \rightarrow * (abstracción) de 3, 4
6 β:\beta : * (variable) de (sort)
7 (λα:.αα)β  :  (\lambda \alpha{:}*.\, \alpha \rightarrow \alpha)\, \beta \;:\; * (aplicación) de 5, 6
Demostración.

A continuación se detalla cada paso de la derivación formal:

  1. Paso 1: β::\beta{:}* \vdash * : \square. Dentro de la hipótesis (a), se aplica (debilitamiento) sobre :\varnothing \vdash * : \square, de la regla (sort).
  2. Paso 2: β:, α:α:\beta{:}*,\ \alpha{:}* \vdash \alpha : *. Con el juicio del paso 1 como premisa, se introduce una nueva variable de tipo α\alpha usando (variable).
  3. Paso 3: β:, α:αα:\beta{:}*,\ \alpha{:}* \vdash \alpha \rightarrow \alpha : *. Se aplica (formación) de tipos función. La regla requiere dos premisas, ambas satisfechas por el juicio del paso 2.
  4. Paso 4: β:():\beta{:}* \vdash (* \rightarrow *) : \square. Se aplica la formación de kinds: dado que :* : \square (paso 1), se puede construir el kind funcional * \to *, que también pertenece a \square.
  5. Paso 5: β:(λα:.αα):()\beta{:}* \vdash (\lambda \alpha{:}*.\, \alpha \rightarrow \alpha) : (* \rightarrow *). Se aplica (abstracción) sobre el juicio del paso 3: se abstrae la variable de tipo α\alpha del constructor αα\alpha \to \alpha para crear un constructor de tipos.
  6. Paso 6: β:β:\beta{:}* \vdash \beta : *. Por (variable), la hipótesis (a) es un juicio válido en su propio contexto.
  7. Paso 7: β:((λα:.αα)β):\beta{:}* \vdash ((\lambda \alpha{:}*.\, \alpha \rightarrow \alpha)\, \beta) : *. Se aplica (aplicación): se toma el constructor de tipos del paso 5 y se le aplica el tipo β\beta 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) β:\beta : *
(b) α:\alpha : *
1 αα:\alpha \rightarrow \alpha : * (formación) de (b), (b)
2 λα:.αα  :  \lambda \alpha{:}*.\, \alpha \rightarrow \alpha \;:\; * \rightarrow * (abstracción) de 1
3 (λα:.αα)β  :  (\lambda \alpha{:}*.\, \alpha \rightarrow \alpha)\, \beta \;:\; * (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

Definición 8.21 (Regla de conversión)
(conversioˊn)ΓA:BΓB:sΓA:Bsi B=βB.\text{(conversión)} \quad \frac{\Gamma \vdash A : B \quad \Gamma \vdash B' : s}{\Gamma \vdash A : B'} \quad \text{si } B =_\beta B'.

La segunda premisa garantiza que BB' sea un tipo o kind bien formado; esto no se deduce automáticamente de la equivalencia de la conversión β\beta entre BB y BB'.

Ejemplo 8.22 (Aplicación de la regla (conversión))

Dado el contexto Γβ:, x:(λα:.αα)β\Gamma \equiv \beta{:}*,\ x : (\lambda \alpha{:}*.\, \alpha \rightarrow \alpha)\, \beta, se quiere mostrar que xx tiene el tipo ββ\beta \rightarrow \beta:

Γx:(λα:.αα)β(var)Γββ:Γx:ββ(conversioˊn).\frac{\Gamma \vdash x : (\lambda \alpha{:}*.\, \alpha \rightarrow \alpha)\, \beta \quad \text{(var)} \qquad \Gamma \vdash \beta \rightarrow \beta : *}{\Gamma \vdash x : \beta \rightarrow \beta} \quad \text{(conversión)}.

En formato de banderas, la derivación abreviada sería:

(a) x:(λα:.αα)βx : (\lambda \alpha{:}*.\, \alpha \rightarrow \alpha)\, \beta
19 x:(λα:.αα)βx : (\lambda \alpha{:}*.\, \alpha \rightarrow \alpha)\, \beta (variable)
20 x:ββx : \beta \rightarrow \beta (conversión) sobre 19, ya que (λα:*. α → α) β =β β → β

Ahora bien, en Lean se pueden definir constructores de tipos propios, como el ejemplo teórico T:=λα:.(αα)T := \lambda \alpha{:}*.\, (\alpha \rightarrow \alpha), y verificar su tipo:

-- Definimos un constructor que toma un tipo T y devuelve el tipo T -> T
def mapeo_interno (T : Type) : Type := T -> T
-- Verificamos su "kind"
#check mapeo_interno -- Lean responde: mapeo_interno : Type -> Type

La 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 -> Nat

Resumen del sistema de reglas de λω̲

Las reglas de derivación en λω\lambda\underline{\omega} generalizan las de sistemas anteriores para operar en todos los niveles de la jerarquía.

ReglaDerivación
(sort):\varnothing \vdash * : \square
(variable)ΓA:sΓ, x:Ax:A(si xΓ)\dfrac{\Gamma \vdash A : s}{\Gamma,\ x : A \vdash x : A} \quad (\text{si } x \notin \Gamma)
(debilitamiento)ΓA:BΓC:sΓ, x:CA:B(si xΓ)\dfrac{\Gamma \vdash A : B \quad \Gamma \vdash C : s}{\Gamma,\ x : C \vdash A : B} \quad (\text{si } x \notin \Gamma)
(formación)ΓA:sΓB:sΓAB:s\dfrac{\Gamma \vdash A : s \quad \Gamma \vdash B : s}{\Gamma \vdash A \rightarrow B : s}
(aplicación)ΓM:ABΓN:AΓMN:B\dfrac{\Gamma \vdash M : A \rightarrow B \quad \Gamma \vdash N : A}{\Gamma \vdash M\, N : B}
(abstracción)Γ, x:AM:BΓAB:sΓλx:A.M:AB\dfrac{\Gamma,\ x : A \vdash M : B \quad \Gamma \vdash A \rightarrow B : s}{\Gamma \vdash \lambda x{:}A.\, M : A \rightarrow B}
(conversión)ΓA:BΓB:sΓA:B(si B=βB)\dfrac{\Gamma \vdash A : B \quad \Gamma \vdash B' : s}{\Gamma \vdash A : B'} \quad (\text{si } B =_\beta B')

Esta tabla resume el sistema λω\lambda\underline{\omega}. Como se puede observar, las reglas de abstracción y formación utilizan exclusivamente el conector no dependiente \rightarrow, lo que distingue a λω\lambda\underline{\omega} de otros sistemas más expresivos del cubo lambda.

Con el Sistema λω\lambda\underline{\omega} se añade la posibilidad de crear operadores que manipulan y construyen tipos. Combinado con λ2\lambda 2, da un sistema, λω\lambda\omega (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.