Teoría de tipo dependiente y funciones de tipo 'arbitrarias'
Mi primera respuesta a esta pregunta fue alta en conceptos y baja en detalles y se reflejó en la pregunta previa, "¿qué está pasando?"; esta respuesta será la misma pero centrada en la pregunta secundaria, "¿podemos obtener funciones de tipo arbitrarias?".
Una extensión de las operaciones algebraicas de suma y producto son los llamados 'operadores grandes', que representan la suma y el producto de una secuencia (o más generalmente, la suma y el producto de una función sobre un dominio) generalmente escritos Σ
y Π
respectivamente. Ver notación Sigma .
Entonces la suma
a₀ + a₁X + a₂X² + ...
podría estar escrito
Σ[i ∈ ℕ]aᵢXⁱ
donde a
hay alguna secuencia de números reales, por ejemplo. El producto se representaría de manera similar con en Π
lugar de Σ
.
Cuando miras desde la distancia, este tipo de expresión se parece mucho a una función 'arbitraria' en X
; Por supuesto, estamos limitados a series expresables y sus funciones analíticas asociadas. ¿Es este un candidato para una representación en una teoría de tipos? ¡Seguro!
La clase de teorías de tipos que tienen representaciones inmediatas de estas expresiones es la clase de teorías de tipos 'dependientes': teorías con tipos dependientes. Naturalmente, tenemos términos que dependen de términos, y en lenguajes como Haskell con funciones de tipo y cuantificación de tipo, términos y tipos dependiendo de los tipos. En un entorno dependiente, también tenemos tipos según los términos. Haskell no es un lenguaje de tipo dependiente, aunque muchas características de los tipos dependientes se pueden simular torturando un poco el idioma .
Curry-Howard y tipos dependientes
El 'isomorfismo de Curry-Howard' comenzó como una observación de que los términos y las reglas de cálculo de tipo del cálculo lambda de tipo simple corresponden exactamente a la deducción natural (según lo formulado por Gentzen) aplicada a la lógica proposicional intuitiva, con tipos que reemplazan las proposiciones. y términos que reemplazan a las pruebas, a pesar de que los dos se inventaron / descubrieron de forma independiente. Desde entonces, ha sido una gran fuente de inspiración para los teóricos de tipos. Una de las cosas más obvias a considerar es si, y cómo, esta correspondencia para la lógica proposicional puede extenderse a lógicas predicadas o de orden superior. Las teorías de tipo dependiente surgieron inicialmente de esta vía de exploración.
Para obtener una introducción al isomorfismo de Curry-Howard para el cálculo lambda de tipo simple, consulte aquí . Como ejemplo, si queremos probar A ∧ B
debemos probar A
y probar B
; Una prueba combinada es simplemente un par de pruebas: una para cada conjunto.
En deducción natural:
Γ ⊢ A Γ ⊢ B
Γ ⊢ A ∧ B
y en cálculo lambda de tipo simple:
Γ ⊢ a : A Γ ⊢ b : B
Γ ⊢ (a, b) : A × B
Existen correspondencias similares para los ∨
tipos de suma, los →
tipos de función y las diversas reglas de eliminación.
Una proposición no demostrable (intuitivamente falsa) corresponde a un tipo deshabitado.
Con la analogía de los tipos como proposiciones lógicas en mente, podemos comenzar a considerar cómo modelar predicados en el mundo tipográfico. Hay muchas formas en que esto se ha formalizado (consulte esta introducción a la teoría de tipo intuitiva de Martin-Löf para un estándar ampliamente utilizado) pero el enfoque abstracto generalmente observa que un predicado es como una proposición con variables de término libre, o, alternativamente, una función que toma términos para proposiciones. Si permitimos que las expresiones de tipo contengan términos, ¡un tratamiento en estilo de cálculo lambda se presenta inmediatamente como una posibilidad!
Considerando solo las pruebas constructivas, ¿de qué constituye una prueba ∀x ∈ X.P(x)
? Podemos considerarlo como una función de prueba, tomando términos ( x
) a pruebas de sus proposiciones correspondientes ( P(x)
). Así que los miembros (pruebas) del tipo (proposición) ∀x : X.P(x)
son funciones dependientes '', que para cada x
en X
dar un término de tipo P(x)
.
¿Qué hay de ∃x ∈ X.P(x)
? Tenemos que cualquier miembro de X
, x
junto con una prueba de P(x)
. Por lo tanto, los miembros (pruebas) del tipo (proposición) ∃x : X.P(x)
son 'pares dependientes': un término distinguido x
en X
, junto con un término de tipo P(x)
.
Notación: usaré
∀x ∈ X...
para declaraciones reales sobre miembros de la clase X
, y
∀x : X...
para expresiones de tipo correspondientes a la cuantificación universal sobre tipo X
. Del mismo modo para ∃
.
Consideraciones combinatorias: productos y sumas
Además de la correspondencia Curry-Howard de tipos con proposiciones, tenemos la correspondencia combinatoria de tipos algebraicos con números y funciones, que es el punto principal de esta pregunta. Afortunadamente, esto se puede extender a los tipos dependientes descritos anteriormente.
Usaré la notación de módulo
|A|
para representar el "tamaño" de un tipo A
, para hacer explícita la correspondencia descrita en la pregunta, entre tipos y números. Tenga en cuenta que este es un concepto fuera de la teoría; No afirmo que sea necesario que exista un operador de este tipo dentro del idioma.
Cuentemos los posibles miembros de tipo (totalmente reducidos, canónicos)
∀x : X.P(x)
cuál es el tipo de funciones dependientes que toman términos x
de tipo X
a términos de tipo P(x)
. Cada una de esas funciones debe tener una salida para cada término de X
, y esta salida debe ser de un tipo particular. Para cada uno x
de X
, a continuación, esto da |P(x)|
'opciones' de salida.
El remate es
|∀x : X.P(x)| = Π[x : X]|P(x)|
lo cual, por supuesto, no tiene mucho sentido si lo X
es IO ()
, pero es aplicable a los tipos algebraicos.
Del mismo modo, un término de tipo
∃x : X.P(x)
es el tipo de pares (x, p)
con p : P(x)
, así que dado cualquiera x
de ellos X
podemos construir un par apropiado con cualquier miembro de P(x)
, dando |P(x)|
'opciones'.
Por lo tanto,
|∃x : X.P(x)| = Σ[x : X]|P(x)|
con las mismas advertencias.
Esto justifica la notación común para los tipos dependientes en las teorías que usan los símbolos Π
y Σ
, de hecho, muchas teorías desdibujan la distinción entre 'para todos' y 'producto' y entre 'hay' y 'suma', debido a las correspondencias mencionadas anteriormente.
Nos estamos acercando!
Vectores: representando tuplas dependientes
¿Podemos ahora codificar expresiones numéricas como
Σ[n ∈ ℕ]Xⁿ
como expresiones de tipo?
No exactamente. Si bien podemos considerar informalmente el significado de expresiones como Xⁿ
en Haskell, donde X
es un tipo y n
un número natural, es un abuso de notación; esto es una expresión de tipo que contiene un número: claramente no una expresión válida.
Por otro lado, con tipos dependientes en la imagen, los tipos que contienen números son precisamente el punto; de hecho, las tuplas dependientes o 'vectores' son un ejemplo muy citado de cómo los tipos dependientes pueden proporcionar seguridad pragmática a nivel de tipo para operaciones como el acceso a listas . Un vector es solo una lista junto con información de nivel de tipo con respecto a su longitud: precisamente lo que buscamos para expresiones de tipo como Xⁿ
.
Mientras dure esta respuesta, dejemos
Vec X n
ser el tipo de longitud- n
vectores de X
valores de tipo.
Técnicamente n
aquí hay, en lugar de un número natural real , una representación en el sistema de un número natural. Podemos representar números naturales ( Nat
) en el estilo de Peano como cero ( 0
) o el sucesor ( S
) de otro número natural, y para que n ∈ ℕ
yo escriba ˻n˼
signifique el término en el Nat
que representa n
. Por ejemplo, ˻3˼
es S (S (S 0))
.
Entonces nosotros tenemos
|Vec X ˻n˼| = |X|ⁿ
para cualquier n ∈ ℕ
.
Tipos nat: promoviendo ℕ términos a tipos
Ahora podemos codificar expresiones como
Σ[n ∈ ℕ]Xⁿ
como tipos Esta expresión particular daría lugar a un tipo que, por supuesto, es isomorfo al tipo de listas de X
, como se identifica en la pregunta. (No solo eso, sino desde un punto de vista teórico de categoría, la función de tipo, que es un functor, que lleva X
al tipo anterior es naturalmente isomorfa para el functor de Lista).
Una pieza final del rompecabezas para las funciones 'arbitrarias' es cómo codificar, para
f : ℕ → ℕ
expresiones como
Σ[n ∈ ℕ]f(n)Xⁿ
para que podamos aplicar coeficientes arbitrarios a una serie de potencias.
Ya entendemos la correspondencia de los tipos algebraicos con los números, lo que nos permite mapear de tipos a números y funciones de tipo a funciones numéricas. ¡También podemos ir hacia otro lado! - tomando un número natural, obviamente hay un tipo algebraico definible con tantos miembros de término, tengamos o no tipos dependientes. Podemos probar esto fácilmente fuera de la teoría de tipos por inducción. Lo que necesitamos es una forma de mapear de números naturales a tipos, dentro del sistema.
Una comprensión agradable es que, una vez que tenemos tipos dependientes, la prueba por inducción y la construcción por recursividad se vuelven íntimamente similares; de hecho, son lo mismo en muchas teorías. Como podemos probar por inducción que existen tipos que satisfacen nuestras necesidades, ¿no deberíamos ser capaces de construirlos?
Hay varias formas de representar tipos a nivel de término. Usaré aquí una notación imaginaria de Haskellish *
para el universo de tipos, generalmente considerada un tipo en un entorno dependiente. 1
Del mismo modo, también hay al menos tantas formas de anotar la " ℕ
eliminación" como las teorías de tipos dependientes. Usaré una notación de coincidencia de patrones Haskellish.
Necesitamos un mapeo, α
desde Nat
hasta *
, con la propiedad
∀n ∈ ℕ.|α ˻n˼| = n.
La siguiente pseudodefinición es suficiente.
data Zero -- empty type
data Successor a = Z | Suc a -- Successor ≅ Maybe
α : Nat -> *
α 0 = Zero
α (S n) = Successor (α n)
Entonces vemos que la acción de α
refleja el comportamiento del sucesor S
, convirtiéndolo en una especie de homomorfismo. Successor
es una función de tipo que 'agrega uno' al número de miembros de un tipo; es decir, |Successor a| = 1 + |a|
para cualquiera a
con un tamaño definido.
Por ejemplo α ˻4˼
(que es α (S (S (S (S 0))))
), es
Successor (Successor (Successor (Successor Zero)))
y los términos de este tipo son
Z
Suc Z
Suc (Suc Z)
Suc (Suc (Suc Z))
que nos da exactamente cuatro elementos: |α ˻4˼| = 4
.
Asimismo, para cualquiera n ∈ ℕ
, tenemos
|α ˻n˼| = n
según sea necesario.
- Muchas teorías requieren que los miembros
*
sean meros representantes de tipos, y una operación se proporciona como un mapeo explícito de términos de tipo *
a sus tipos asociados. Otras teorías permiten que los tipos literales sean entidades de nivel de término.
¿Funciones 'arbitrarias'?
¡Ahora tenemos el aparato para expresar una serie de potencias totalmente generales como un tipo!
Las series
Σ[n ∈ ℕ]f(n)Xⁿ
se convierte en el tipo
∃n : Nat.α (˻f˼ n) × (Vec X n)
donde ˻f˼ : Nat → Nat
hay alguna representación adecuada dentro del lenguaje de la función f
. Podemos ver esto de la siguiente manera.
|∃n : Nat.α (˻f˼ n) × (Vec X n)|
= Σ[n : Nat]|α (˻f˼ n) × (Vec X n)| (property of ∃ types)
= Σ[n ∈ ℕ]|α (˻f˼ ˻n˼) × (Vec X ˻n˼)| (switching Nat for ℕ)
= Σ[n ∈ ℕ]|α ˻f(n)˼ × (Vec X ˻n˼)| (applying ˻f˼ to ˻n˼)
= Σ[n ∈ ℕ]|α ˻f(n)˼||Vec X ˻n˼| (splitting product)
= Σ[n ∈ ℕ]f(n)|X|ⁿ (properties of α and Vec)
¿Qué tan 'arbitrario' es esto? Estamos limitados no solo a los coeficientes enteros por este método, sino a los números naturales. Aparte de eso, f
puede ser cualquier cosa, dado un lenguaje Turing Complete con tipos dependientes, podemos representar cualquier función analítica con coeficientes numéricos naturales.
No he investigado la interacción de esto con, por ejemplo, el caso provisto en la pregunta de List X ≅ 1/(1 - X)
o qué posible sentido podrían tener estos 'tipos' negativos y no enteros en este contexto.
Esperemos que esta respuesta sirva para explorar hasta dónde podemos llegar con las funciones de tipo arbitrario.