Como entendo as regras de Hindley-Milner?
Hindley-Milner é um conjunto de regras na forma de cálculo sequencial (não dedução natural) que demonstra que podemos deduzir o tipo (mais geral) de um programa da construção do programa sem declarações explícitas de tipo.
Os símbolos e notação
Primeiro, vamos explicar os símbolos e discutir a precedência do operador
- 𝑥 é um identificador (informalmente, um nome de variável).
- : significa é um tipo de (informalmente, uma instância de ou "é-a").
- 𝜎 (sigma) é uma expressão que é uma variável ou função.
- assim 𝑥: 𝜎 é lido " 𝑥 is-a 𝜎 "
- ∈ significa "é um elemento de"
- Gam (Gama) é um ambiente.
- ⊦ (o sinal de afirmação) significa afirmações (ou prova, mas contextualmente "afirma" lê melhor.)
- 𝚪 ⊦ 𝑥 : 𝜎 é então lido "𝚪 afirma que 𝑥, é-a 𝜎 "
- 𝑒 é uma instância real (elemento) do tipo 𝜎 .
- 𝜏 (tau) é um tipo: básico, variável ( 𝛼 ), funcional 𝜏 → 𝜏 ' ou produto 𝜏 × 𝜏' (o produto não é usado aqui)
- 𝜏 → 𝜏 ' é um tipo funcional em que 𝜏 e 𝜏' são tipos potencialmente diferentes.
𝑒.𝑒 significa 𝜆 (lambda) é uma função anônima que recebe um argumento 𝑥 e retorna uma expressão, 𝑒 .
deixe 𝑥 = 𝑒₀ in 𝑒₁ significa na expressão, 𝑒₁ , substitua 𝑒₀ onde quer que 𝑥 apareça.
⊑ significa que o elemento anterior é um subtipo (informalmente - subclasse) do último elemento.
- 𝛼 é uma variável de tipo.
- ∀ 𝛼.𝜎 é um tipo, ∀ (para todas) variáveis de argumento, 𝛼 , retornando 𝜎 expressão
- ∉ livre (𝚪) significa que não é um elemento das variáveis de tipo livre de 𝚪 definidas no contexto externo. (Variáveis vinculadas são substituíveis.)
Tudo acima da linha é a premissa, tudo abaixo é a conclusão ( Per Martin-Löf )
Precedência, por exemplo
Eu peguei alguns dos exemplos mais complexos das regras e inseri parênteses redundantes que mostram precedência:
- 𝑥: 𝜎 ∈ 𝚪 pode ser escrito (𝑥: 𝜎) ∈ 𝚪
𝚪 ⊦ 𝑥 : 𝜎 pode ser escrito 𝚪 ⊦ ( 𝑥 : 𝜎 )
⊦ ⊦ let 𝑥 = 𝑒₀ em 𝑒₁ : 𝜏
é equivalente 𝚪 ⊦ (( let ( 𝑥 = 𝑒₀ ) em 𝑒₁ ): 𝜏 )
𝚪 ⊦ 𝜆𝑥.𝑒 : 𝜏 → 𝜏 ' é equivalente 𝚪 ⊦ (( 𝜆𝑥.𝑒 ): ( 𝜏 → 𝜏' ))
Então, grandes espaços que separam declarações de asserção e outras pré-condições indicam um conjunto de tais pré-condições e, finalmente, a linha horizontal que separa a premissa da conclusão traz o fim da ordem de precedência.
As regras
O que se segue aqui são interpretações em inglês das regras, cada uma seguida de uma nova correção e uma explicação.
Variável
Dado 𝑥 é um tipo de 𝜎 (sigma), um elemento de 𝚪 (Gamma),
conclua 𝚪 afirma 𝑥 é um 𝜎.
Em outras palavras, em in, sabemos que 𝑥 é do tipo 𝜎 porque 𝑥 é do tipo 𝜎 em 𝚪.
Isso é basicamente uma tautologia. Um nome de identificador é uma variável ou uma função.
Função Aplicação
Dado 𝚪 afirma 𝑒₀ é um tipo funcional e 𝚪 afirma 𝑒₁ é um 𝜏
conclui 𝚪 afirma aplicar a função 𝑒₀ a 𝑒₁ é um tipo 𝜏 '
Para reafirmar a regra, sabemos que o aplicativo de função retorna o tipo 𝜏 'porque a função tem o tipo 𝜏 → 𝜏' e obtém um argumento do tipo 𝜏.
Isso significa que, se soubermos que uma função retorna um tipo e a aplicamos a um argumento, o resultado será uma instância do tipo que sabemos que ela retorna.
Função Abstração
Dado 𝚪 e 𝑥 do tipo 𝜏 assevera 𝑒 é um tipo, 𝜏 '
concluir 𝚪 afirma uma função anônima, 𝜆 de 𝑥 expressão retornada, 𝑒 é do tipo 𝜏 → 𝜏'.
Novamente, quando vemos uma função que pega returns e retorna uma expressão 𝑒, sabemos que ela é do tipo 𝜏 → 𝜏 'porque 𝑥 (a 𝜏) afirma que 𝑒 é um 𝜏'.
Se sabemos que 𝑥 é do tipo 𝜏 e, portanto, uma expressão 𝑒 é do tipo 𝜏 ', então uma função de 𝑥 expressão retornada 𝑒 é do tipo 𝜏 → 𝜏'.
Vamos declaração variável
Dadas 𝚪 afirmações 𝑒₀, do tipo 𝜎 e 𝚪 e 𝑥, do tipo 𝜎, afirma 𝑒₁ do tipo 𝜏
concluem 𝚪 afirmações let
𝑥 = in
𝑒₀ do tipo 𝜏
Vagamente, 𝑥 está vinculado a 𝑒₀ em 𝑒₁ (a 𝜏) porque 𝑒₀ é um and e 𝑥 é um 𝜎 que afirma que 𝑒₁ é um 𝜏.
Isso significa que, se tivermos uma expressão 𝑒₀ que é 𝜎 (sendo uma variável ou uma função) e algum nome, 𝑥, também a, e uma expressão 𝑒₁ do tipo 𝜏, poderemos substituir 𝑒₀ por onde quer que ela apareça de 𝑒₁.
Instanciação
Dadas 𝚪 afirmações 𝑒 do tipo and 'e 𝜎' são um subtipo de 𝜎
concluem 𝚪 afirmações 𝑒 são do tipo 𝜎
Uma expressão, 𝑒 é do tipo pai 𝜎 porque a expressão 𝑒 é o subtipo 𝜎 'e 𝜎 é o tipo pai de 𝜎'.
Se uma instância é de um tipo que é um subtipo de outro tipo, também é uma instância desse supertipo - o tipo mais geral.
Generalização
Dado que 𝚪 afirma 𝑒 é um a e 𝛼 não é um elemento das variáveis livres de 𝚪,
conclua 𝚪 afirma erts, digite para todas as expressões de argumento 𝛼 retornando uma expressão 𝜎
Portanto, em geral, 𝑒 é digitado 𝜎 para todas as variáveis de argumento (𝛼) retornando 𝜎, porque sabemos que 𝑒 é um 𝜎 e 𝛼 não é uma variável livre.
Isso significa que podemos generalizar um programa para aceitar todos os tipos de argumentos ainda não vinculados no escopo que contém (variáveis que não são locais). Essas variáveis associadas são substituíveis.
Juntando tudo
Dadas certas suposições (como nenhuma variável livre / indefinida, um ambiente conhecido), conhecemos os tipos de:
- elementos atômicos de nossos programas (Variável),
- valores retornados por funções (Function Application),
- construções funcionais (Abstração de Função),
- deixar ligações (Let Variable Declarations),
- tipos principais de instâncias (Instanciação) e
- todas as expressões (generalização).
Conclusão
Essas regras combinadas nos permitem provar o tipo mais geral de um programa declarado, sem a necessidade de anotações de tipo.