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 nat\texttt{nat}, λx:nat.x\lambda x{:}\texttt{nat}.\,x, otra para bool\texttt{bool}, λx:bool.x\lambda x{:}\texttt{bool}.\,x, 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 λ2\lambda 2 (también conocido como Cálculo Lambda Polimórfico o Sistema FF) garantiza que los términos dependan de los tipos. En resumen, el propósito de λ2\lambda 2 es extender los conceptos de abstracción y aplicación: en λ\lambda\rightarrow 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, Λα:.M\Lambda \alpha{:}*.\, M, que toma un tipo α\alpha como argumento y devuelve un término MM. Se usa la mayúscula Λ\Lambda para distinguirla visualmente de la abstracción de términos λ\lambda.
  • Aplicación de tipos: se puede tomar una de estas funciones polimórficas y aplicarla a un tipo concreto σ\sigma, escribiendo MσM\,\sigma.
Ejemplo 8.1 (La identidad genérica)

En λ2\lambda 2 se puede capturar la identidad en un único término polimórfico:

IdΛα:.λx:α.x.\mathrm{Id} \equiv \Lambda \alpha{:}*.\, \lambda x{:}\alpha.\, x.

Este término Id\mathrm{Id} es una función que primero espera un tipo (α\alpha) y, cuando lo recibe, devuelve la función identidad para ese tipo específico.

  1. nat\texttt{nat}: si se quiere la identidad para números naturales, se aplica Id\mathrm{Id} al tipo nat\texttt{nat}. La β\beta-reducción de segundo orden da el resultado esperado: Id nat(Λα:.λx:α.x) natβλx:nat.x.\mathrm{Id}\ \texttt{nat} \equiv (\Lambda \alpha{:}*.\, \lambda x{:}\alpha.\, x)\ \texttt{nat} \rightarrow_\beta \lambda x{:}\texttt{nat}.\, x. Por lo tanto, se tiene la identidad para naturales a partir del término genérico.
  2. Tipos funcionales: la generalidad de Id\mathrm{Id} va más allá. Si se necesita una identidad para funciones de tipo natbool\texttt{nat} \rightarrow \texttt{bool}, el proceso es idéntico: Id (natbool)(Λα:.λx:α.x) (natbool)βλx:(natbool).x.\mathrm{Id}\ (\texttt{nat} \rightarrow \texttt{bool}) \equiv (\Lambda \alpha{:}*.\, \lambda x{:}\alpha.\, x)\ (\texttt{nat} \rightarrow \texttt{bool}) \rightarrow_\beta \lambda x{:}(\texttt{nat} \rightarrow \texttt{bool}).\, x.

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 λx:α.x\lambda x{:}\alpha.\, x tiene tipo αα\alpha \rightarrow \alpha, una primera conjetura para el tipo de IdΛα:.λx:α.x\mathrm{Id} \equiv \Lambda \alpha{:}*.\, \lambda x{:}\alpha.\, x podría ser:

tipo tentativo: (αα),\text{tipo tentativo: } * \rightarrow (\alpha \rightarrow \alpha),

donde * representa el «tipo de todos los tipos». Pero aplicando la α\alpha-equivalencia, donde los nombres de las variables ligadas no importan, se tendría:

Λα:.λx:α.x=αΛβ:.λx:β.x.\Lambda \alpha{:}*.\, \lambda x{:}\alpha.\, x \quad =_\alpha \quad \Lambda \beta{:}*.\, \lambda x{:}\beta.\, x.

Si se usara el tipo tentativo, sus tipos serían:

Λα:.λx:α.x:(αα),Λβ:.λx:β.x:(ββ).\Lambda \alpha{:}*.\, \lambda x{:}\alpha.\, x : * \rightarrow (\alpha \rightarrow \alpha), \qquad \Lambda \beta{:}*.\, \lambda x{:}\beta.\, x : * \rightarrow (\beta \rightarrow \beta).

Dos términos idénticos tendrían tipos diferentes; por definición de la α\alpha-equivalencia, esto no es posible. La variable de tipo (α\alpha o β\beta) 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 Π\Pi. En lugar de la flecha simple se escribe:

Πα:.(αα).\Pi \alpha{:}*.\, (\alpha \rightarrow \alpha).

Este tipo representa a las funciones que, para todo tipo α\alpha, devuelven un término de tipo αα\alpha \rightarrow \alpha. La variable α\alpha está ahora ligada por el cuantificador Π\Pi, resolviendo el problema. Ahora se puede asignar un tipo único a la identidad polimórfica:

(Λα:.λx:α.x):(Πα:.αα).(\Lambda \alpha{:}*.\, \lambda x{:}\alpha.\, x) : (\Pi \alpha{:}*.\, \alpha \rightarrow \alpha).

Por la ligadura de Π\Pi, la α\alpha-equivalencia se preserva como se debe:

Πα:.(αα)=αΠβ:.(ββ).\Pi \alpha{:}*.\, (\alpha \rightarrow \alpha) \quad =_\alpha \quad \Pi \beta{:}*.\, (\beta \rightarrow \beta).

El sistema formal λ2

Con la intuición y la maquinaria de los tipos Π\Pi establecidas, se puede definir formalmente el Sistema λ2\lambda 2.

Definición 8.2 (Sintaxis de tipos y términos en λ2)

Usando notación gramatical, como en el módulo del cálculo lambda, se tiene:

  • El conjunto de tipos, T2\mathbb{T}2, se extiende para incluir los tipos Π\Pi: T2=V(T2T2)(ΠV:.T2),\mathbb{T}2 = \mathbb{V} \mid (\mathbb{T}2 \rightarrow \mathbb{T}2) \mid (\Pi\, \mathbb{V}{:}*.\, \mathbb{T}2), donde V\mathbb{V} es el conjunto de las variables de tipo: α,β,γ\alpha, \beta, \gamma, etc.
  • El conjunto de términos pre-tipados, ΛT2\Lambda_{\mathbb{T}2}, se extiende para incluir abstracción y aplicación de tipos: ΛT2=V(ΛT2ΛT2)(ΛT2T2)(λV:T2.ΛT2)(ΛV:.ΛT2).\Lambda_{\mathbb{T}2} = V \mid (\Lambda_{\mathbb{T}2}\, \Lambda_{\mathbb{T}2}) \mid (\Lambda_{\mathbb{T}2}\, \mathbb{T}2) \mid (\lambda V{:}\mathbb{T}2.\, \Lambda_{\mathbb{T}2}) \mid (\Lambda\, \mathbb{V}{:}*.\, \Lambda_{\mathbb{T}2}).
Nota 8.3 (Contextos de λ2)

Las reglas de derivación de λ2\lambda 2 son las mismas de λ\lambda\rightarrow 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 λ2\lambda 2 se amplían en consecuencia: además de declaraciones de variables de término x:Ax : A con AT2A \in \mathbb{T}2, admiten declaraciones de variables de tipo α:\alpha : *, y toda variable de tipo debe declararse antes de su primer uso.

Definición 8.4 (Reglas de derivación clave de λ2)

(abstracción2_2) Si, asumiendo que α\alpha es un tipo, se puede demostrar que el término MM tiene tipo AA, entonces se concluye que Λα:.M\Lambda \alpha{:}*.\, M tiene tipo Πα:.A\Pi \alpha{:}*.\, A:

Γ, α:M:AΓΛα:.M:Πα:.A.\frac{\Gamma,\ \alpha : * \vdash M : A}{\Gamma \vdash \Lambda \alpha{:}*.\, M : \Pi \alpha{:}*.\, A}.

(aplicación2_2) Si se tiene un término polimórfico MM de tipo Πα:.A\Pi \alpha{:}*.\, A y un tipo bien formado BB, se puede aplicar MM a BB. El tipo resultante es AA, pero con todas las ocurrencias de α\alpha reemplazadas por BB:

ΓM:(Πα:.A)ΓB:ΓMB:A[α:=B].\frac{\Gamma \vdash M : (\Pi \alpha{:}*.\, A) \qquad \Gamma \vdash B : *}{\Gamma \vdash M\, B : A[\alpha := B]}.

(formación) ΓB:\Gamma \vdash B : * si BT2B \in \mathbb{T}2 y todas las variables de tipo libres de BB están declaradas en Γ\Gamma. Esta regla es la que deriva la segunda premisa de (aplicación2_2) para tipos compuestos como αα\alpha \to \alpha o natbool\texttt{nat} \to \texttt{bool}.

Las reglas (variable), (aplicación) y (abstracción) de λ\lambda\rightarrow se mantienen sin cambios.

Ejemplo 8.5 (Derivación formal de la identidad polimórfica)

Demostrar el juicio:

(Λα.λx:α.x):(Πα:.αα).\vdash (\Lambda \alpha.\, \lambda x{:}\alpha.\, x) : (\Pi \alpha{:}*.\, \alpha \rightarrow \alpha).
Demostración.

A probar:

  1. El objetivo es derivar el cuerpo más interno de la expresión, que es la variable xx. Para esto se asume un contexto Γ1\Gamma_1 donde tanto el tipo α\alpha como la variable xx con dicho tipo están declarados. Sea Γ1={α:, x:α}\Gamma_1 = \{\alpha{:}*,\ x{:}\alpha\}. Se aplica la regla (variable), ya que la condición (x:α)(x{:}\alpha) es un miembro del contexto Γ1\Gamma_1: α:, x:αx:α(1)\alpha{:}*,\ x{:}\alpha \vdash x : \alpha \quad (1)
  2. El objetivo es demostrar α:λx:α.x:αα\alpha{:}* \vdash \lambda x{:}\alpha.\, x : \alpha \to \alpha. Usando el juicio (1) como premisa, se aplica la regla de (abstracción). La regla abstrae la variable de término xx, elimina x:αx{:}\alpha del contexto y construye un tipo flecha (\to): α:, x:αx:αpremisa, del juicio (1)α:contexto resultanteλx:αabstraccioˊn.xcuerpo:ααtipo construido(2)\frac{ \overbrace{\alpha{:}*,\ x{:}\alpha \vdash x : \alpha}^{\text{premisa, del juicio (1)}} }{ \underbrace{\alpha{:}*}_{\text{contexto resultante}} \vdash \underbrace{\lambda x{:}\alpha}_{\text{abstracción}} .\, \underbrace{x}_{\text{cuerpo}} : \underbrace{\alpha \to \alpha}_{\text{tipo construido}} } \quad (2)
  3. Para demostrar el juicio original se usa el juicio (2) como premisa y se aplica la regla de abstracción de tipo, denotada (abst2_2). Esta regla abstrae la variable de tipo α\alpha, elimina α:\alpha{:}* del contexto y construye un tipo universal (cuantificado) Π\Pi: α:λx:α.x:ααpremisa, del juicio (2)contexto finalΛα:abstraccioˊn.(λx:α.x)cuerpo:Πα:.(αα)tipo final\frac{ \overbrace{\alpha{:}* \vdash \lambda x{:}\alpha.\, x : \alpha \to \alpha}^{\text{premisa, del juicio (2)}} }{ \underbrace{\varnothing}_{\text{contexto final}} \vdash \underbrace{\Lambda \alpha{:}*}_{\text{abstracción}} .\, \underbrace{(\lambda x{:}\alpha.\, x)}_{\text{cuerpo}} : \underbrace{\Pi \alpha{:}*.\,(\alpha \to \alpha)}_{\text{tipo final}} }

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, IdΛα:.λx:α.x\mathrm{Id} \equiv \Lambda \alpha{:}*.\, \lambda x{:}\alpha.\, x, 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 Λα\Lambda \alpha.

def identidad_polimorfica {α : Type} (x : α) : α := x

Se 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 λ2\lambda 2 hereda las mismas propiedades de λ\lambda\rightarrow, 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 β\beta-reducción se extiende incluyendo la aplicación de tipos: (Λα:.M)TβM[α:=T](\Lambda \alpha{:}*.\, M)\, T \rightarrow_\beta M[\alpha := T]. 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 λ2\lambda 2 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.