El sistema de tipos Hindley-Milner ( HM ) es un sistema de tipos clásico para el cálculo lambda con polimorfismo paramétrico . También se conoce como Damas-Milner o Damas-Hindley-Milner . Fue descrito por primera vez por J. Roger Hindley [ 1 ] y posteriormente redescubierto por Robin Milner [ 2 ] . Luis Damas aportó un análisis formal detallado y una demostración del método en su tesis doctoral [ 3 ] [ 4 ] .
Entre las propiedades más notables de HM se encuentran su completitud y su capacidad para inferir el tipo más general de un programa dado sin anotaciones de tipo proporcionadas por el programador u otras sugerencias. El algoritmo W es un método de inferencia de tipos eficiente en la práctica y se ha aplicado con éxito en grandes bases de código, aunque tiene una alta complejidad teórica . [ nota 1 ] HM se utiliza preferentemente para lenguajes de programación funcional . Se implementó por primera vez como parte del sistema de tipos del lenguaje de programación ML . Desde entonces, HM se ha extendido de varias maneras, sobre todo con restricciones de clase de tipo como las de Haskell .
Introducción
Como método de inferencia de tipos, Hindley-Milner permite deducir los tipos de variables, expresiones y funciones a partir de programas escritos sin tipado. Al ser sensible al ámbito , no se limita a derivar los tipos solo a partir de una pequeña porción del código fuente , sino que puede hacerlo a partir de programas o módulos completos. Además, al ser capaz de manejar tipos paramétricos , es fundamental para los sistemas de tipos de muchos lenguajes de programación funcional . Su primera aplicación de este tipo se dio en el lenguaje de programación ML .
El origen se encuentra en el algoritmo de inferencia de tipos para el cálculo lambda con tipos simples , ideado por Haskell Curry y Robert Feys en 1958. En 1969, J. Roger Hindley extendió este trabajo y demostró que su algoritmo siempre infería el tipo más general. En 1978, Robin Milner [ 2 ] , independientemente del trabajo de Hindley, proporcionó un algoritmo equivalente, el Algoritmo W. En 1982, Luis Damas [ 4 ] finalmente demostró que el algoritmo de Milner es completo y lo extendió para admitir sistemas con referencias polimórficas.
Monomorfismo vs. polimorfismo
En el cálculo lambda de tipos simples , los tipos T son constantes de tipo atómico o tipos de función de la formaEstos tipos son monomórficos . Ejemplos típicos son los tipos utilizados en valores aritméticos:
Por el contrario, el cálculo lambda sin tipado es completamente neutral respecto al tipado, y muchas de sus funciones pueden aplicarse de forma significativa a cualquier tipo de argumento. El ejemplo más sencillo es la función identidad.
que simplemente devuelve cualquier valor al que se aplique. Ejemplos menos triviales incluyen tipos paramétricos como las listas .
Si bien el polimorfismo en general implica que las operaciones aceptan valores de más de un tipo, el polimorfismo utilizado aquí es paramétrico. En la literatura también se encuentra la notación de esquemas de tipos , que enfatiza la naturaleza paramétrica del polimorfismo. Además, las constantes pueden tipificarse con variables de tipo (cuantificadas). Por ejemplo, los siguientes esquemas de tipos cuantifican universalmente sobre, lo que significa que son ciertas para todas las posibles:
Los tipos polimórficos pueden convertirse en monomórficos mediante la sustitución consistente de sus variables. Ejemplos de instancias monomórficas son:
En términos más generales, los tipos son polimórficos cuando contienen variables de tipo, mientras que los tipos que no las contienen son monomórficos.
A diferencia de los sistemas de tipos utilizados, por ejemplo, en Pascal (1970) o C (1972), que solo admiten tipos monomórficos, HM está diseñado con énfasis en el polimorfismo paramétrico. Los sucesores de los lenguajes mencionados, como C++ (1985), se centraron en diferentes tipos de polimorfismo, concretamente el subtipado en relación con la programación orientada a objetos y la sobrecarga . Si bien el subtipado es incompatible con HM, existe una variante de sobrecarga sistemática disponible en el sistema de tipos de Haskell, basado en HM.
polimorfismo de Let
Al extender la inferencia de tipos para el cálculo lambda de tipos simples hacia el polimorfismo, hay que decidir si es admisible asignar un tipo polimórfico no solo como tipo de una expresión, sino también como tipo de una variable ligada a λ. Esto permitiría asignar el tipo genérico identidad a la variable 'id' en:
(λ id . ... (id 3) ... (id "texto") ... ) (λ x . x)
Permitir esto da lugar al cálculo lambda polimórfico ; sin embargo, la inferencia de tipos en este sistema no es decidible. [ 5 ] En cambio, HM distingue las variables que están inmediatamente ligadas a una expresión de las variables ligadas a λ más generales, llamando a las primeras variables ligadas a let, y permite que se asignen tipos polimórficos solo a estas. Esto lleva al polimorfismo let donde el ejemplo anterior toma la forma
sea id = λ x . x en ... (id 3) ... (id "texto") ...
que se puede tipificar con un tipo polimórfico para 'id'. Como se indicó, la sintaxis de la expresión se extiende para hacer explícitas las variables ligadas a let, y al restringir el sistema de tipos para permitir que solo las variables ligadas a let tengan tipos polimórficos, mientras que los parámetros en las abstracciones lambda deben obtener un tipo monomórfico, la inferencia de tipos se vuelve decidible.
Descripción general
El resto de este artículo se desarrolla de la siguiente manera:
- Se define el sistema de tipos HM. Esto se logra describiendo un sistema de deducción que precisa qué expresiones tienen qué tipo, si es que tienen alguno.
- A partir de ahí, se avanza hacia la implementación del método de inferencia de tipos. Tras presentar una variante sintáctica del sistema deductivo anterior, se esboza una implementación eficiente (algoritmo J), apelando principalmente a la intuición metalógica del lector.
- Dado que aún no está claro si el algoritmo J implementa realmente el sistema de deducción inicial, se introduce una implementación menos eficiente (algoritmo W) y se sugiere su uso en una demostración.
- Finalmente, se abordan otros temas relacionados con el algoritmo.
Se utiliza la misma descripción del sistema de deducción en todo momento, incluso para los dos algoritmos, para que las distintas formas en que se presenta el método HM sean directamente comparables.
El sistema de tipos Hindley-Milner
El sistema de tipos se puede describir formalmente mediante reglas sintácticas que definen un lenguaje para expresiones, tipos, etc. La presentación de esta sintaxis no es demasiado formal, ya que se escribe no para estudiar la gramática superficial , sino la profunda , y deja algunos detalles sintácticos abiertos. Esta forma de presentación es habitual. Partiendo de esto, se utilizan reglas de tipado para definir la relación entre expresiones y tipos. Como antes, la forma empleada es algo flexible.
Sintaxis
Las expresiones que se deben escribir son exactamente las del cálculo lambda extendido con una expresión let, como se muestra en la tabla adjunta. Se pueden usar paréntesis para desambiguar una expresión. La aplicación es de enlace izquierdo y enlaza con mayor fuerza que la abstracción o la construcción let-in.
Los tipos se dividen sintácticamente en dos grupos: monotipos y politipos. [ nota 2 ]
Monotipos
Los monotipos siempre designan un tipo particular.se representan sintácticamente como términos .
Ejemplos de monotipos incluyen constantes de tipo comooy tipos paramétricos como. Estos últimos tipos son ejemplos de aplicaciones de funciones de tipo , por ejemplo, del conjunto donde el superíndice indica el número de parámetros de tipo. El conjunto completo de funciones de tipoes arbitrario en HM, [ nota 3 ] excepto que debe contener al menos, el tipo de funciones. A menudo se escribe en notación infija por conveniencia. Por ejemplo, una función que asigna enteros a cadenas tiene el tipoNuevamente, los paréntesis pueden usarse para desambiguar una expresión de tipo. La aplicación tiene una vinculación más fuerte que la flecha infija, que es de vinculación derecha.
Las variables de tipo se admiten como monotipos. Los monotipos no deben confundirse con los tipos monomórficos, que excluyen las variables y solo permiten términos básicos.
Dos monotipos son iguales si tienen términos idénticos.
Politipos
Los politipos (o esquemas de tipos ) son tipos que contienen variables delimitadas por cero o más cuantificadores para todos, por ejemplo:.
Una función con politipopuede asignar cualquier valor del mismo tipo a sí mismo, y la función identidad es un valor para este tipo.
Como otro ejemplo,es el tipo de una función que asigna números enteros a todos los conjuntos finitos. Una función que devuelve la cardinalidad de un conjunto sería un valor de este tipo.
Los cuantificadores solo pueden aparecer en el nivel superior. Por ejemplo, un tipoestá excluido por la sintaxis de tipos. Además, los monotipos están incluidos en los politipos, por lo que un tipo tiene la forma general, dóndeyes un monotipo.
La igualdad de politipos depende de reordenar la cuantificación y renombrar las variables cuantificadas (-conversión). Además, las variables cuantificadas que no aparecen en el monotipo pueden eliminarse.
Contexto y escritura
Para unir de manera significativa las partes aún disjuntas (expresiones sintácticas y tipos) se necesita una tercera parte: el contexto. Sintácticamente, un contexto es una lista de pares, llamadas asignaciones , suposiciones o vinculaciones , cada par indica que el valor de la variabletiene tipoLas tres partes combinadas dan un juicio de tipo de la forma, afirmando que bajo supuestos, la expresióntiene tipo.
Variables de tipo libre
En un tipo, el símboloes el cuantificador que vincula las variables de tipoen el monotipoLas variablesse denominan cuantificadas y cualquier ocurrencia de una variable de tipo cuantificado ense denomina variable de tipo ligada y todas las variables de tipo no ligadas ense denominan libres . Además de la cuantificaciónEn los politipos, las variables de tipo también pueden estar ligadas al aparecer en el contexto, pero con el efecto inverso en el lado derecho de laEn ese caso, dichas variables se comportan como constantes de tipo. Finalmente, una variable de tipo puede aparecer legalmente sin ligar en una tipificación, en cuyo caso se cuantifican implícitamente.
La presencia de variables de tipo tanto ligadas como no ligadas es algo inusual en los lenguajes de programación. A menudo, todas las variables de tipo se tratan implícitamente como cuantificadas. Por ejemplo, no existen cláusulas con variables libres en Prolog . Del mismo modo, en Haskell, [ nota 4 ] donde todas las variables de tipo aparecen implícitamente cuantificadas, es decir, un tipo de Haskell a -> asignificaAquí. Relacionado y también muy poco común es el efecto de unión del lado derecho.de las tareas.
Normalmente, la mezcla de variables de tipo ligado y no ligado se origina a partir del uso de variables libres en una expresión. La función constante K =proporciona un ejemplo. Tiene el monotipo. Se puede forzar el polimorfismo mediante. Aquí,tiene el tipo. La variable monotipo librese origina a partir del tipo de variablelimitado al ámbito circundante.tiene el tipoUno podría imaginar la variable de tipo libreen el tipo deestar obligado por elen el tipo dePero tal alcance no puede expresarse en HM. Más bien, la vinculación se realiza mediante el contexto.
Orden de tipo
El polimorfismo implica que una misma expresión puede tener (quizás infinitas) variantes. Sin embargo, en este sistema de tipos, estas variantes no son completamente independientes, sino que están condicionadas por el polimorfismo paramétrico.
Como ejemplo, la identidadpuede tenercomo su tipo así como oy muchos otros, pero no. El tipo más general para esta función es , mientras que los demás son más específicos y pueden derivarse del general reemplazando consistentemente otro tipo por el parámetro de tipo , es decir, la variable cuantificadaEl contraejemplo falla porque la sustitución no es consistente.
La sustitución consistente puede formalizarse aplicando una sustitución.al término de un tipo, escritoComo sugiere el ejemplo, la sustitución no solo está fuertemente relacionada con un orden, que expresa que un tipo es más o menos especial, sino también con la cuantificación total que permite que se aplique la sustitución.
Formalmente, en HM, un tipoes más general queformalmente, si alguna variable cuantificada ense sustituye de forma consistente de tal manera que se obtienecomo se muestra en la barra lateral. Este orden forma parte de la definición de tipo del sistema de tipos.
En nuestro ejemplo anterior, aplicando la sustituciónresultaría en.
Si bien sustituir un tipo monomórfico (base) por una variable cuantificada es sencillo, sustituir un politipo presenta algunas dificultades debido a la presencia de variables libres. En particular, las variables no ligadas no deben reemplazarse, ya que se tratan como constantes. Además, las cuantificaciones solo pueden ocurrir en el nivel superior. Al sustituir un tipo paramétrico, es necesario elevar sus cuantificadores. La tabla de la derecha aclara esta regla.
Como alternativa, consideremos una notación equivalente para los politipos sin cuantificadores, en la que las variables cuantificadas se representan mediante un conjunto diferente de símbolos. En dicha notación, la especialización se reduce a una simple sustitución consistente de dichas variables.
La relaciónes un pedido parcial y es su elemento más pequeño.
Tipo principal
Si bien la especialización de un esquema de tipos es una de las aplicaciones del orden, este desempeña un segundo papel crucial en el sistema de tipos. La inferencia de tipos con polimorfismo se enfrenta al reto de resumir todos los tipos posibles que puede tener una expresión. El orden garantiza que dicho resumen exista como el tipo más general de la expresión.
Sustitución en tipificaciones
El orden de tipos definido anteriormente puede extenderse a las tipificaciones porque la cuantificación total implícita de las tipificaciones permite un reemplazo consistente:
Contrariamente a la regla de especialización, esto no forma parte de la definición, sino que, al igual que la cuantificación total implícita, es una consecuencia de las reglas de tipo que se definen a continuación. Las variables de tipo libres en una tipificación sirven como marcadores de posición para un posible refinamiento. El efecto vinculante del entorno a las variables de tipo libres en el lado derecho deLo que prohíbe su sustitución en la regla de especialización es, de nuevo, que un reemplazo tiene que ser consistente y tendría que incluir toda la tipificación.
Este artículo analizará cuatro conjuntos de reglas diferentes:
- sistema declarativo
- sistema sintáctico
- algoritmo J
- algoritmo W
Sistema deductivo
La sintaxis de HM se traslada a la sintaxis de las reglas de inferencia que conforman el cuerpo del sistema formal , utilizando los tipos como juicios . Cada regla define qué conclusión se puede extraer de qué premisas. Además de los juicios, algunas condiciones adicionales introducidas anteriormente también pueden utilizarse como premisas.
Una demostración que utiliza las reglas es una secuencia de juicios tal que todas las premisas se enumeran antes de una conclusión. Los ejemplos a continuación muestran un posible formato de demostraciones. De izquierda a derecha, cada línea muestra la conclusión, lade la regla aplicada y las premisas, ya sea haciendo referencia a una línea (número) anterior si la premisa es un juicio o haciendo explícito el predicado.
Reglas de mecanografía
- Véase también Reglas de mecanografía
El recuadro lateral muestra las reglas de deducción del sistema de tipos HM. A grandes rasgos, las reglas se pueden dividir en dos grupos:
Las primeras cuatro reglas(acceso a variables o funciones),( aplicación , es decir, llamada a una función con un parámetro),( abstracción , es decir, declaración de función) yLas declaraciones de variables se centran en la sintaxis, presentando una regla para cada forma de expresión. Su significado es evidente a primera vista, ya que descomponen cada expresión, demuestran sus subexpresiones y, finalmente, combinan los tipos individuales encontrados en las premisas con el tipo en la conclusión.
El segundo grupo está formado por las dos reglas restantes.y. Manejan la especialización y generalización de tipos. Mientras que la regladebería quedar claro en la sección sobre especialización anterior ,Complementa lo anterior, trabajando en la dirección opuesta. Permite la generalización, es decir, cuantificar variables monotípicas no ligadas en el contexto.
Los dos ejemplos siguientes ilustran el funcionamiento del sistema de reglas. Dado que se proporcionan tanto la expresión como el tipo, se trata de un uso de las reglas para la verificación de tipos.
Ejemplo : Una prueba paradónde, podría escribirse
Ejemplo : Para demostrar la generalización, Se muestra a continuación:
polimorfismo de Let
Aunque no es visible de inmediato, el conjunto de reglas codifica una regulación bajo la cual un tipo puede generalizarse o no mediante un uso ligeramente variable de monotipos y politipos en las reglas.yRecuerda queydenotan poli- y monotipos respectivamente.
En regla, el valor variable del parámetro de la funciónse agrega al contexto con un tipo monomórfico a través de la premisa, mientras que en la regla La variable ingresa al entorno en forma polimórfica.. Aunque en ambos casos la presencia deEn este contexto, se impide el uso de la regla de generalización para cualquier variable libre en la asignación; esta regulación impone el tipo de parámetro.en un-la expresión debe permanecer monomórfica, mientras que en una expresión let, la variable podría introducirse de forma polimórfica, lo que posibilita las especializaciones.
Como consecuencia de esta regulación,no se puede tipificar, ya que el parámetroestá en una posición monomórfica, mientras quetiene tipo, porquese ha introducido en una expresión let y, por lo tanto, se trata como polimórfica.
Regla de generalización
La regla de generalización también merece un análisis más detallado. Aquí, la cuantificación total implícita en la premisasimplemente se mueve al lado derecho deen la conclusión, limitada por un cuantificador universal explícito. Esto es posible, ya queNo se produce de forma espontánea en este contexto. De nuevo, si bien esto hace plausible la regla de generalización, no es realmente una consecuencia. Por el contrario, la regla de generalización forma parte de la definición del sistema de tipos de HM y la cuantificación total implícita es una consecuencia.
Un algoritmo de inferencia
Ahora que se dispone del sistema deductivo de HM, se podría presentar un algoritmo y validarlo con respecto a las reglas. Alternativamente, podría derivarse analizando con mayor detalle cómo interactúan las reglas y cómo se forman las pruebas. Esto se aborda en el resto de este artículo, centrándonos en las posibles decisiones que se pueden tomar al demostrar una tipificación.
Grados de libertad para elegir las reglas
Aislando los puntos en una prueba, donde no es posible ninguna decisión, el primer grupo de reglas centrado en la sintaxis no deja elección ya que a cada regla sintáctica le corresponde una regla de tipificación única, que determina una parte de la prueba, mientras que entre la conclusión y las premisas de estas partes fijas existen cadenas dey Podría ocurrir. Tal cadena también podría existir entre la conclusión de la demostración y la regla para la expresión superior. Todas las demostraciones deben tener la forma esbozada.
Porque la única opción en una prueba con respecto a la selección de reglas son las yEn cuanto a las cadenas, la forma de la prueba plantea la cuestión de si se puede precisar aún más, de modo que dichas cadenas no sean necesarias. De hecho, esto es posible y da lugar a una variante del sistema de reglas sin tales reglas.
Sistema de reglas dirigido por la sintaxis
Un tratamiento contemporáneo de HM utiliza un sistema de reglas puramente dirigido por la sintaxis debido a Clement [ 6 ] como paso intermedio. En este sistema, la especialización se ubica directamente después de la original.regla y se fusionó con ella, mientras que la generalización pasa a formar parte de laregla. Allí también se determina que la generalización siempre produzca el tipo más general mediante la introducción de la función., que cuantifica todas las variables monotipo no ligadas en.
Formalmente, para validar que este nuevo sistema de reglases equivalente al original, uno tiene que demostrar que, que se descompone en dos subpruebas:
- ( Consistencia )
- ( Integridad )
Si bien la coherencia se puede observar descomponiendo las reglasy deen pruebas en, es probable que sea visible queestá incompleto, ya que no se puede demostraren, por ejemplo, pero solo Sin embargo , se puede demostrar una versión ligeramente más débil de completitud [ 7 ] , a saber:
lo que implica que se puede derivar el tipo principal para una expresión enlo que nos permite generalizar la demostración al final.
ComparandoyAhora, solo aparecen monotipos en los juicios de todas las reglas. Además, la forma de cualquier prueba posible con el sistema de deducción es ahora idéntica a la forma de la expresión (ambas vistas como árboles ). Por lo tanto, la expresión determina completamente la forma de la prueba. EnLa forma probablemente se determinaría con respecto a todas las reglas exceptoy, que permiten construir ramas (cadenas) de longitud arbitraria entre los demás nodos.
Grados de libertad que ejemplifican las reglas
Ahora que se conoce la estructura de la prueba, ya se está cerca de formular un algoritmo de inferencia de tipos. Dado que cualquier prueba para una expresión dada debe tener la misma estructura, se puede asumir que los monotipos en los juicios de la prueba son indeterminados y considerar cómo determinarlos.
Aquí entra en juego el orden de sustitución (especialización). Aunque a primera vista no se pueden determinar los tipos localmente, se espera que sea posible refinarlos con la ayuda del orden mientras se recorre el árbol de prueba, asumiendo además, dado que el algoritmo resultante se convertirá en un método de inferencia, que el tipo en cualquier premisa se determinará como el mejor posible. Y de hecho, se puede, como se observa en las reglas desugiere:
- [ Abs ] : La elección crítica es τ . En este punto, no se sabe nada sobre τ , por lo que solo se puede asumir el tipo más general, que esEl plan consiste en especializar el tipo si fuera necesario. No se permite un politipo en este caso, así que por ahora tendremos que usar algún α . Para evitar capturas no deseadas, una variable de tipo que aún no esté en la prueba es una opción segura. Además, hay que tener en cuenta que este monotipo aún no está fijo, sino que podría refinarse posteriormente.
- [ Var ] : La elección radica en cómo refinar σ . Dado que cualquier elección de un tipo τ aquí depende del uso de la variable, que no se conoce localmente, la opción más segura es la más general. Utilizando el mismo método anterior, se pueden instanciar todas las variables cuantificadas en σ con nuevas variables monotipo, manteniéndolas así abiertas a un mayor refinamiento.
- [ Let ] : La regla no deja otra opción. Hecho.
- [ Aplicación ] : Solo la regla de aplicación podría forzar un refinamiento de las variables "abiertas" hasta el momento, como lo requieren ambas premisas.
- La primera premisa obliga a que el resultado de la inferencia sea de la forma.
- Si es así, perfecto. Después se puede elegir su τ ' para el resultado.
- De lo contrario, podría tratarse de una variable abierta. En ese caso, se puede refinar a la forma requerida con dos nuevas variables, como antes.
- De lo contrario, la comprobación de tipos falla porque la primera premisa infirió un tipo que no es ni puede convertirse en un tipo de función .
- La segunda premisa exige que el tipo inferido sea igual a τ de la primera premisa. Ahora disponemos de dos tipos posiblemente diferentes, quizás con variables de tipo abiertas, para comparar e igualar si es posible. Si lo es, se encuentra una mejora; de lo contrario, se detecta un error de tipo. Se conoce un método eficaz para "igualar dos términos" mediante sustitución: la Unificación de Robinson en combinación con el algoritmo Union-Find .
- La primera premisa obliga a que el resultado de la inferencia sea de la forma.
Para resumir brevemente el algoritmo de unión-búsqueda, dado el conjunto de todos los tipos en una prueba, permite agruparlos en clases de equivalencia mediante un procedimiento de unión y elegir un representante para cada una de dichas clases utilizando un procedimiento de búsqueda . Haciendo hincapié en la palabra procedimiento en el sentido de efecto secundario , claramente estamos abandonando el ámbito de la lógica para preparar un algoritmo eficaz. El representante de unase determina de tal manera que, si tanto a como b son variables de tipo, entonces el representante es arbitrariamente uno de ellos, pero al unir una variable y un término, el término se convierte en el representante. Suponiendo que se dispone de una implementación de unión-búsqueda, se puede formular la unificación de dos monotipos de la siguiente manera:
unificar(ta, tb): ta = encontrar(ta) tb = encontrar(tb) si ambos ta,tb son términos de la forma D p1..pn con D,n idénticos entonces unify(ta[i], tb[i]) para cada parámetro i correspondiente sino si al menos uno de ta,tb es una variable de tipo entonces unión(ta, tb) De lo contrario, se producirá el error 'los tipos no coinciden'.
Ahora que tenemos un esbozo de un algoritmo de inferencia, se ofrece una presentación más formal en la siguiente sección. Se describe en Milner [ 2 ] P. 370 y ss. como algoritmo J.
Algoritmo J
La presentación del Algoritmo J es un mal uso de la notación de reglas lógicas, ya que incluye efectos secundarios pero permite una comparación directa conal tiempo que se expresa una implementación eficiente. Las reglas ahora especifican un procedimiento con parámetrosflexibleen la conclusión donde la ejecución de las premisas procede de izquierda a derecha.
El procedimientoespecializa el politipocopiando el término y reemplazando consistentemente las variables de tipo ligado por nuevas variables monotype.' produce una nueva variable monotipo. Probablemente,tiene que copiar el tipo introduciendo nuevas variables para la cuantificación para evitar capturas no deseadas. En general, el algoritmo ahora procede haciendo siempre la elección más general dejando la especialización a la unificación, que por sí misma produce el resultado más general. Como se señaló anteriormente , el resultado finaltiene que generalizarse aal final, para obtener el tipo más general para una expresión dada.
Debido a que los procedimientos utilizados en el algoritmo tienen un costo cercano a O(1), el costo total del algoritmo es casi lineal con respecto al tamaño de la expresión para la cual se debe inferir un tipo. Esto contrasta notablemente con muchos otros intentos de desarrollar algoritmos de inferencia de tipos, que a menudo resultaron ser NP-difíciles , o incluso indecidibles con respecto a la terminación. Por lo tanto, el algoritmo HM se desempeña tan bien como los mejores algoritmos de verificación de tipos completamente informados. En este contexto, la verificación de tipos significa que un algoritmo no tiene que encontrar una prueba, sino solo validar una ya dada.
La eficiencia se reduce ligeramente porque se debe mantener la vinculación de las variables de tipo en el contexto para permitir el cálculo dey habilitar una comprobación de ocurrencias para evitar la creación de tipos recursivos duranteUn ejemplo de tal caso es, para los cuales no se puede derivar ningún tipo usando HM. En la práctica, los tipos son solo términos pequeños y no construyen estructuras expansivas. Por lo tanto, en el análisis de complejidad, se puede tratar su comparación como una constante, manteniendo costos O(1).
Demostrando el algoritmo
En la sección anterior, al esbozar el algoritmo, se insinuó su demostración mediante argumentación metalógica. Si bien esto conduce a un algoritmo eficiente J, no está claro si el algoritmo refleja adecuadamente los sistemas de deducción D o S que sirven como base semántica.
El punto más crítico en la argumentación anterior es el refinamiento de las variables monotype ligadas por el contexto. Por ejemplo, el algoritmo cambia audazmente el contexto mientras infiere, por ejemplo, porque la variable monotype se agregó al contexto para el parámetromás tarde necesita ser refinado paraal manejar la aplicación. El problema es que las reglas de deducción no permiten tal refinamiento. Argumentar que el tipo refinado podría haberse agregado antes en lugar de la variable monotype es, en el mejor de los casos, una solución provisional.
La clave para lograr un argumento formalmente satisfactorio reside en incluir adecuadamente el contexto dentro del refinamiento. Formalmente, la tipificación es compatible con la sustitución de variables de tipo libre.
Refinar las variables libres significa, por lo tanto, refinar toda la tipificación.
Algoritmo W
A partir de ahí, una demostración del algoritmo J conduce al algoritmo W, que solo produce los efectos secundarios impuestos por el procedimiento.explícito al expresar su composición serial por medio de las sustituciones . La presentación del algoritmo W en la barra lateral todavía utiliza efectos secundarios en las operaciones establecidas en cursiva, pero estos ahora se limitan a generar nuevos símbolos. La forma de juicio es, que denota una función con un contexto y una expresión como parámetro que produce un monotipo junto con una sustitución.es una versión sin efectos secundarios deproduciendo una sustitución que es el unificador más general .
Si bien el algoritmo W normalmente se considera el algoritmo HM y a menudo se presenta directamente después del sistema de reglas en la literatura, su propósito es descrito por Milner [ 2 ] en la página 369 de la siguiente manera:
- Tal como está, W no es un algoritmo eficiente; se aplican sustituciones con demasiada frecuencia. Fue formulado para facilitar la demostración de su corrección. Ahora presentamos un algoritmo J más simple que simula W de forma precisa.
Si bien consideraba que W era más complejo y menos eficiente, lo presentó en su publicación antes que J. Tiene sus ventajas cuando los efectos secundarios no están disponibles o son indeseados. W también es necesario para demostrar la completitud, lo cual él considera parte de la prueba de solidez.
Obligaciones de prueba
Antes de formular las obligaciones de prueba, es necesario destacar una diferencia entre los sistemas de reglas D y S y los algoritmos presentados.
Si bien el desarrollo anterior hizo un uso indebido de los monotipos como variables de prueba "abiertas", se evitó la posibilidad de que las variables monotípicas adecuadas se vieran afectadas introduciendo nuevas variables y esperando que todo saliera bien. Pero hay un inconveniente: una de las promesas era que estas nuevas variables se "tendrían en cuenta" como tales. El algoritmo no cumple con esta promesa.
Tener un contexto, la expresión tampoco se puede escribiro, pero los algoritmos dan con el tipo, donde W además proporciona la sustituciónEsto significa que el algoritmo no detecta todos los errores de tipo. Esta omisión se puede solucionar fácilmente distinguiendo con mayor precisión entre variables de prueba y variables monotipo.
Los autores eran plenamente conscientes del problema, pero decidieron no solucionarlo. Se podría suponer que existe una razón pragmática detrás de esto. Si bien una implementación más adecuada de la inferencia de tipos habría permitido que el algoritmo manejara monotipos abstractos, estos no eran necesarios para la aplicación prevista, donde ninguno de los elementos en un contexto preexistente tiene variables libres. En este sentido, se eliminó la complicación innecesaria en favor de un algoritmo más simple. La desventaja restante es que la prueba del algoritmo con respecto al sistema de reglas es menos general y solo se puede realizar para contextos concomo condición adicional.
La condición adicional en la obligación de completitud aborda cómo la deducción puede generar muchos tipos, mientras que el algoritmo siempre produce uno. Al mismo tiempo, la condición adicional exige que el tipo inferido sea realmente el más general.
Para probar adecuadamente las obligaciones, primero es necesario fortalecerlas para permitir la activación del lema de sustitución que enlaza la sustitución.a través dey. A partir de ahí, las demostraciones se realizan por inducción sobre la expresión.
Otra obligación de prueba es el lema de sustitución en sí, es decir, la sustitución de la tipificación, que finalmente establece la cuantificación total. Esto último no puede probarse formalmente, ya que no se dispone de dicha sintaxis.
Extensiones
Definiciones recursivas
Para que la programación sea práctica, se necesitan funciones recursivas. Una propiedad fundamental del cálculo lambda es que las definiciones recursivas no están disponibles directamente, sino que pueden expresarse mediante un combinador de punto fijo . Sin embargo, el combinador de punto fijo no puede formularse en una versión tipada del cálculo lambda sin que ello tenga un efecto desastroso en el sistema, como se explica a continuación.
Regla de escritura
El artículo original [ 4 ] muestra que la recursión puede realizarse mediante un combinador. Por lo tanto , una posible definición recursiva podría formularse como: ::={\mathtt {let}}\ v={\mathit {fix}}(\lambda v.e_{1})\ {\mathtt {in}}\ e_{2}} .
Como alternativa, es posible una extensión de la sintaxis de la expresión y una regla de tipado adicional:
dónde
básicamente fusionandoymientras se incluyen las variables definidas recursivamente en posiciones monotípicas donde aparecen a la izquierda de lapero como politipos a su derecha.
Consecuencias
Si bien lo anterior es sencillo, tiene un precio.
La teoría de tipos conecta el cálculo lambda con la computación y la lógica. La sencilla modificación anterior tiene efectos en ambos:
- La propiedad de normalización fuerte queda invalidada, ya que se pueden formular términos no terminantes.
- La lógica se derrumba debido al tipose vuelve habitado .
Sobrecarga
La sobrecarga implica que se pueden definir y usar diferentes funciones con el mismo nombre. La mayoría de los lenguajes de programación, al menos, ofrecen sobrecarga con las operaciones aritméticas integradas (+, <, etc.), lo que permite al programador escribir expresiones aritméticas de la misma forma, incluso para diferentes tipos numéricos como into real. Dado que la combinación de estos diferentes tipos dentro de la misma expresión también requiere una conversión implícita, la sobrecarga, especialmente para estas operaciones, suele estar integrada en el propio lenguaje de programación. En algunos lenguajes, esta característica se generaliza y se pone a disposición del usuario, por ejemplo, en C++.
Si bien en la programación funcional se ha evitado la sobrecarga ad hoc debido a los costos computacionales tanto en la verificación de tipos como en la inferencia , se ha introducido un método para sistematizar la sobrecarga que se asemeja, tanto en forma como en nomenclatura, a la programación orientada a objetos, pero que opera un nivel superior. En este sistema, las "instancias" no son objetos (es decir, a nivel de valor), sino tipos. El ejemplo de ordenación rápida mencionado en la introducción utiliza la sobrecarga en los órdenes, con la siguiente anotación de tipo en Haskell:
Ordenar rápido :: Orden a => [ a ] -> [ a ]En este caso, el tipo ano solo es polimórfico, sino que también está restringido a ser una instancia de alguna clase de tipo Ordque proporciona los predicados de ordenación <utilizados >=en el cuerpo de las funciones. Las implementaciones adecuadas de estos predicados se pasan a quicksort como parámetros adicionales, tan pronto como quicksort se utiliza en tipos más concretos que proporcionan una única implementación de la función sobrecargada quickSort.
Dado que las "clases" solo admiten un único tipo como argumento, el sistema de tipos resultante aún puede proporcionar inferencia. Además, las clases de tipos pueden equiparse con algún tipo de orden de sobrecarga que permite organizar las clases como un retículo .
Tipos de orden superior
El polimorfismo paramétrico implica que los tipos se pasan como parámetros, como si fueran valores propios. Al pasarlos como argumentos a funciones propias, pero también a funciones de tipo (como en las constantes de tipo paramétricas), surge la pregunta de cómo tipificar los tipos de forma más precisa. Los tipos de orden superior se utilizan para crear un sistema de tipos aún más expresivo.
La unificación ya no es decidible en presencia de metatipos, lo que imposibilita la inferencia de tipos en este grado de generalidad. Además, asumir un tipo de todos los tipos que se incluya a sí mismo como tipo conduce a una paradoja, como en el conjunto de todos los conjuntos, por lo que es necesario proceder por niveles de abstracción. La investigación en cálculo lambda de segundo orden , un nivel superior, demostró que la inferencia de tipos es indecidible en este grado de generalidad.
Haskell introduce un nivel superior llamado tipo . En Haskell estándar, los tipos se infieren y se utilizan para poco más que describir la aridad de los constructores de tipos. Por ejemplo, se piensa que un constructor de tipo de lista mapea un tipo (el tipo de sus elementos) a otro tipo (el tipo de la lista que contiene dichos elementos); notacionalmente esto se expresa como. Existen extensiones de lenguaje que extienden los tipos para emular características de un sistema de tipos dependiente . [ 8 ]
Subtipificación
Los intentos de combinar la subtipificación y la inferencia de tipos han causado bastante frustración. Es sencillo acumular y propagar restricciones de subtipificación (a diferencia de las restricciones de igualdad de tipos), haciendo que las restricciones resultantes formen parte de los esquemas de tipificación inferidos, por ejemplo, dóndees una restricción en la variable de tipoSin embargo, debido a que las variables de tipo ya no se unifican de manera entusiasta en este enfoque, tiende a generar esquemas de tipado grandes y difíciles de manejar que contienen muchas variables de tipo y restricciones inútiles, lo que los hace difíciles de leer y comprender. Por lo tanto, se dedicó un esfuerzo considerable a simplificar dichos esquemas de tipado y sus restricciones, utilizando técnicas similares a las de la simplificación de autómatas finitos no deterministas (NFA) (útiles en presencia de tipos recursivos inferidos). [ 9 ] Más recientemente, Dolan y Mycroft [ 10 ] formalizaron la relación entre la simplificación del esquema de tipado y la simplificación de NFA y demostraron que una toma algebraica de la formalización del subtipado permitía generar esquemas de tipado principales compactos para un lenguaje similar a ML (llamado MLsub). En particular, su esquema de tipado propuesto utilizaba una forma restringida de tipos de unión e intersección en lugar de restricciones explícitas. Parreaux afirmó posteriormente [ 11 ] que esta formulación algebraica era equivalente a un algoritmo relativamente simple parecido al Algoritmo W, y que el uso de tipos de unión e intersección no era esencial.
Por otro lado, la inferencia de tipos ha demostrado ser más difícil en el contexto de los lenguajes de programación orientados a objetos, porque los métodos de objetos tienden a requerir polimorfismo de primera clase al estilo del Sistema F (donde la inferencia de tipos es indecidible) y debido a características como el polimorfismo acotado por F. En consecuencia, los sistemas de tipos con subtipos que permiten la programación orientada a objetos, como el sistema de Cardelli, [ 12 ] no admiten la inferencia de tipo estilo HM.
El polimorfismo de filas puede utilizarse como alternativa al subtipado para admitir características del lenguaje como los registros estructurales. [ 13 ] Si bien este estilo de polimorfismo es menos flexible que el subtipado en algunos aspectos, en particular al requerir más polimorfismo del estrictamente necesario para lidiar con la falta de direccionalidad en las restricciones de tipo, tiene la ventaja de que puede integrarse con los algoritmos HM estándar con bastante facilidad.
Notas
- ↑ La inferencia de tipos de Hindley-Milner es DEXPTIME -completa. De hecho, simplemente decidir si un programa ML es tipificable (sin tener que inferir un tipo) es en sí mismo DEXPTIME -completo. El comportamiento no lineal se manifiesta, pero principalmente en entradas patológicas . Por lo tanto, las demostraciones teóricas de complejidad de Mairson (1990) y Kfoury, Tiuryn y Urzyczyn (1990) sorprendieron a la comunidad investigadora.
- ↑ En el artículo original, los politipos se denominan "esquemas de tipos".
- ↑ Los tipos paramétricosNo estaban presentes en el artículo original sobre HM y no son necesarios para presentar el método. Ninguna de las reglas de inferencia que se describen a continuación los tendrá en cuenta ni siquiera los mencionará. Lo mismo ocurre con los "tipos primitivos" no paramétricos de dicho artículo. Toda la maquinaria para la inferencia de tipos polimórficos se puede definir sin ellos. Se han incluido aquí a modo de ejemplos, pero también porque la naturaleza de HM se basa en tipos paramétricos. Esto proviene del tipo de función., incorporado en las reglas de inferencia, a continuación, que ya tiene dos parámetros y se ha presentado aquí solo como un caso especial.
- ↑ Haskell proporciona la extensión de lenguaje ScopedTypeVariables que permite incluir en el ámbito todas las variables de tipo cuantificadas.
Referencias
- ↑ Hindley, J. Roger (1969). "El esquema de tipos principal de un objeto en lógica combinatoria". Transactions of the American Mathematical Society . 146 : 29–60 . doi : 10.2307/1995158 . JSTOR 1995158 .
- 1 2 3 4 Milner, Robin (1978). "Una teoría del polimorfismo de tipos en programación". Journal of Computer and System Sciences . 17 (3): 348– 374. CiteSeerX 10.1.1.67.5276 . doi : 10.1016/0022-0000(78)90014-4 . hdl : 20.500.11820/d16745d7-f113-44f0-a7a3-687c2b709f66 . S2CID 388583 .
- ↑ Damas, Luis (1985). Asignación de tipos en lenguajes de programación (tesis doctoral). Universidad de Edimburgo. hdl : 1842/13555 . CST-33-85.
- 1 2 3 Damas, Luis; Milner, Robin (1982). Esquemas de tipos principales para programas funcionales (PDF) . 9.º Simposio sobre Principios de Lenguajes de Programación (POPL'82). ACM. págs. 207–212 . doi : 10.1145/582153.582176 . ISBN 978-0-89791-065-1. Archivado del original (PDF) el 22-03-2022 . Consultado el 03-12-2012 .
- ↑ Wells, JB (1994). "La tipabilidad y la verificación de tipos en el cálculo lambda de segundo orden son equivalentes e indecidibles" . Actas del 9.º Simposio Anual IEEE sobre Lógica en Ciencias de la Computación (LICS) . págs. 176–185 . doi : 10.1109/LICS.1994.316068 . ISBN 0-8186-6310-3. S2CID 15078292 .
- ↑ Clement (1986). Un lenguaje aplicativo simple: Mini-ML (PDF) . LFP'86. ACM. doi : 10.1145/319838.319847 . ISBN 978-0-89791-200-6.
- ↑ Vaughan, Jeff (23 de julio de 2008) [5 de mayo de 2005]. "Una prueba de corrección para el algoritmo de inferencia tipo Hindley-Milner" (PDF) . Archivado del original (PDF) el 24 de marzo de 2012.
{{cite journal}}: Para citar una revista se requiere|journal=( ayuda ) - ↑ Yorgey; Brent; Weirich; Stephanie; Cretin; Julien; Peyton Jones; Simin; Vytiniotis; Dmitrios; Magalhaes; José Pedro (enero de 2012). "Dando un ascenso a Haskell" . Actas del 8.º taller ACM SIGPLAN sobre tipos en el diseño e implementación de lenguajes . págs. 53–66 . doi : 10.1145/2103786.2103795 . ISBN 978-1-4503-1120-5.
- ↑ Pottier, François (1998). Inferencia de tipo en presencia de subtipificación: de la teoría a la práctica (Tesis) . Recuperado el 10 de agosto de 2021 .
- ↑ Dolan, Stephen; Mycroft, Alan (2017). "Polimorfismo, subtipado e inferencia de tipos en MLsub" (PDF) . POPL 2017: Actas del 44.º Simposio ACM SIGPLAN sobre Principios de Lenguajes de Programación . doi : 10.1145/3009837.3009882 .
- ↑ Parreaux, Lionel (2020). "La esencia simple del subtipado algebraico: inferencia de tipos principales con subtipado simplificado" . 25.ª Conferencia Internacional ACM SIGPLAN sobre Programación Funcional - ICFP 2020, [Evento en línea], 24-26 de agosto de 2020. doi : 10.1145 /3409006 .
- ↑ Cardelli, Luca; Martini, Simone; Mitchell, John C.; Scedrov, Andre (1994). "Una extensión del sistema F con subtipado". Information and Computation, vol. 9. North Holland, Ámsterdam. pp. 4–56 . doi : 10.1006/inco.1994.1013 .
- ↑ Daan Leijen, Registros extensibles con etiquetas con ámbito , Instituto de Ciencias de la Información e Informática, Universidad de Utrecht, Borrador, Revisión: 76, 23 de julio de 2005
- Mairson, Harry G. (1990). "Decidir la tipabilidad de ML es completo para tiempo exponencial determinista". Actas del 17.º simposio ACM SIGPLAN-SIGACT sobre principios de lenguajes de programación - POPL '90 . ACM. págs. 382–401 . doi : 10.1145/96709.96748 . ISBN 978-0-89791-343-0. S2CID 75336 .
- Kfoury, AJ; Tiuryn, J.; Urzyczyn, P. (1990). "ML typability is dexptime-complete". Caap '90 . Lecture Notes in Computer Science. Vol. 431. pp. 206–220 . doi : 10.1007/3-540-52590-4_50 . ISBN 978-3-540-52590-5.
Enlaces externos
- Una implementación legible en Haskell del algoritmo W junto con su código fuente en GitHub .
- Una implementación sencilla del algoritmo de Hindley-Milner en Python .
- Sistemas de tipos
- teoría de tipos
- Inferencia de tipo
- Cálculo lambda
- informática teórica
- Métodos formales
- 1969 en informática
- 1978 en informática
- 1985 en informática
- Algoritmos