Articulo de referencia

Sistema tipo Hindley-Milner

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-Hindle...

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 formaTT{\displaystyle T\rightarrow T}Estos tipos son monomórficos . Ejemplos típicos son los tipos utilizados en valores aritméticos:

3:nortemetrobmiradd 3 4:nortemetrobmiradd:nortemetrobmirnortemetrobmirnortemetrobmir{\displaystyle {\begin{array}{ll}3&:{\mathtt {Número}}\\{\mathtt {sumar}}\ 3\ 4&:{\mathtt {Número}}\\{\mathtt {sumar}}&:{\mathtt {Número}}\rightarrow {\mathtt {Número}}\rightarrow {\mathtt {Número}}\end{array}}}

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.

idλincógnita.incógnita{\displaystyle {\mathtt {id}}\equiv \lambda xx}

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α{\displaystyle \alpha }, lo que significa que son ciertas para todas las posiblesα{\displaystyle \alpha }:

doonortes:α.αList αList αnorteil:α.List αid:α.αα{\displaystyle {\begin{array}{ll}{\mathtt {cons}}&:\forall \alpha .\alpha \rightarrow {\mathtt {List}}\ \alpha \rightarrow {\mathtt {List}}\ \alpha \\{\mathtt {nil}}&:\forall \alpha .{\mathtt {List}}\ \alpha \\{\mathtt {id}}&:\forall \alpha .\alpha \rightarrow \alpha \end{array}}}

Los tipos polimórficos pueden convertirse en monomórficos mediante la sustitución consistente de sus variables. Ejemplos de instancias monomórficas son:

id:StrinortegramoStrinortegramonorteil:List nortemetrobmir{\displaystyle {\begin{array}{ll}{\mathtt {id}}'&:{\mathtt {String}}\rightarrow {\mathtt {String}}\\{\mathtt {nil}}'&:{\mathtt {List}}\ {\mathtt {Number}}\end{array}}}

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.τ{\displaystyle \tau }se representan sintácticamente como términos .

Ejemplos de monotipos incluyen constantes de tipo comoinortet{\displaystyle {\mathtt {int}}}ostrinortegramo{\displaystyle {\mathtt {string}}}y tipos paramétricos comoMETROapag (Smit strinortegramo) inortet{\displaystyle {\mathtt {Mapa\ (Conjunto\ cadena)\ entero}}}. Estos últimos tipos son ejemplos de aplicaciones de funciones de tipo , por ejemplo, del conjunto {METROapag2, Smit1, strinortegramo0, inortet0, 2}{\displaystyle \{{\mathtt {Map^{2},\ Set^{1},\ string^{0},\ int^{0}}},\ \rightarrow ^{2}\}}donde el superíndice indica el número de parámetros de tipo. El conjunto completo de funciones de tipodo{\displaystyle C}es arbitrario en HM, [ nota 3 ] excepto que debe contener al menos2{\displaystyle \rightarrow ^{2}}, 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 tipoinortetstrinortegramo{\displaystyle {\mathtt {int}}\rightarrow {\mathtt {string}}}Nuevamente, 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:α.αα{\displaystyle \forall \alpha .\alpha \rightarrow \alpha }.

Una función con politipoα.αα{\displaystyle \forall \alpha .\alpha \rightarrow \alpha }puede asignar cualquier valor del mismo tipo a sí mismo, y la función identidad es un valor para este tipo.

Como otro ejemplo,α.(Smit α)inortet{\displaystyle \forall \alpha .({\mathtt {Set}}\ \alpha )\rightarrow {\mathtt {int}}}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 tipoα.αα.α{\displaystyle \forall \alpha .\alpha \rightarrow \forall \alpha .\alpha }está 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α1αnorte.τ{\displaystyle \forall \alpha _{1}\dots \forall \alpha _{n}.\tau }, dóndenorte0{\displaystyle n\geq 0}yτ{\displaystyle \tau }es un monotipo.

La igualdad de politipos depende de reordenar la cuantificación y renombrar las variables cuantificadas (α{\displaystyle \alpha }-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 paresincógnita:σ{\displaystyle x:\sigma }, llamadas asignaciones , suposiciones o vinculaciones , cada par indica que el valor de la variableincógnitai{\displaystyle x_{i}}tiene tipoσi.{\displaystyle \sigma _{i}.}Las tres partes combinadas dan un juicio de tipo de la formaΓ  mi:σ{\displaystyle \Gamma \ \vdash \ e:\sigma }, afirmando que bajo supuestosΓ{\displaystyle \Gamma }, la expresiónmi{\displaystyle e}tiene tipoσ{\displaystyle \sigma }.

Variables de tipo libre

En un tipoα1αnorte.τ{\displaystyle \forall \alpha _{1}\dots \forall \alpha _{n}.\tau }, el símbolo{\displaystyle \forall }es el cuantificador que vincula las variables de tipoαi{\displaystyle \alpha _{i}}en el monotipoτ{\displaystyle \tau }Las variablesαi{\displaystyle \alpha _{i}}se denominan cuantificadas y cualquier ocurrencia de una variable de tipo cuantificado enτ{\displaystyle \tau }se denomina variable de tipo ligada y todas las variables de tipo no ligadas enτ{\displaystyle \tau }se denominan libres . Además de la cuantificación{\displaystyle \forall }En 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 la{\displaystyle \vdash }En 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 -> asignificaα.αα{\displaystyle \forall \alpha .\alpha \rightarrow \alpha }Aquí. Relacionado y también muy poco común es el efecto de unión del lado derecho.σ{\displaystyle \sigma }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 =λincógnita.λy.incógnita{\displaystyle \lambda x.\lambda yx}proporciona un ejemplo. Tiene el monotipoαβα{\displaystyle \alpha \rightarrow \beta \rightarrow \alpha }. Se puede forzar el polimorfismo mediantelmit k=λincógnita.(lmit F=λy.incógnita inorte F) inorte k{\displaystyle \mathbf {let} \ k=\lambda x.(\mathbf {let} \ f=\lambda y.x\ \mathbf {in} \ f)\ \mathbf {in} \ k}. Aquí,F{\displaystyle f}tiene el tipoγ.γα{\displaystyle \forall \gamma .\gamma \rightarrow \alpha }. La variable monotipo libreα{\displaystyle \alpha }se origina a partir del tipo de variableincógnita{\displaystyle x}limitado al ámbito circundante.k{\displaystyle k}tiene el tipoαβ.αβα{\displaystyle \forall \alpha \forall \beta .\alpha \rightarrow \beta \rightarrow \alpha }Uno podría imaginar la variable de tipo libreα{\displaystyle \alpha }en el tipo deF{\displaystyle f}estar obligado por elα{\displaystyle \forall \alpha }en el tipo dek{\displaystyle k}Pero 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 identidadλincógnita.incógnita{\displaystyle \lambda x.x}puede tenerα.αα{\displaystyle \forall \alpha .\alpha \rightarrow \alpha }como su tipo así como cadenacadena{\displaystyle {\texttt {string}}\rightarrow {\texttt {string}}}oenteroentero{\displaystyle {\texttt {int}}\rightarrow {\texttt {int}}}y muchos otros, pero noenterocadena{\displaystyle {\texttt {int}}\rightarrow {\texttt {string}}}. El tipo más general para esta función es α.αα{\displaystyle \forall \alpha .\alpha \rightarrow \alpha }, 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 cuantificadaα{\displaystyle \alpha }El contraejemplo falla porque la sustitución no es consistente.

La sustitución consistente puede formalizarse aplicando una sustitución.S={ aiτi,  }{\displaystyle S=\left\{\ a_{i}\mapsto \tau _{i},\ \dots \ \right\}}al término de un tipoτ{\displaystyle \tau }, escritoSτ{\displaystyle S\tau }Como 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 tipoσ{\displaystyle \sigma '}es más general queσ{\displaystyle \sigma }formalmenteσσ{\displaystyle \sigma '\sqsubseteq \sigma }, si alguna variable cuantificada enσ{\displaystyle \sigma '}se sustituye de forma consistente de tal manera que se obtieneσ{\displaystyle \sigma }como 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ónS={αcadena}{\displaystyle S=\left\{\alpha \mapsto {\texttt {string}}\right\}}resultaría enα.ααcadenacadena{\displaystyle \forall \alpha .\alpha \rightarrow \alpha \sqsubseteq {\texttt {string}}\rightarrow {\texttt {string}}}.

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ón{\displaystyle \sqsubseteq }es un pedido parcial y α.α{\displaystyle \forall \alpha .\alpha }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:

Γmi:σSΓmi:Sσ{\displaystyle \Gamma \vdash e:\sigma \quad \Longrightarrow \quad S\Gamma \vdash e:S\sigma }

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 de{\displaystyle \vdash }Lo 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:

  1. D{\displaystyle \vdash _{D}}sistema declarativo
  2. S{\displaystyle \vdash _{S}}sistema sintáctico
  3. J{\displaystyle \vdash _{J}}algoritmo J
  4. W{\displaystyle \vdash _{W}}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, la[norteametromi]{\displaystyle [{\mathtt {Name}}]}de 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[Var]{\displaystyle [{\mathtt {Var}}]}(acceso a variables o funciones),[Apagpag]{\displaystyle [{\mathtt {App}}]}( aplicación , es decir, llamada a una función con un parámetro),[Abs]{\displaystyle [{\mathtt {Abs}}]}( abstracción , es decir, declaración de función) y[Lmit]{\displaystyle [{\mathtt {Let}}]}Las 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.[Inortest]{\displaystyle [{\mathtt {Inst}}]}y[GRAMOminorte]{\displaystyle [{\mathtt {Gen}}]}. Manejan la especialización y generalización de tipos. Mientras que la regla[Inortest]{\displaystyle [{\mathtt {Inst}}]}debería quedar claro en la sección sobre especialización anterior ,[GRAMOminorte]{\displaystyle [{\mathtt {Gen}}]}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 paraΓDid(norte):inortet{\displaystyle \Gamma \vdash _{D}id(n):int}dóndeΓ=id:α.αα, norte:inortet{\displaystyle \Gamma =id:\forall \alpha .\alpha \rightarrow \alpha ,\ n:int}, podría escribirse

1:ΓDid:α.αα[Var](id:α.ααΓ)2:ΓDid:inortetinortet[Inortest](1), (α.ααinortetinortet)3:ΓDnorte:inortet[Var](norte:inortetΓ)4:ΓDid(norte):inortet[Apagpag](2), (3){\displaystyle {\begin{array}{llll}1:&\Gamma \vdash _{D}id:\forall \alpha .\alpha \rightarrow \alpha &[{\mathtt {Var}}]&(id:\forall \alpha .\alpha \rightarrow \alpha \in \Gamma )\\2:&\Gamma \vdash _{D}id:int\rightarrow int&[{\mathtt {Inst}}]&(1),\ (\forall \alpha .\alpha \rightarrow \alpha \sqsubseteq int\rightarrow int)\\3:&\Gamma \vdash _{D}n:int&[{\mathtt {Var}}]&(n:int\in \Gamma )\\4:&\Gamma \vdash _{D}id(n):int&[{\mathtt {App}}]&(2),\ (3)\\\end{array}}}

Ejemplo : Para demostrar la generalización, D dejarid=λincógnita.incógnita en id:α.αα{\displaystyle \vdash _{D}\ {\textbf {let}}\,id=\lambda x.x\ {\textbf {in}}\ id\,:\,\forall \alpha .\alpha \rightarrow \alpha } Se muestra a continuación:

1:incógnita:αDincógnita:α[Var](incógnita:α{incógnita:α})2:Dλincógnita.incógnita:αα[Abs](1)3:id:ααDid:αα[Var](id:αα{id:αα})4:Ddejarid=λincógnita.incógnita en id:αα[Lmit](2), (3)5:Ddejarid=λincógnita.incógnita en id:α.αα[GRAMOminorte](4), (αFrmimi(ϵ)){\displaystyle {\begin{array}{llll}1:&x:\alpha \vdash _{D}x:\alpha &[{\mathtt {Var}}]&(x:\alpha \in \left\{x:\alpha \right\})\\2:&\vdash _{D}\lambda x.x:\alpha \rightarrow \alpha &[{\mathtt {Abs}}]&(1)\\3:&id:\alpha \rightarrow \alpha \vdash _{D}id:\alpha \rightarrow \alpha &[{\mathtt {Var}}]&(id:\alpha \rightarrow \alpha \in \left\{id:\alpha \rightarrow \alpha \right\})\\4:&\vdash _{D}{\textbf {let}}\,id=\lambda x.x\ {\textbf {in}}\ id\,:\,\alpha \rightarrow \alpha &[{\mathtt {Let}}]&(2),\ (3)\\5:&\vdash _{D}{\textbf {let}}\,id=\lambda x.x\ {\textbf {in}}\ id\,:\,\forall \alpha .\alpha \rightarrow \alpha &[{\mathtt {Gen}}]&(4),\ (\alpha \not \in free(\epsilon ))\\\end{array}}}

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.[Abs]{\displaystyle [{\mathtt {Abs}}]}y[Lmit]{\displaystyle [{\mathtt {Let}}]}Recuerda queσ{\displaystyle \sigma }yτ{\displaystyle \tau }denotan poli- y monotipos respectivamente.

En regla[Abs]{\displaystyle [{\mathtt {Abs}}]}, el valor variable del parámetro de la funciónλincógnita.mi{\displaystyle \lambda x.e}se agrega al contexto con un tipo monomórfico a través de la premisaΓ, incógnita:τDmi:τ{\displaystyle \Gamma ,\ x:\tau \vdash _{D}e:\tau '}, mientras que en la regla [Lmit]{\displaystyle [{\mathtt {Let}}]}La variable ingresa al entorno en forma polimórfica.Γ, incógnita:σDmi1:τ{\displaystyle \Gamma ,\ x:\sigma \vdash _{D}e_{1}:\tau }. Aunque en ambos casos la presencia deincógnita{\displaystyle x}En 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.incógnita{\displaystyle x}en unλ{\displaystyle \lambda }-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,λF.(Fverdadero,F0){\displaystyle \lambda f.(f\,{\textrm {true}},f\,{\textrm {0}})}no se puede tipificar, ya que el parámetroF{\displaystyle f}está en una posición monomórfica, mientras quedejar F=λincógnita.incógnitaen(Fverdadero,F0){\displaystyle {\textbf {let}}\ f=\lambda x.x\,{\textbf {in}}\,(f\,{\textrm {true}},f\,{\textrm {0}})}tiene tipo(bool,inortet){\displaystyle (bool,int)}, porqueF{\displaystyle f}se 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 premisaΓDmi:σ{\displaystyle \Gamma \vdash _{D}e:\sigma }simplemente se mueve al lado derecho deD{\displaystyle \vdash _{D}}en la conclusión, limitada por un cuantificador universal explícito. Esto es posible, ya queα{\displaystyle \alpha }No 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 de[Inortest]{\displaystyle [{\mathtt {Inst}}]}y[GRAMOminorte]{\displaystyle [{\mathtt {Gen}}]} 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 [Inortest]{\displaystyle [{\mathtt {Inst}}]}y[GRAMOminorte]{\displaystyle [{\mathtt {Gen}}]}En 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.[Var]{\displaystyle [{\mathtt {Var}}]}regla y se fusionó con ella, mientras que la generalización pasa a formar parte de la[Lmit]{\displaystyle [{\mathtt {Let}}]}regla. Allí también se determina que la generalización siempre produzca el tipo más general mediante la introducción de la función.Γ¯(τ){\displaystyle {\bar {\Gamma }}(\tau )}, que cuantifica todas las variables monotipo no ligadas enΓ{\displaystyle \Gamma }.

Formalmente, para validar que este nuevo sistema de reglasS{\displaystyle \vdash _{S}}es equivalente al originalD{\displaystyle \vdash _{D}}, uno tiene que demostrar queΓD mi:σΓS mi:σ{\displaystyle \Gamma \vdash _{D}\ e:\sigma \Leftrightarrow \Gamma \vdash _{S}\ e:\sigma }, que se descompone en dos subpruebas:

  • ΓD mi:σΓS mi:σ{\displaystyle \Gamma \vdash _{D}\ e:\sigma \Leftarrow \Gamma \vdash _{S}\ e:\sigma }( Consistencia )
  • ΓD mi:σΓS mi:σ{\displaystyle \Gamma \vdash _{D}\ e:\sigma \Rightarrow \Gamma \vdash _{S}\ e:\sigma }( Integridad )

Si bien la coherencia se puede observar descomponiendo las reglas[Lmit]{\displaystyle [{\mathtt {Let}}]}y[Var]{\displaystyle [{\mathtt {Var}}]} deS{\displaystyle \vdash _{S}}en pruebas enD{\displaystyle \vdash _{D}}, es probable que sea visible queS{\displaystyle \vdash _{S}}está incompleto, ya que no se puede demostrarλ incógnita.incógnita:α.αα{\displaystyle \lambda \ x.x:\forall \alpha .\alpha \rightarrow \alpha }enS{\displaystyle \vdash _{S}}, por ejemplo, pero solo λ incógnita.incógnita:αα{\displaystyle \lambda \ x.x:\alpha \rightarrow \alpha }Sin embargo , se puede demostrar una versión ligeramente más débil de completitud [ 7 ] , a saber:

  • ΓD mi:σΓS mi:τΓ¯(τ)σ{\displaystyle \Gamma \vdash _{D}\ e:\sigma \Rightarrow \Gamma \vdash _{S}\ e:\tau \wedge {\bar {\Gamma }}(\tau )\sqsubseteq \sigma }

lo que implica que se puede derivar el tipo principal para una expresión enS{\displaystyle \vdash _{S}}lo que nos permite generalizar la demostración al final.

ComparandoD{\displaystyle \vdash _{D}}yS{\displaystyle \vdash _{S}}Ahora, 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. EnD{\displaystyle \vdash _{D}}La forma probablemente se determinaría con respecto a todas las reglas excepto[Inortest]{\displaystyle [{\mathtt {Inst}}]}y[GRAMOminorte]{\displaystyle [{\mathtt {Gen}}]}, 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 deS{\displaystyle \vdash _{S}}sugiere:

  • [ 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 esα.α{\displaystyle \forall \alpha .\alpha }El 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.
    1. La primera premisa obliga a que el resultado de la inferencia sea de la formaττ{\displaystyle \tau \rightarrow \tau '}.
      • 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 .
    2. 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 .

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 unanorteionorte(a,b){\displaystyle {\mathtt {union}}(a,b)}se 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 conS{\displaystyle \vdash _{S}}al tiempo que se expresa una implementación eficiente. Las reglas ahora especifican un procedimiento con parámetrosΓ,mi{\displaystyle \Gamma ,e}flexibleτ{\displaystyle \tau }en la conclusión donde la ejecución de las premisas procede de izquierda a derecha.

El procedimientoinortest(σ){\displaystyle inst(\sigma )}especializa el politipoσ{\displaystyle \sigma }copiando el término y reemplazando consistentemente las variables de tipo ligado por nuevas variables monotype.nortemiwvar{\displaystyle newvar}' produce una nueva variable monotipo. Probablemente,Γ¯(τ){\displaystyle {\bar {\Gamma }}(\tau )}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 finalτ{\displaystyle \tau }tiene que generalizarse aΓ¯(τ){\displaystyle {\bar {\Gamma }}(\tau )}al 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 deΓ¯(τ){\displaystyle {\bar {\Gamma }}(\tau )}y habilitar una comprobación de ocurrencias para evitar la creación de tipos recursivos durantenorteiFy(α,τ){\displaystyle {\mathit {unify}}(\alpha ,\tau )}Un ejemplo de tal caso esλ incógnita.(incógnita incógnita){\displaystyle \lambda \ x.(x\ x)}, 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λF.(F 1){\displaystyle \lambda f.(f\ 1)}, porque la variable monotype se agregó al contexto para el parámetroF{\displaystyle f}más tarde necesita ser refinado parainortetβ{\displaystyle int\rightarrow \beta }al 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.

ΓSmi:τSΓSmi:Sτ{\displaystyle \Gamma \vdash _{S}e:\tau \quad \Longrightarrow \quad S\Gamma \vdash _{S}e:S\tau }

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.unión{\displaystyle {\textit {union}}}explícito al expresar su composición serial por medio de las sustituciones Si{\displaystyle S_{i}}. 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Γmi:τ,S{\displaystyle \Gamma \vdash e:\tau ,S}, que denota una función con un contexto y una expresión como parámetro que produce un monotipo junto con una sustitución.mgu{\displaystyle {\textsf {mgu}}}es una versión sin efectos secundarios deunión{\displaystyle {\textit {union}}}produciendo 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 contexto1:inortet, F:α{\displaystyle 1:int,\ f:\alpha }, la expresiónF 1{\displaystyle f\ 1} tampoco se puede escribirD{\displaystyle \vdash _{D}}oS{\displaystyle \vdash _{S}}, pero los algoritmos dan con el tipoβ{\displaystyle \beta }, donde W además proporciona la sustitución{αinortetβ}{\displaystyle \left\{\alpha \mapsto int\rightarrow \beta \right\}}Esto 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 conFrmimi(Γ)={\displaystyle free(\Gamma )=\emptyset }como condición adicional.

(Exactitud)ΓWmi:τ,SΓSmi:τ(Lo completo)ΓSmi:τΓWmi:τ,Spara todos τ dónde ¯(τ)τ{\displaystyle {\begin{array}{lll}{\text{(Correctness)}}&\Gamma \vdash _{W}e:\tau ,S&\quad \Longrightarrow \quad \Gamma \vdash _{S}e:\tau \\{\text{(Completeness)}}&\Gamma \vdash _{S}e:\tau &\quad \Longrightarrow \quad \Gamma \vdash _{W}e:\tau ',S\quad \quad {\text{forall}}\ \tau \ {\text{where}}\ {\overline {\emptyset }}(\tau ')\sqsubseteq \tau \end{array}}}

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.S{\displaystyle S}a través deS{\displaystyle \vdash _{S}}yW{\displaystyle \vdash _{W}}. 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. Fiincógnita:α.(αα)α{\displaystyle {\mathit {fix}}:\forall \alpha .(\alpha \rightarrow \alpha )\rightarrow \alpha }Por lo tanto , una posible definición recursiva podría formularse como: rmido v=mi1 inorte mi2 ::=lmit v=Fiincógnita(λv.mi1) inorte mi2{\displaystyle {\mathtt {rec}}\ v=e_{1}\ {\mathtt {in}}\ e_{2}\ ::={\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:

Γ,Γmi1:τ1Γ,Γminorte:τnorteΓ,Γmi:τΓ  rmido v1=mi1 anorted  anorted vnorte=minorte inorte mi:τ[Rmido]{\displaystyle \displaystyle {\frac {\Gamma ,\Gamma '\vdash e_{1}:\tau _{1}\quad \dots \quad \Gamma ,\Gamma '\vdash e_{n}:\tau _{n}\quad \Gamma ,\Gamma ''\vdash e:\tau }{\Gamma \ \vdash \ {\mathtt {rec}}\ v_{1}=e_{1}\ {\mathtt {and}}\ \dots \ {\mathtt {and}}\ v_{n}=e_{n}\ {\mathtt {in}}\ e:\tau }}\quad [{\mathtt {Rec}}]}

dónde

  • Γ=v1:τ1, , vnorte:τnorte{\displaystyle \Gamma '=v_{1}:\tau _{1},\ \dots ,\ v_{n}:\tau _{n}}
  • Γ=v1:Γ¯( τ1 ), , vnorte:Γ¯( τnorte ){\displaystyle \Gamma ''=v_{1}:{\bar {\Gamma }}(\ \tau _{1}\ ),\ \dots ,\ v_{n}:{\bar {\Gamma }}(\ \tau _{n}\ )}

básicamente fusionando[Abs]{\displaystyle [{\mathtt {Abs}}]}y[Lmit]{\displaystyle [{\mathtt {Let}}]}mientras se incluyen las variables definidas recursivamente en posiciones monotípicas donde aparecen a la izquierda de lainorte{\displaystyle {\mathtt {in}}}pero 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:

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{\displaystyle *\to *}. 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α. (αT)αα{\displaystyle \forall \alpha .\ (\alpha \leq T)\Rightarrow \alpha \rightarrow \alpha }, dóndeαT{\displaystyle \alpha \leq T}es una restricción en la variable de tipoα{\displaystyle \alpha }Sin 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 CardelliF<:{\displaystyle F_{<:}}, [ 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

  1. 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.
  2. En el artículo original, los politipos se denominan "esquemas de tipos".
  3. Los tipos paramétricosdo ττ{\displaystyle C\ \tau \dots \tau }No 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.ττ{\displaystyle \tau \rightarrow \tau }, 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.
  4. Haskell proporciona la extensión de lenguaje ScopedTypeVariables que permite incluir en el ámbito todas las variables de tipo cuantificadas.

Referencias

  1. 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 . 
  2. 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 .  
  3. Damas, Luis (1985). Asignación de tipos en lenguajes de programación (tesis doctoral). Universidad de Edimburgo. hdl : 1842/13555 . CST-33-85.
  4. 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 .
  5. 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 . 
  6. Clement (1986). Un lenguaje aplicativo simple: Mini-ML (PDF) . LFP'86. ACM. doi : 10.1145/319838.319847 . ISBN 978-0-89791-200-6.
  7. 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 )
  8. 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.
  9. 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 .
  10. 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 .
  11. 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 .
  12. 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 . 
  13. 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.
  • 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 .