Que parte de Hindley-Milner você não entende?


851

Eu juro que costumava haver uma T-shirt para a venda caracteriza as palavras imortais:


Que parte de

Hindley-Milner

você não entende?


No meu caso, a resposta seria ... tudo!

Em particular, muitas vezes vejo notações como essa nos artigos de Haskell, mas não tenho idéia do que isso significa. Não tenho idéia de que ramo da matemática deveria ser.

Eu reconheço as letras do alfabeto grego, é claro, e símbolos como "∉" (o que geralmente significa que algo não é um elemento de um conjunto).

Por outro lado, nunca vi "⊢" antes (a Wikipedia afirma que pode significar "partição" ). Também não estou familiarizado com o uso do vinculo aqui. (Geralmente, denota uma fração, mas não parece ser o caso aqui.)

Se alguém pudesse pelo menos me dizer por onde começar a procurar entender o que significa esse mar de símbolos, isso seria útil.


8
Se você está procurando uma boa explicação do algoritmo, o melhor que eu encontrei até agora está no capítulo 30 das Linguagens de programação: aplicativo e interpretação de Shriram Krishnamurthi (licenciado por CC!).
precisa saber é o seguinte

2
@laslowh Obrigado! Eu estou lendo. Versão mais recente: cs.brown.edu/courses/cs173/2012/book/book.pdf
SnowOnion

Respostas:


652
  • A barra horizontal significa que "[acima] implica [abaixo]".
  • Se houver várias expressões em [acima], considere- as juntas; todos os [acima] devem ser verdadeiros para garantir o [abaixo].
  • :meios tem tipo
  • meio é em . (Da mesma forma, significa "não está dentro".)
  • Γé geralmente usado para se referir a um ambiente ou contexto; nesse caso, pode ser considerado como um conjunto de anotações de tipo, emparelhando um identificador com seu tipo. Portanto, x : σ ∈ Γsignifica que o ambiente Γinclui o fato de que xtem tipo σ.
  • pode ser lido como prova ou determina. Γ ⊢ x : σsignifica que o ambiente Γdetermina que xtem tipo σ.
  • ,é uma maneira de incluir suposições adicionais específicas em um ambiente Γ.
    Portanto, Γ, x : τ ⊢ e : τ'significa que o ambiente Γ, com a suposição adicional de substituição que xpossui tipoτ , prova que epossui tipo τ'.

Conforme solicitado: precedência do operador, do mais alto para o mais baixo:

  • Operadores infix e mixfix específicos do idioma, como λ x . e, ∀ α . σ, e τ → τ', let x = e0 in e1, e espaços em branco para aplicação de função.
  • :
  • e
  • , (associativo à esquerda)
  • espaço em branco que separa várias proposições (associativas)
  • a barra horizontal

19
Quais são as regras de precedência dos operadores?
Randomblue

:e são muito semelhantes, pois significam que uma coisa está contida em outra - um conjunto contém elementos e um tipo contém valores, em certo sentido. A diferença crucial é que isso x ∈ Ssignifica que um conjunto Scontém literalmente um elemento x, enquanto Γ ⊢ x : Tque isso xpode ser deduzido para habitar o tipo Tno contexto Γ. Considerando isso, a regra Var diz: »Se x está literalmente contido no contexto, pode (trivialmente) ser deduzido dele«.
David

@Randomblue I explicitada a precedência de símbolos adicionando parênteses em todos os lugares, por exemplo (Γ,(x:τ))⊢(x:σ), ver overleaf.com/read/ddmnkzjtnqbd#/61990222
SnowOnion

327

Essa sintaxe, embora possa parecer complicada, é realmente bastante simples. A idéia básica vem da lógica formal: toda a expressão é uma implicação, com a metade superior sendo as suposições e a metade inferior sendo o resultado. Ou seja, se você souber que as expressões principais são verdadeiras, pode concluir que as expressões inferiores também são verdadeiras.

Símbolos

Outra coisa a ter em mente é que algumas letras têm significados tradicionais; particularmente, Γ representa o "contexto" em que você está - ou seja, quais são os tipos de outras coisas que você já viu. Então, algo como Γ ⊢ ..."significa a expressão ...quando você conhece os tipos de cada expressão Γ.

O símbolo significa essencialmente que você pode provar algo. O mesmo Γ ⊢ ...ocorre com uma declaração dizendo "Eu posso provar ...em um contexto Γ. Essas declarações também são chamadas de julgamentos de tipo.

Outra coisa a ter em mente: em matemática, assim como ML e Scala, x : σsignifica que xtem tipo σ. Você pode lê-lo como o de Haskell x :: σ.

O que cada regra significa

Portanto, sabendo disso, a primeira expressão se torna fácil de entender: se sabemos que x : σ ∈ Γ(isto é, xtem algum tipo σem algum contexto Γ), então sabemos que Γ ⊢ x : σ(isto é, em Γ, xtem tipo σ). Realmente, isso não está dizendo nada super interessante; apenas mostra como usar seu contexto.

As outras regras também são simples. Por exemplo, pegue [App]. Essa regra possui duas condições: e₀é uma função de algum tipo τpara outro tipo τ'e e₁é um valor do tipo τ. Agora você sabe que tipo obterá aplicando-se e₀a e₁! Espero que isso não seja uma surpresa :).

A próxima regra tem mais uma nova sintaxe. Particularmente, Γ, x : τsignifica apenas o contexto composto Γe o julgamento x : τ. Portanto, se sabemos que a variável xtem um tipo de τe a expressão etem um tipo τ', também sabemos o tipo de uma função que recebe xe retorna e. Isso nos diz o que fazer se descobrimos que tipo de função e qual tipo ela retorna, portanto também não deve surpreender.

O próximo diz apenas como lidar com letinstruções. Se você sabe que alguma expressão e₁tem um tipo τ, contanto que xtenha um tipo σ, uma letexpressão que se liga localmente xa um valor de tipo σfará com que e₁tenha um tipo τ. Realmente, isso apenas diz que uma instrução let permite essencialmente expandir o contexto com uma nova ligação - que é exatamente o que letfaz!

A [Inst]regra lida com a sub-digitação. Ele diz que, se você tem um valor de tipo σ'e é um subtipo de σ( representa uma relação de ordenação parcial), essa expressão também é do tipo σ.

A regra final lida com tipos de generalização. Um rápido aparte: uma variável livre é uma variável que não é introduzida por uma instrução let ou lambda dentro de alguma expressão; essa expressão agora depende do valor da variável livre em seu contexto. A regra está dizendo que, se houver alguma variável αque não seja "livre" em nada no seu contexto, é seguro dizer que qualquer expressão cujo tipo você conhece e : σterá esse tipo para qualquer valor de α.

Como usar as regras

Então, agora que você entende os símbolos, o que você faz com essas regras? Bem, você pode usar essas regras para descobrir o tipo de vários valores. Para fazer isso, observe sua expressão (digamos f x y) e encontre uma regra que tenha uma conclusão (a parte inferior) que corresponda à sua afirmação. Vamos chamar o que você está tentando encontrar seu "objetivo". Neste caso, você iria olhar para a regra que termina em e₀ e₁. Quando você encontrar isso, agora precisará encontrar regras que comprovem tudo acima da linha desta regra. Geralmente, essas coisas correspondem aos tipos de sub-expressões; portanto, você está essencialmente recorrendo a partes da expressão. Você apenas faz isso até terminar sua árvore de provas, o que fornece uma prova do tipo de sua expressão.

Portanto, todas essas regras fazem é especificar exatamente - e nos detalhes matematicamente usuais habituais: P - como descobrir os tipos de expressões.

Agora, isso deve parecer familiar se você já usou o Prolog - você está essencialmente computando a árvore de provas como um intérprete humano do Prolog. Há uma razão para o Prolog ser chamado de "programação lógica"! Isso também é importante, pois a primeira maneira em que fui apresentado ao algoritmo de inferência HM foi implementando-o no Prolog. Isso é surpreendentemente simples e deixa claro o que está acontecendo. Você certamente deveria tentar.

Nota: Provavelmente cometi alguns erros nesta explicação e adoraria que alguém as apontasse. Eu estarei cobrindo isso na sala de aula em algumas semanas, então ficarei mais confiante: P.


5
\ alpha é uma variável do tipo não livre, não uma variável usual. Portanto, para explicar a regra de generalização, muito mais deve ser explicado.
nponeccop 22/09/12

2
@nponeccop: Hmm, bom ponto. Na verdade, eu nunca vi essa regra específica antes. Você poderia me ajudar a explicar isso corretamente?
Tikhon Jelvis 22/09/12

8
@TikhonJelvis: É realmente muito simples, que lhe permite generalizar (assumindo Γ = {x : τ}) λy.x : σ → τpara ∀ σ. σ → τ, mas não para ∀ τ. σ → τ, porque τé variável livre em Γ. O artigo da Wikipedia sobre HM explica muito bem.
Vitus

7
Acredito que a parte da resposta relacionada [Inst]seja um pouco imprecisa. Este é apenas o meu entendimento até agora, mas os sigmas nas regras [Inst]e [Gen]não se referem a tipos, mas a esquemas de tipos . Portanto, o operador é um pedido parcial, não relacionado à sub-digitação, como a conhecemos nos idiomas OO. Está relacionado a valores polimórficos como id = λx. x. A sintaxe completa para essa função seria id = ∀x. λx. x. Agora, obviamente, podemos ter um id2 = ∀xy. λx. x, onde ynão é usado. Então id2 ⊑ id, é o que [Inst]diz a regra.
19615 Ionuț Stan Stan

71

se alguém pudesse pelo menos me dizer por onde começar a procurar entender o que esse mar de símbolos significa

Veja " Fundamentos práticos de linguagens de programação " . , Capítulos 2 e 3, sobre o estilo da lógica por meio de julgamentos e derivações. O livro inteiro está disponível na Amazon.

Capítulo 2

Definições indutivas

Definições indutivas são uma ferramenta indispensável no estudo de linguagens de programação. Neste capítulo, desenvolveremos a estrutura básica das definições indutivas e daremos alguns exemplos de seu uso. Uma definição indutiva consiste em um conjunto de regras para derivar julgamentos , ou asserções , de várias formas. Julgamentos são declarações sobre um ou mais objetos sintáticos de um tipo especificado. As regras especificam condições necessárias e suficientes para a validade de um julgamento e, portanto, determinam completamente seu significado.

2.1 Julgamentos

Começamos com a noção de julgamento ou afirmação sobre um objeto sintático. Faremos uso de muitas formas de julgamento, incluindo exemplos como estes:

  • n nat - n é um número natural
  • n = n1 + n2 - n é a soma de n1 e n2
  • τ type - τ é um tipo
  • e : τ - expressão e tem o tipo τ
  • ev - expressão e tem valor v

Um julgamento afirma que um ou mais objetos sintáticos têm uma propriedade ou mantêm alguma relação um com o outro. A propriedade ou relação em si é chamada de forma de julgamento , e o julgamento de que um objeto ou objetos tem essa propriedade ou permanece nessa relação é considerado uma instância dessa forma de julgamento. Um formulário de julgamento também é chamado de predicado , e os objetos que constituem uma instância são seus assuntos . Escrevemos um J para o julgamento afirmando que J detém de a . Quando não é importante enfatizar o assunto do julgamento, (o texto é cortado aqui)


53

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

Diagrama Lógico VAR

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

APP Logic Diagram

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

Diagrama da lógica do ABS

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

LET Diagrama Lógico

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

Diagrama Lógico INST

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

Diagrama Lógico GEN

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.


1
um resumo tão bom Aaron!
bhurlow 22/03

48

A notação vem da dedução natural .

⊢ é chamado de catraca .

As 6 regras são muito fáceis.

Var regra é regra bastante trivial - diz que, se o tipo de identificador já está presente no seu ambiente de tipos, para inferir o tipo, você apenas o tira do ambiente como está.

AppA regra diz que, se você tiver dois identificadores e0e e1puder inferir seus tipos, poderá inferir o tipo de aplicativo e0 e1. A regra é lida assim se você souber disso e0 :: t0 -> t1e e1 :: t0(o mesmo t0!), O aplicativo será bem digitado e o tipo será t1.

Abse Letsão regras para inferir tipos de abstração lambda e entrada.

Inst A regra diz que você pode substituir um tipo por um menos geral.


4
Este é o cálculo sequencial, não a dedução natural.
Roman Cheplyaka 21/09/12

12
@RomanCheplyaka bem, a notação é a mesma. O artigo da wikipedia tem uma comparação interessante das duas técnicas: en.wikipedia.org/wiki/Natural_deduction#Sequent_calculus . O cálculo sequencial nasceu em resposta direta às falhas da dedução natural; portanto, se a pergunta é "de onde veio essa notação", então "dedução natural" é tecnicamente uma resposta mais correta.
Dan Burton

2
@RomanCheplyaka Outra consideração é que o cálculo sequencial é puramente sintático (é por isso que existem tantas regras estruturais) enquanto essa notação não é. A primeira regra assume que o contexto é um conjunto, enquanto no cálculo sequencial é uma construção sintática mais simples.
nponeccop 23/09/12

@ Cheplyaka, na verdade, não, tem algo que se parece com um "sequente", mas não é um cálculo sequencial. Haper desenvolve uma compreensão disso em seu livro de texto como um "julgamento de ordem superior". Isso realmente é dedução natural.
Philip JF

15

Há duas maneiras de pensar em e: σ. Um é "a expressão e tem o tipo σ", o outro é "o par ordenado da expressão e e o tipo σ".

Veja Γ como o conhecimento sobre os tipos de expressões, implementado como um conjunto de pares de expressão e tipo, e: σ.

A catraca ⊢ significa que, pelo conhecimento à esquerda, podemos deduzir o que está à direita.

A primeira regra [Var] pode ser lida assim:
Se nosso conhecimento Γ contiver o par e: σ, podemos deduzir de Γ que e tem o tipo σ.

A segunda regra [App] pode ser lida:
se nós de Γ podemos deduzir que e_0 tem o tipo τ → τ ', e nós de Γ podemos deduzir que e_1 tem o tipo τ, então nós de Γ podemos deduzir que e_0 e_1 tem o tipo τ '.

É comum escrever Γ, e: σ em vez de Γ e {e: σ}.

A terceira regra [Abs] pode, assim, ser lida:
se de Γ estendido com x: τ podemos deduzir que e tem o tipo τ ', então de Γ podemos deduzir que λx.e tem o tipo τ → τ'.

A quarta regra [Let] é deixada como um exercício. :-)

A quinta regra [Inst] pode ser lida:
se nós de Γ podemos deduzir que e tem o tipo σ 'e σ' é um subtipo de σ, então nós de Γ podemos deduzir que e tem o tipo σ.

A sexta e a última regra [Gen] podem ser lidas:
se nós de Γ podemos deduzir que e tem o tipo σ, e α não é uma variável de tipo livre em nenhum dos tipos em Γ, então nós de Γ podemos deduzir que e tem o tipo ∀α σ.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.