¿Cómo se debe leer la notación de los sistemas de tipos?
(langdev.stackexchange.com)- Aunque la notación de los sistemas de tipos varía según el material, si se entiende el marco común de gramática, relaciones de tipado y reglas de inferencia, se puede seguir la mayoría de las variantes
- Un sistema de tipos opera sobre la sintaxis abstracta de un lenguaje, por lo que primero hay que distinguir gramaticalmente entre los términos (terms) que tienen tipo y los tipos en sí
⊢ e: τes un juicio de tipado que significa “la expresiónetiene el tipoτ”, y se lee como una regla de inferencia donde, si todas las condiciones sobre la línea horizontal son verdaderas, la conclusión debajo también lo es- Cuando aparecen variables y funciones, se agrega un contexto, como en
Γ ⊢ e: τ, para rastrear los nombres de variables y sus tipos en el scope actual - Muchas reglas de tipado pueden leerse como una función recursiva de comprobación de tipos, pero no todo juicio lógico se convierte directamente en un algoritmo de comprobación de tipos decidible
Notación de sistemas de tipos a partir de la gramática
- Un sistema de tipos es un sistema sintáctico de un lenguaje de programación y consiste en un conjunto de reglas que operan sobre la sintaxis abstracta del lenguaje
- Una explicación completa de un sistema de tipos normalmente presenta primero las estructuras sintácticas que trata mediante una gramática, y las expresa con notación BNF
- Incluso en el lenguaje tipado más simple, la sintaxis se divide a grandes rasgos en dos categorías
e: expresiones (expressions) que tienen tipoτ: tipos (types) que se asignan a las expresiones
- El lenguaje de ejemplo tiene como expresiones literales booleanos, literales enteros, expresiones condicionales, operaciones aritméticas y operaciones de comparación, y usa
BooleIntcomo tipos - Según el material, el símbolo de tipo puede escribirse como
t,T,σu otras letras griegas minúsculas en lugar deτ, pero la estructura general es similar - Los lenguajes más complejos pueden incluir más categorías sintácticas, como sentencias o patrones de pattern matching
Leer relaciones y juicios de tipado
- Después de fijar la gramática, normalmente se define una relación de tipado de la forma
e : τ1 + 2 : Intsignifica “1 + 2es de tipoInt”1 + 2 : Boolsignifica que la misma expresión es de tipoBool, por lo que no es correctotrue + 2 : Intno tiene sentido como expresión, así que no tiene ningún tipo
⊢ e : τes un juicio de tipado, y⊢puede leerse como “la afirmación que sigue es verdadera”- Una regla sin nada sobre la línea horizontal es un axioma (axiom), siempre verdadero
⊢ true : Bool⊢ false : Bool- Reglas para literales enteros como
⊢ 0 : Int,⊢ 1 : Int,⊢ -1 : Int
- Una regla con contenido tanto arriba como abajo de la línea horizontal es una regla de inferencia
- Si todas las condiciones superiores son verdaderas, la conclusión inferior es verdadera
- Si
e₁ye₂son ambosInt, entoncese₁ + e₂esInt - Si
e₁ye₂son ambosInt, entoncese₁ < e₂esBool
Expresiones condicionales y variables de tipo
- Las dos ramas de
if ... then ... else ...pueden ser de cualquier tipo, pero deben tener el mismo tipo entre síif true then 1 else 2es válidoif true then false else truees válidoif true then 1 else trueno es válido
- Para expresar esto, la regla usa la variable
τpara representar el tipo de las ramas- La expresión condicional
e₁debe serBool - La rama
thene₂y la ramaelsee₃deben tener el mismo tipoτ - El tipo de toda la expresión condicional también pasa a ser
τ
- La expresión condicional
- Al aplicar la regla, se puede elegir cualquier tipo como
τ, pero dentro de la misma regla esa elección debe mantenerse de forma consistente
Leer las reglas de inferencia como algoritmos
- Esta notación proviene de la lógica formal, y la forma de especificar sistemas de tipos se parece especialmente a la deducción natural
- Estas reglas se usan para construir pruebas formales sobre propiedades del sistema, y son importantes para demostrar propiedades como la seguridad de tipos
- Un juicio lógico no siempre corresponde directamente a un algoritmo de comprobación de tipos decidible
- En muchos casos,
⊢ e : τpuede leerse como una función que obtiene el tipoτa partir de la expresióne- Para cada forma de expresión de la gramática normalmente hay una regla
- Cada regla de tipado puede verse como una rama de una función recursiva de comprobación de tipos
- La función
inferdel ejemplo corresponde al siguiente flujotrueofalseesBool- Un literal entero es
Int e₁ + e₂, después de comprobar que los resultados de inferir ambos lados sonInt, esInte₁ < e₂, después de comprobar que ambos lados sonInt, esBoolif e₁ then e₂ else e₃comprueba que la condición seaBool, comprueba que los tipos de las dos ramas sean iguales y luego devuelve ese tipo
- Aunque no pueda trasladarse directamente a un algoritmo, si se piensa en
ecomo entrada y enτcomo salida dentro del juicio, el flujo de información resulta más fácil de entender
Variables y contexto
- Para tratar un lenguaje de programación útil se necesitan variables, y el ejemplo se amplía agregando funciones hasta tomar la forma del cálculo lambda simplemente tipado
- La gramática extendida incluye lo siguiente
- Variable
x - Abstracción de función
λx:τ. e - Aplicación de función
e e - Tipo de función
τ → τ
- Variable
λx:τ. ecorresponde a(x:τ) => een TypeScript, yf xcorresponde af(x)- El tipo de una variable depende del contexto en el que aparece, así que no se puede escribir una regla con una forma simple como
⊢ x : ??? - Por eso el juicio de tipado se extiende a
Γ ⊢ e : τΓes el contexto o entorno de tipos⊢separa las suposiciones del contexto a la izquierda de la afirmación que se probará a la derecha- Se lee como “bajo el contexto
Γ, la expresiónetiene el tipoτ”
- Algorítmicamente,
Γpuede verse como una entrada adicional con formaMap<Variable, Type> - Formalmente, el contexto también se especifica como una estructura sintáctica
∅: contexto vacíoΓ, x:τ: contexto con un binding de variable agregado- A veces se usa
•como contexto vacío en lugar de∅
- En esta representación, el contexto se parece a una lista de asociaciones que mapea nombres de variables a tipos
Qué hace el contexto dentro de las reglas
- Muchas reglas de tipado pasan el contexto sin modificarlo
Γ ⊢ true : Bool- Si
Γ ⊢ e₁ : IntyΓ ⊢ e₂ : Int, entoncesΓ ⊢ e₁ + e₂ : Int
- En las reglas de uso de variables y expresiones lambda, el contexto cumple un papel central
- Si
x:τ ∈ Γ, entoncesΓ ⊢ x : τ - Si
Γ, x:τ₁ ⊢ e : τ₂, entoncesΓ ⊢ (λx:τ₁. e) : τ₁ → τ₂
- Si
- Al comprobar el tipo del cuerpo
ede una expresión lambda, el contexto se extiende con el nuevo bindingx:τ₁ - La regla de variables juzga que, si existe un binding de la variable en el contexto actual, esa variable tiene ese tipo
- El contexto se usa como un mecanismo de comunicación que transmite información entre la regla de expresiones lambda y la regla de variables
- Para simplificar, las especificaciones de sistemas de tipos de este estilo suelen asumir que todas las variables ya fueron resueltas y hechas únicas, y no tratan el shadowing de variables
- La regla de aplicación de funciones comprueba conjuntamente los tipos de la expresión función y de la expresión argumento
e₁debe tener el tipoτ₁ → τ₂e₂debe tener el tipoτ₁- El tipo de la aplicación completa
e₁ e₂pasa a serτ₂
Notación adicional frecuente
- Las reglas de inferencia no siempre se escriben solo en vertical
- Varias condiciones pueden colocarse horizontalmente, una junto a otra
- La disposición vertical y la horizontal pueden mezclarse dentro de la misma regla
- Las condiciones sobre la línea horizontal normalmente son otros juicios, pero también pueden aparecer condiciones laterales (side conditions), es decir, condiciones booleanas arbitrarias
x:τ ∈ Γen la regla de variables es un ejemplo- En sistemas de tipos algorítmicos puede usarse
α fresh, lo que significa queαdebe ser una variable de tipo nueva, distinta de las demás variables de tipo
Subtipado
- El subtipado es una relación que trata la compatibilidad entre tipos de forma más flexible que la igualdad estricta, y debe definirse explícitamente
- Normalmente se escribe
τ₁ <: τ₂y se lee “τ₁es subtipo deτ₂” - Una relación de subtipado simple puede introducir el tipo superior
⊤y el tipo inferior⊥τ <: τ: todo tipo es subtipo de sí mismoτ <: ⊤: todo tipo es subtipo de⊤⊥ <: τ:⊥es subtipo de todo tipo
- La primera regla es la regla reflexiva, y a menudo se abrevia como
refl - Para permitir subtipado, la relación debe usarse explícitamente en cada regla de tipado que lo admita
- En la regla de aplicación de funciones, si el tipo del argumento
τ₁es subtipo del tipo del parámetroτ₂, se puede permitir la aplicación
- En la regla de aplicación de funciones, si el tipo del argumento
Múltiples contextos y comprobación bidireccional de tipos
- Algunos sistemas de tipos definen juicios de tipado que incluyen más de un contexto
- El segundo contexto normalmente se llama
Δ Γ;Δ ⊢ e : τse usa con frecuencia cuando ambos contextos funcionan como entradasΓ ⊢ e : τ ⊣ Δse usa con frecuencia cuandoΔfunciona como salida
- El segundo contexto normalmente se llama
- El segundo contexto se usa de distintas maneras según el propósito
- Puede hacer que ciertas variables solo puedan referenciarse dentro de una determinada expresión
- En lenguajes de programación conscientes de recursos, puede usarse como contexto de salida para rastrear qué variables se consumieron
- La comprobación bidireccional de tipos es un enfoque que realiza inferencia de tipos no local limitada sin un resolvedor de restricciones
- Un sistema bidireccional divide el juicio general
Γ ⊢ e : τen dos juicios especializadosΓ ⊢ e ⇐ τ: juicio de comprobación (checking) que verifica si la expresiónetiene el tipo esperadoτ; algorítmicamente,τes una entradaΓ ⊢ e ⇒ τ: juicio de inferencia (inference) usado cuando no hay información de tipo esperada; algorítmicamente,τes una salida
- Los dos juicios se definen de manera mutuamente recursiva para transmitir información de tipos en ambas direcciones
- Con este método se pueden omitir algunas anotaciones de tipo, y la regla de comprobación de una abstracción lambda puede obtener el tipo del parámetro a partir del tipo de función esperado, por lo que puede omitirse la anotación del binder de variable
1 comentarios
Opiniones en Hacker News
Guy Steele dio una charla sobre este tema hace tiempo. Incluso les puso nombres buscables a algunas notaciones, como diagramas bidimensionales de reglas de inferencia.
Él lo llama metanotación de ciencias de la computación, aunque personalmente me parece más cercano a la teoría de lenguajes de programación. https://m.youtube.com/watch?v=dCuZkaaou0Q
https://en.wikipedia.org/wiki/Guy_Steele Guy Steele
https://www.codemesh.io/codemesh2017/guy-l-steele charla "A Cobbler's Child" en Code Mesh 2017
https://www.youtube.com/watch?v=qNPlDnX6Mio "A Cobbler's Child" (video en YouTube)
https://www.youtube.com/watch?v=dCuZkaaou0Q "It's Time for a New Old Language" (video en YouTube)
https://news.ycombinator.com/item?id=15473199 discusión en HN
https://labs.oracle.com/pls/apex/f?p=94065:40150:0::::P40150... diapositivas
Es raro que quienes intentan hablar con la mayor precisión sobre los oficios humanos más precisos usen una notación ambigua e inconsistente.
Esta notación se remonta hasta Frege. Es difícil buscarla si no sabes qué buscar, pero este artículo parece un resumen bastante bueno: https://plato.stanford.edu/entries/frege-logic
El símbolo de torniquete
|-ya se usaba, y la línea horizontal que en clase llamaban “Fregescher Schlussstrich”, es decir, la raya de conclusión de Frege, originalmente parece haber sido parte del propio torniquete y luego se convirtió en un elemento separado en la notación moderna.Types and Programming Languages, de Benjamin C. Pierce, es un buen libro de texto que cubre estos temas.
Aunque estudié ciencias de la computación, todavía me confunde la diferencia de significado entre
|–y|=, y en qué nivel de metasintaxis está cada una de las variables usadas.Irónicamente, una de las causas es que la notación en sí no tiene tipos explícitos.
Para quien esté dudando si leerlo: este artículo explica la notación de sistemas de tipos que aparece en papers de ciencias de la computación y, en la práctica, es una introducción a la notación BNF para sistemas de tipos, reglas de inferencia, etc.
Parece un buen resumen.
Entiendo el concepto lógico de la aplicación de tipos, pero como no leo papers de ciencias de la computación con frecuencia, no termino de fijar en la cabeza la correspondencia entre símbolos y significado.
En uno de los ejemplos,
𝗍𝗋𝗎𝖾+2:𝖨𝗇𝗍significa “𝗍𝗋𝗎𝖾+2es de tipo𝖨𝗇𝗍”, pero se dice que es aún más raro porque la expresión𝗍𝗋𝗎𝖾+2en sí no tiene sentido y tampoco tiene tipo.Sin embargo, en Python,
True + 2efectivamente es un entero y su valor es 3. Más allá de si debería ser así o no, de hecho lo es.True + 2tiene sentido, basta con definir directamente una regla de juicio que lo permita.La lógica y la teoría de sistemas de tipos no se preocupan por cuáles axiomas y reglas de inferencia uses; solo permiten razonar sobre esas reglas y sus interacciones. Por ejemplo, puedes poner
|- True : Bool,|- True : Int, o, si quieres permitirlo solo en ciertas expresiones, hacer que de|- x : Intse derive|- True + x: Int.truese mapea a 1, así quetrue+1=2.True + 2no arroje error en Python o C, sigue siendo una tontería porque hace que la semántica del lenguaje sea más difícil de razonar solo para darle al programador un poco de azúcar sintáctica.Buenísimo. Durante años tuve curiosidad por esto, pero no sabía qué términos de búsqueda usar para investigarlo más.
A veces molesta un poco cuando alguien libera gratis conocimiento arcano que a uno le costó aprender ;) Ojalá hubiera existido un artículo así cuando yo estaba aprendiendo esto. Espero que, al hacerlo más accesible, haya menos lenguajes desastrosos.
Al leer el Ada Reference Manual, reconocí de inmediato este tipo de sintaxis. No sabía cómo se llamaba, pero me pareció interesante verla en un caso de uso real, y todo el lenguaje está definido con esa notación.
Ej.: https://ada-lang.io/docs/arm/AA-3/AA-3.7#syntax
El Ada Reference Manual explicita la notación que usa. Utiliza una variante de la Backus-Naur Form, y en la sección enlazada explica esa variante específica.
Este parece un buen lugar para predicar una de esas causas que decidí defender hasta el final. En el formato de anotación de tipos que usa dos puntos, los espacios a ambos lados de los dos puntos deberían ser iguales.
Para mí, por casualidad existen dos símbolos distintos que tienen la misma forma, es decir, dos puntos. Uno es el dos puntos de etiqueta, en el que la parte anterior introduce la posterior, como en inglés, o lo de la izquierda es una etiqueta para lo de la derecha; ahí entran el inicio de bloques en Python, los pares clave-valor y los pares nombre-valor de structs en C o Rust.
El otro es una anotación de tipos tomada de las matemáticas. Es una relación binaria, y las relaciones binarias llevan el mismo espacio a izquierda y derecha. Así como no escribimos
x= 1,x> ynix+ z, lo natural no sería escribirx: X, sinox : X.Cuando veo
a: b, lo leo de inmediato como dos puntos de etiqueta, y si es una anotación de tipo, cada vez necesito una conversión mental adicional, muy pequeña. Estoy hablando de sintaxis de lenguajes de programación, y personalmente prefiero mucho másx : XqueX x.[1] “Evangelion” es una palabra genial que viene de εὐαγγέλιον, es decir, “buena noticia”. [2] https://en.wikipedia.org/wiki/Colon_(punctuation)#Usage_in_E...
f: X->Y, con más espacio a la derecha de los dos puntos; de 3 libros que revisé, 1 usaba solo esa notación.Además, eso sigue siendo más bien una forma de etiquetado: etiqueta una función de una forma específica. En matemáticas, cuando los dos puntos se usan con un significado realmente distinto es como abreviatura de
such that; por ejemplo, aparece a menudo en definiciones de conjuntos como{ x : x \in IN and x | 2}o junto con cuantificadores.X x.x: Xme resulta mucho más natural y se siente más cercano al modo en que se usan los dos puntos en el lenguaje natural.Hay una proposición y lo que viene después de los dos puntos la explica con más detalle; en ese sentido, el tipo encaja perfecto como información adicional sobre lo que está a la izquierda.
t[espacio]:[espacio]T.La teoría de tipos en general tiene aspectos de caos e inconsistencia, pero este caso es una de esas raras ocasiones en que todos son bastante consistentes. Me dio curiosidad ver cómo lo escribía yo en la licenciatura, y resulta que también lo dejé bonito y simétrico: https://dvt.name/logic/horse2.pdf
x: Xcorresponde al uso en el que “después de los dos puntos viene una explicación”.Es decir, algo como
variable x: It’s an X.age: intse puede leer fácilmente en inglés como “person’s age: an integer”.Por eso los dos puntos nunca me han molestado demasiado.