“Uma prova é um programa; a fórmula que prova é um tipo para o programa "

37

Esse pode ser um tipo filosófico de pergunta, mas acredito que haja uma resposta objetiva.

Se você ler o artigo da Wikipedia sobre Haskell, poderá encontrar o seguinte:

A linguagem está enraizada nas observações de Haskell Curry e seus descendentes intelectuais, de que "uma prova é um programa; a fórmula que prova é um tipo para o programa"

Agora, o que estou perguntando é: isso realmente não se aplica a praticamente todas as linguagens de programação? Que recurso (ou conjunto de recursos) do Haskell o torna compatível com esta declaração? Em outras palavras, quais são as maneiras visíveis em que essa afirmação afetou o design do idioma?


fonte
4
Alguém quer explicar por que o "fechamento" vota, por favor?
11
@Grigory Javadyan: Eu não votei para fechar, mas é provavelmente porque a questão é fora do tópico para SO - perguntas filosóficas, objetivamente respondíveis ou não, geralmente não são apropriadas aqui. Nesse caso, acho que é justificável, porque a resposta tem implicações práticas profundas sobre como o Haskell é realmente usado.
2
@ Gregory: se esta pergunta era um problema real com uma solução real (em código), ela pode permanecer no SO. Votou para fechar e mudar para Programadores.
9
Só para acrescentar a isso porque estou um pouco empolgado - as respostas a essas perguntas são repletas de referências a pesquisas difíceis sobre CS e, nesse sentido, são mais "objetivas" que 90% do SO. Além disso, os critérios das variáveis ​​de seis letras (de que a solução precisa de código) são incrivelmente estreitos para uma ampla gama de questões de programação genuínas que não são subjetivas nem fora de tópico. Eu realmente odiaria ver o ressurgir debate inclusionist / deletionist no SO, mas se claramente a programação tópicos como este ter colidido, então eu me preocupo ...
SCLV
2
Eu sou ambivalente sobre onde isso acaba, principalmente porque não sou muito claro sobre que tipo de conteúdo deve ser o Programmers.SE vs. SO. Mas direi que os programadores são descritos em vários lugares como sendo "questões subjetivas", o que não é enfaticamente essa questão . Minha resposta é o mais informal e ondulada possível, e eu ainda poderia fazer o backup facilmente com referências que até mesmo os editores da Wikipedia aceitariam.
CA McCann

Respostas:

38

O conceito essencial se aplica universalmente de alguma maneira, sim, mas raramente de uma maneira útil.

Para começar, da perspectiva da teoria dos tipos, isso pressupõe que as linguagens "dinâmicas" sejam melhor consideradas como tendo um único tipo, que contém (entre outras coisas) metadados sobre a natureza do valor que o programador vê, incluindo o que essas linguagens dinâmicas chamariam um "tipo" em si (que não é a mesma coisa, conceitualmente). É provável que essas provas não sejam interessantes, portanto esse conceito é principalmente relevante para idiomas com sistemas de tipo estático.

Além disso, muitos idiomas que supostamente possuem um "sistema de tipo estático" devem ser considerados dinâmicos na prática, nesse contexto, porque permitem inspeção e conversão de tipos em tempo de execução. Em particular, isso significa qualquer idioma com suporte interno padrão para "reflexão" ou algo semelhante. C #, por exemplo.

Haskell é incomum na quantidade de informações que espera que um tipo forneça - em particular, as funções não podem depender de nenhum valor além dos especificados como argumentos. Em uma linguagem com variáveis ​​globais mutáveis, por outro lado, qualquer função pode (potencialmente) inspecionar esses valores e alterar o comportamento de acordo. Assim, uma função Haskell com o tipo A -> Bpode ser considerado como uma prova programa miniatura que Aimplica B; uma função equivalente em muitas outras línguas apenas nos diria que Ae qualquer estado global que esteja no escopo combinado implica B.

Observe que, embora o Haskell tenha suporte para coisas como reflexão e tipos dinâmicos, o uso desses recursos deve ser indicado na assinatura de tipo de uma função; da mesma forma para o uso do estado global. Nenhum dos dois está disponível por padrão.

Não são maneiras de quebrar as coisas em Haskell, bem como, por exemplo, permitindo exceções de tempo de execução, ou usando operações primitivas não-padrão fornecidos pelo compilador, mas aqueles vêm com uma forte expectativa de que eles só serão utilizadas com plena compreensão de maneiras que ganharam' t danifica o significado do código externo. Em teoria, o mesmo poderia ser dito de outras línguas, mas, na prática, com a maioria das outras línguas, é mais difícil realizar as coisas sem "trapacear" e menos desaprovado em "trapacear". E, claro, nas verdadeiras linguagens "dinâmicas", a coisa toda permanece irrelevante.

O conceito pode ser levado muito mais longe do que em Haskell também.

CA McCann
fonte
Observe que as exceções podem ser totalmente integradas a um sistema de tipos.
gardenhead
18

Você está certo de que a correspondência Curry-Howard é uma coisa muito geral. Vale a pena se familiarizar um pouco com a história: http://en.wikipedia.org/wiki/Curry-Howard_correspondence

Você notará que, conforme formulada originalmente, essa correspondência se aplicava particularmente à lógica intuicionista, por um lado, e ao cálculo lambda simplesmente digitado (STLC), por outro.

Haskell clássico - versões de 98 ou mesmo anteriores, foi muito próximo do STLC, e na maioria das vezes houve uma tradução direta e muito simples entre qualquer expressão dada em Haskell e um termo correspondente no STLC (estendido com recursão e alguns tipos primitivos). Então, isso tornou Curry-Howard muito explícito. Hoje, graças às extensões, essa tradução é um negócio um pouco mais complicado.

Então, em certo sentido, a questão é por que Haskell "desugars" no STLC de maneira tão direta. Duas coisas vem a mente:

  • Tipos. Ao contrário de Scheme, que também é um tipo de cálculo lambda açucarado (entre outras coisas), Haskell é fortemente tipado. Isso significa que não existem termos no Haskell clássico que, por definição, não podem ser termos bem digitados no STLC.
  • Pureza. Mais uma vez, diferente do Scheme, mas como o STLC, Haskell é uma linguagem pura e referencialmente transparente. Isso é muito importante. Idiomas com efeitos colaterais podem ser incorporados a idiomas sem efeitos colaterais. No entanto, fazer isso é uma transformação de todo o programa, não apenas um desapropriador local. Para ter a correspondência direta , é necessário que você comece com uma linguagem puramente funcional.

Há também uma maneira importante pela qual Haskell, como a maioria dos idiomas, falha em relação à aplicação direta da correspondência de Curry-Howard. Haskell, como uma linguagem completa, contém a possibilidade de recursão ilimitada e, portanto, de não terminação. O STLC não possui um operador de ponto de fixação, não está completo e está fortemente normalizando - o que significa que nenhuma redução de um termo no STLC falhará ao finalizar. A possibilidade de recursão significa que se pode "trapacear" Curry-Howard. Por exemplo, let x = x in xtem o tipoforall a. a- ou seja, como nunca retorna, posso fingir que me dá qualquer coisa! Como sempre podemos fazer isso em Haskell, isso significa que não podemos "acreditar" totalmente em qualquer prova correspondente a um programa Haskell, a menos que tenhamos uma prova separada de que o próprio programa está sendo encerrado.

A linhagem da programação funcional anterior a Haskell (principalmente a família ML) foi o resultado de uma pesquisa de CS focada em criar linguagens sobre as quais você poderia facilmente provar coisas (entre outras coisas), pesquisar muito ciente e decorrente do CH para começar. Por outro lado, Haskell serviu de linguagem de host e de inspiração para vários assistentes de prova em desenvolvimento, como Agda e Epigram, que estão enraizados em desenvolvimentos na teoria de tipos muito relacionados à linhagem de CH.

sclv
fonte
11
Pode ser bom enfatizar que o não-término prejudica a prova de certas maneiras que, embora obviamente catastróficas do ponto de vista lógico, preservam muitas outras propriedades. Em particular, uma função A -> B, dada a A, produzirá um Bou nada. Ele nunca produzirá ae Cqual valor do tipo Bfornece, ou se diverge, ainda depende exclusivamente do Afornecido.
@camccann - um pouco estridente, mas eu distinguiria entre inferior e "nada", o que é mais parecido Void, não? A preguiça torna cada vez mais complicado. Eu diria que uma função A -> B sempre produz um valor do tipo B, mas esse valor pode ter menos informações do que se esperaria.
Sclv
Nitpicking é divertido! Quando digo "nada", quero dizer, no nível de valor, em um contexto de realização de avaliação, enquanto o fundo realmente existe apenas como uma abstração, não como algo tangível. Uma expressão que está sendo avaliada nunca "verá" um valor inferior, apenas os termos que ela não usa (que pode ser inferior) e os termos que usa (que possuem valores não inferiores). Tentar usar o fundo "nunca acontece" em certo sentido, porque tentar fazer isso termina a avaliação de toda a expressão antes que o uso ocorra.
12

Para uma aproximação de primeira ordem, a maioria das outras linguagens (fracamente e / ou uni-tipadas) não suporta o rigoroso delineamento no nível de idioma entre um

  • proposição (ou seja, um tipo)
  • uma prova (ou seja, um programa demonstrando como podemos construir a proposição a partir de um conjunto de primitivas e / ou outras construções de ordem superior )

e a relação estrita entre os dois. Na verdade, as melhores garantias fornecidas por outros idiomas são

  • dada uma restrição limitada na entrada, juntamente com o que quer que esteja no ambiente no momento, podemos produzir um valor com uma restrição limitada. (tipos estáticos tradicionais, cf C / Java)
  • cada construção é do mesmo tipo (tipos dinâmicos, cf ruby ​​/ python)

Observe que, por tipo , nos referimos a uma proposição e, portanto, a algo que descreve muito mais informações do que apenas int ou bool . Em Haskell, existe uma cultura permeante de uma função que é afetada apenas por seus argumentos - sem exceções *.

Para ser um pouco mais rigoroso, a idéia geral é que, ao aplicar uma abordagem intuicionista rígida para (quase) todas as construções de programa (isto é, só podemos provar o que podemos construir) e limitar o conjunto de construções primitivas de tal forma maneira que nós temos

  • proposições estritas para todas as primitivas de idioma
  • um conjunto limitado de mecanismos pelos quais os primitivos podem ser combinados

As construções de Haskell tendem a prestar-se muito bem ao raciocínio sobre seu comportamento. Se pudermos construir uma prova (leia-se: function) provando que isso Aimplica B, isso tem propriedades muito úteis:

  • ele sempre mantém (desde que temos uma A, podemos construir um B)
  • essa implicação depende única no A, e nada mais.

permitindo assim raciocinar sobre invariantes locais / globais de maneira eficaz. Voltar à pergunta original; Os recursos de linguagem de Haskell que melhor facilitam essa mentalidade são:

  • Pureza / Segmentação de efeitos em construções explícitas (os efeitos são contabilizados e digitados!)
  • Digite Inferência / Verificação nos compiladores Haskell
  • A capacidade de incorporar controle e / ou fluxo de dados invariantes nas proposições / tipos que um programa está tentando provar: (com polimorfismo, famílias de tipos, GADT etc.)
  • Integridade referencial

Nenhuma delas é única para Haskell (muitas dessas idéias são incrivelmente antigas). No entanto, quando combinados com um rico conjunto de abstrações nas bibliotecas padrão (geralmente encontradas nas classes de tipos), com vários níveis de sintaxe e um compromisso estrito com a pureza no design do programa, terminamos com uma linguagem que de alguma forma consegue ser prático o suficiente para aplicações do mundo real , mas ao mesmo tempo é mais fácil argumentar sobre as línguas mais tradicionais.

Essa pergunta merece uma resposta suficientemente profunda, e eu não poderia fazer justiça nesse contexto. Eu sugiro ler mais na wikipedia / na literatura:

* NB: Estou ignorando / ignorando alguns dos aspectos mais complicados das impurezas de Haskell (exceções, não terminação etc.) que apenas complexariam o argumento.

Raeez
fonte
4

Qual recurso? O sistema de tipos (sendo estático, puro, polimórfico). Um bom ponto de partida são os "Teoremas de graça" de Wadler. Efeito perceptível no design do idioma? O tipo de E / S, tipo classes.

ja.
fonte
0

A hierarquia Kleene nos mostra que provas não são programas.

A primeira relação recursiva é:

R1( Program , Iteration )  Program halts at Iteration.
R2( Theorem , Proof ) Proof proves a Theorem.

As primeiras relações recursivamente enumeráveis ​​são:

(exists x) R1( Program , x )  Program Halts.
(exists x) R2( Theorem , x)   Theorem is provable.

Portanto, um programa é um teorema e a iteração que existe na qual o programa pára é como a prova que existe que prova o teorema.

Program = Theorem
Iteration = Proof

Quando um programa é produzido corretamente a partir de uma especificação, devemos ser capazes de provar que satisfaz a especificação e, se podemos provar que um programa satisfaz uma especificação, é a síntese correta do programa. Portanto, executamos a síntese do programa se provarmos que o programa atende à especificação. O teorema de que o programa satisfaz a especificação é o programa em que o teorema se refere ao programa que é sintetizado.

As falsas conclusões de Martin Lof nunca produziram nenhum programa de computador e é incrível que as pessoas acreditem que seja uma metodologia de síntese de programa. Nunca foram dados exemplos completos de um programa sendo sintetizado. Uma especificação como "insira um tipo e produza um programa desse tipo" não é uma função. Existem vários programas desse tipo e escolher um aleatoriamente não é uma função recursiva ou mesmo uma função. É apenas uma tentativa boba de mostrar a síntese do programa com um programa bobo que não representa um programa de computador real calculando uma função recursiva.

Charlie Volkstorf
fonte
2
como é que esta resposta a pergunta: "quais são as formas perceptíveis em que esta declaração afetaram o projeto da linguagem?"
mosquito
11
@gnat - esta resposta aborda uma suposição subjacente na pergunta original, a saber: " doesn't this really apply to pretty much all the programming languages?" Esta resposta afirma / mostra que essa suposição é inválida, portanto não faz sentido abordar o restante das perguntas que são baseadas em uma premissa falha .