¿Cómo entiendo las reglas de Hindley-Milner?
Hindley-Milner es un conjunto de reglas en forma de cálculo secuencial (no deducción natural) que demuestra que podemos deducir el tipo (más general) de un programa a partir de la construcción del programa sin declaraciones de tipo explícitas.
Los símbolos y la notación.
Primero, expliquemos los símbolos y analicemos la precedencia del operador
- 𝑥 es un identificador (informalmente, un nombre de variable).
- : significa es un tipo de (informalmente, una instancia de, o "es-a").
- 𝜎 (sigma) es una expresión que es una variable o una función.
- así 𝑥: 𝜎 se lee " 𝑥 es-a 𝜎 "
- ∈ significa "es un elemento de"
- 𝚪 (Gamma) es un entorno.
- ⊦ (el signo de aserción) significa afirma (o prueba, pero contextualmente "afirma" se lee mejor).
- 𝚪 ⊦ 𝑥 : 𝜎 se lee así "𝚪 afirma que 𝑥, es-a 𝜎 "
- 𝑒 es una instancia real (elemento) de tipo 𝜎 .
- 𝜏 (tau) es un tipo: básico, variable ( 𝛼 ), funcional 𝜏 → 𝜏 ' o producto 𝜏 × 𝜏' (el producto no se usa aquí)
- 𝜏 → 𝜏 ' es un tipo funcional donde 𝜏 y 𝜏' son tipos potencialmente diferentes.
𝜆𝑥.𝑒 significa que 𝜆 (lambda) es una función anónima que toma un argumento, 𝑥 , y devuelve una expresión, 𝑒 .
dejar 𝑥 = 𝑒₀ en 𝑒₁ significa en expresión, 𝑒₁ , sustituir 𝑒₀ donde aparezca 𝑥 .
⊑ significa que el elemento anterior es un subtipo (informalmente - subclase) del último elemento.
- 𝛼 es una variable de tipo.
- ∀ 𝛼.𝜎 es un tipo, ∀ (para todas) variables de argumento, 𝛼 , que devuelve la expresión 𝜎
- ∉ libre (𝚪) significa que no es un elemento de las variables de tipo libre de 𝚪 definidas en el contexto externo. (Las variables enlazadas son sustituibles).
Todo lo que está por encima de la línea es la premisa, todo lo que está debajo es la conclusión ( Per Martin-Löf )
Precedencia, por ejemplo
Tomé algunos de los ejemplos más complejos de las reglas e inserté paréntesis redundantes que muestran precedencia:
- 𝑥: 𝜎 ∈ 𝚪 podría escribirse (𝑥: 𝜎) ∈ 𝚪
𝚪 ⊦ 𝑥 : 𝜎 podría escribirse 𝚪 ⊦ ( 𝑥 : 𝜎 )
𝚪 ⊦ let 𝑥 = 𝑒₀ en 𝑒₁ : 𝜏
es equivalente 𝚪 ⊦ (( let ( 𝑥 = 𝑒₀ ) en 𝑒₁ ): 𝜏 )
𝚪 ⊦ 𝜆𝑥.𝑒 : 𝜏 → 𝜏 ' es equivalentemente 𝚪 ⊦ (( 𝜆𝑥.𝑒 ): ( 𝜏 → 𝜏' ))
Luego, los espacios grandes que separan las afirmaciones de afirmación y otras condiciones previas indican un conjunto de tales condiciones previas, y finalmente la línea horizontal que separa la premisa de la conclusión trae el final del orden de precedencia.
Las normas
Lo que sigue aquí son interpretaciones en inglés de las reglas, cada una seguida de una nueva declaración y una explicación.
Variable
Dado que 𝑥 es un tipo de 𝜎 (sigma), un elemento de 𝚪 (Gamma),
concluye que 𝚪 afirma que 𝑥 es un 𝜎.
Dicho de otra manera, en 𝚪, sabemos que 𝑥 es de tipo 𝜎 porque 𝑥 es de tipo 𝜎 en 𝚪.
Esto es básicamente una tautología. Un nombre identificador es una variable o una función.
Aplicación de funciones
Dado que rts afirma 𝑒₀ es un tipo funcional y 𝚪 afirma 𝑒₁ es una 𝜏
conclusión 𝚪 afirma aplicar la función 𝑒₀ a 𝑒₁ es un tipo 𝜏 '
Para reformular la regla, sabemos que la aplicación de función devuelve el tipo 𝜏 'porque la función tiene el tipo 𝜏 → 𝜏' y obtiene un argumento de tipo 𝜏.
Esto significa que si sabemos que una función devuelve un tipo y la aplicamos a un argumento, el resultado será una instancia del tipo que sabemos que devuelve.
Abstracción de funciones
Dado que 𝚪 y 𝑥 de tipo 𝜏 afirma 𝑒 es un tipo, 𝜏 '
concluye 𝚪 afirma una función anónima, 𝜆 de 𝑥 expresión de retorno, 𝑒 es de tipo 𝜏 → 𝜏'.
Nuevamente, cuando vemos una función que toma 𝑥 y devuelve una expresión 𝑒, sabemos que es de tipo 𝜏 → 𝜏 'porque 𝑥 (a 𝜏) afirma que 𝑒 es a 𝜏'.
Si sabemos que 𝑥 es de tipo 𝜏 y, por lo tanto, una expresión 𝑒 es de tipo 𝜏 ', entonces una función de 𝑥 que devuelve la expresión 𝑒 es de tipo 𝜏 → 𝜏'.
Dejar declaración variable
Dado 𝚪 afirma 𝑒₀, de tipo 𝜎, y 𝚪 y 𝑥, de tipo 𝜎, afirma 𝑒₁ de tipo 𝜏
concluye 𝚪 afirma let
𝑥 = 𝑒₀ in
𝑒₁ de tipo 𝜏
En términos generales, 𝑥 está vinculado a 𝑒₀ en 𝑒₁ (a 𝜏) porque 𝑒₀ es un 𝜎, y 𝑥 es un 𝜎 que afirma que 𝑒₁ es un 𝜏.
Esto significa que si tenemos una expresión 𝑒₀ que es un 𝜎 (que es una variable o una función), y algún nombre, 𝑥, también un 𝜎, y una expresión 𝑒₁ de tipo 𝜏, entonces podemos sustituir 𝑒₀ por 𝑥 donde sea que aparezca dentro de 𝑒₁.
Instanciación
Dado 𝚪 afirma 𝑒 de tipo 𝜎 'y 𝜎' es un subtipo de 𝜎
concluye 𝚪 afirma 𝑒 es de tipo 𝜎
Una expresión, 𝑒 es de tipo padre 𝜎 porque la expresión 𝑒 es subtipo 𝜎 ', y 𝜎 es el tipo padre de 𝜎'.
Si una instancia es de un tipo que es un subtipo de otro tipo, entonces también es una instancia de ese supertipo, el tipo más general.
Generalización
Dado que rts afirma 𝑒 es un 𝜎 y 𝛼 no es un elemento de las variables libres de 𝚪,
concluya 𝚪 afirma 𝑒, escriba para todas las expresiones de argumento 𝛼 devolviendo una expresión 𝜎
Entonces, en general, 𝑒 se escribe 𝜎 para todas las variables de argumento (𝛼) que devuelven 𝜎, porque sabemos que 𝑒 es un a y 𝛼 no es una variable libre.
Esto significa que podemos generalizar un programa para aceptar todos los tipos de argumentos que aún no están vinculados en el ámbito que lo contiene (variables que no son no locales). Estas variables ligadas son sustituibles.
Poniendolo todo junto
Dados ciertos supuestos (como no hay variables libres / indefinidas, un entorno conocido), conocemos los tipos de:
- elementos atómicos de nuestros programas (Variable),
- valores devueltos por funciones (aplicación de función),
- construcciones funcionales (abstracción de funciones),
- enlaces de let (Declaraciones de variables de Let),
- tipos primarios de instancias (instanciación) y
- todas las expresiones (generalización).
Conclusión
Estas reglas combinadas nos permiten probar el tipo más general de un programa afirmado, sin requerir anotaciones de tipo.