O que sabemos sobre programas comprovadamente corretos?


37

A complexidade cada vez maior dos programas de computador e a posição cada vez mais crucial que os computadores têm em nossa sociedade me deixam pensando por que ainda não usamos coletivamente linguagens de programação nas quais você precisa dar uma prova formal de que seu código funciona corretamente.

Acredito que o termo seja um 'compilador de certificação' (o encontrei aqui ): um compilador que compila uma linguagem de programação na qual não apenas é necessário escrever o código, mas também declarar a especificação do código e provar que o código adere ao especificação (ou use um fornecedor automatizado para fazer isso).

Ao pesquisar na Internet, encontrei apenas projetos que usam uma linguagem de programação muito simples ou projetos com falha que tentam adaptar linguagens de programação modernas. Isso me leva à minha pergunta:

Existem compiladores de certificação implementando uma linguagem de programação completa ou isso é muito difícil / teoricamente impossível?

Além disso, ainda não vi nenhuma classe de complexidade que envolva programas comprováveis, como "a classe de todas as línguas decididas por uma máquina de Turing para a qual existe uma prova de que essa máquina de Turing pára", que chamarei de ProvableR , como análogo a R , o conjunto de linguagens recursivas.

Eu posso ver vantagens de estudar uma classe de complexidade: por exemplo, para ProvabeueR o problema da parada é decidível (eu até conjecturo ProvumabeueRE definido da maneira óbvia. a maior classe de idiomas para a qual é decidível). Além disso, duvido que excluiríamos programas praticamente úteis: quem usaria um programa quando você não pode provar que ele termina?

Então, minha segunda pergunta é:

O que sabemos sobre classes de complexidade que exigem que suas linguagens contenham provadamente determinadas propriedades?


11
Um compilador pode enumerar todas as provas possíveis de comprimento i, deixando ir de 1 ao infinito, até encontrar uma prova de que o programa é interrompido. Se exigirmos que a entrada para o compilador pare comprovadamente, o compilador sempre encontrará essa prova. Como o problema da parada é indecidível, devemos concluir que existem programas que são interrompidos, mas não existe prova disso. O ponto crucial é que os programas são incapazes de descobrir se existe uma prova, e não são incapazes de encontrar uma prova se ela existe.
Alex10 Brink

3
Eu acho que você deveria separá-los. São perguntas diferentes com respostas diferentes.
Re

4
Na primeira pergunta, um artigo influente é "Processos sociais e provas de teoremas e programas", portal.acm.org/citation.cfm?id=359106
Colin McQuillan

11
A verificação do programa é indecidível. Portanto, um problema é dizer o que constitui uma boa solução. Veja cstheory.stackexchange.com/questions/4016/…
Radu GRIGore

2
@ Colin: vale a pena ler esse artigo para analisar suas provas, mas suas previsões foram falsificadas. Atualmente, temos compiladores, kernels de sistema operacional, coletores de lixo e bancos de dados comprovadamente corretos, todos eles previstos como impossíveis. O truque para evitar sua crítica era evitar a verificação humana dos detalhes de baixo nível das provas formais, mas usar a verificação mecânica das provas e usar humanos para verificar o verificador de provas. A referência de Noam à teoria dos tipos é onde está o estado da arte, o que deixa os programas imperativos em certo ponto, uma vez que a teoria dos tipos é funcional.
Neel Krishnaswami

Respostas:


28

"Compilador de certificação" geralmente significa algo ligeiramente diferente: significa que você possui um compilador que pode provar que o código de máquina que ele emite implementa corretamente implementa a semântica de alto nível. Ou seja, é uma prova de que não há erros do compilador. Os programas que as pessoas fornecem ao compilador ainda podem estar errados, mas o compilador gerará uma versão correta do código da máquina do programa errado. A maior história de sucesso nesse sentido é o compilador verificado pelo CompCert , que é um compilador para um grande subconjunto de C.

O compilador Compcert em si é um programa com uma prova de correção (feita em Coq), que garante que, se gerar código para um programa, ele estará correto (com relação à semântica operacional de assembly & C que os designers do CompCert usaram). O esforço para verificar essas coisas pela máquina é bastante grande; normalmente, a prova de correção estará entre 1x e 100x o tamanho do programa que você está verificando. Escrever programas e provas verificados pela máquina é uma nova habilidade que você precisa aprender - não é matemática ou programação, como de costume, embora dependa de ser capaz de fazer as duas coisas bem. Parece que você está começando do zero, como se fosse um programador iniciante novamente.

Porém, não existem barreiras teóricas especiais. A única coisa nesse sentido é o teorema de Blum Size, que para qualquer idioma em que todos os programas são totais, é possível encontrar um programa em um idioma recursivo geral que será pelo menos exponencialmente maior quando programado no idioma total. A maneira de entender esse resultado é que um idioma total codifica não apenas um programa, mas também uma prova de término. Assim, você pode ter programas curtos com provas de término longas. No entanto, isso realmente não importa na prática, pois apenas escreveremos programas com provas de terminação gerenciáveis.

EDIT: Dai Le pediu alguma elaboração do último ponto.

Essa é principalmente uma afirmação pragmática, baseada no fato de que, se você pode entender por que um programa funciona, é improvável que o motivo seja alguns milhões de páginas invariáveis ​​e vastas. (Os invariantes mais longos que usei têm algumas páginas e, rapaz, eles fazem os revisores resmungarem! Compreensivelmente, também, uma vez que o invariante é a razão pela qual o programa trabalha despojado de toda a narrativa que ajuda as pessoas a entendê-lo.)

Mas também existem algumas razões teóricas. Basicamente, não sabemos muitas maneiras de inventar sistematicamente programas cujas provas de correção são muito longas. O método principal é (1) pegar a lógica na qual você prova a exatidão, (2) encontrar uma propriedade que não possa ser expressa diretamente nessa lógica (provas de consistência são a fonte típica) e (3) encontrar um programa cujo prova de correção depende de uma família de consequências expressáveis ​​da propriedade inexprimível. Como (2) é inexprimível, isso significa que a prova de cada conseqüência expressável deve ser feita independentemente, o que permite aumentar o tamanho da prova de correção. Como um exemplo simples, observe que na lógica de primeira ordem com uma relação pai, você não pode expressar a relação ancestral.kk) é expressável, para cada fixo . Portanto, ao fornecer um programa que usa alguma propriedade de ancestralidade até um certo nível (digamos, 100), você pode forçar uma prova de correção no FOL para conter provas dessas propriedades centenas de vezes.k

A abordagem sofisticada sobre esse assunto é chamada de "matemática reversa" e é o estudo de quais axiomas são necessários para provar os teoremas dados. Não sei muito sobre isso, mas se você postar uma pergunta sobre sua aplicação no CS, tenho certeza de que pelo menos Timothy Chow, e provavelmente várias outras pessoas, poderão lhe dizer coisas interessantes.


11
Poderia, por favor, elaborar este ponto "só vamos escrever programas com provas de terminação gerenciáveis" um pouco mais?
Dai Le

Obrigado pela sua resposta atualizada! Sua resposta realmente abre minha perspectiva. Na verdade, eu também trabalha um pouco em "matemática reversa", mas não percebi a conexão que você mencionou. Obrigado novamente!
Dai Le

11
O ponto em sua edição está relacionado ao fato de que quase não temos candidatos a tautologias que exijam provas longas em sistemas de provas naturais (como, por exemplo, Frege). Parte da razão disso é que a única maneira de conhecermos uma tautologia é tautológica, porque tínhamos uma prova em mente, que necessariamente não demorou tanto!
Joshua Grochow

22

Eu acho que a resposta para a primeira pergunta é que geralmente é muito trabalho com as ferramentas atuais. Para entender, sugiro tentar provar a correção do Bubble Sort no Coq (ou se você preferir um pouco mais de desafio, use o Quick Sort). Não acho que seja razoável esperar que os programadores escrevam programas verificados, desde que provar que a correção de tais algoritmos básicos seja tão difícil e demorado.

Essa pergunta é semelhante a perguntar por que os matemáticos não escrevem provas formais verificáveis ​​por verificadores de provas? Escrever um programa com uma prova formal de correção significa provar um teorema matemático sobre o código escrito, e a resposta a essa pergunta também se aplica à sua pergunta.

Isso não significa que não houve casos de sucesso de programas verificados. Eu sei que existem grupos que estão provando a correção de sistemas como o hipervisor da Microsoft . Um caso relacionado é o Verified C Compiler da Microsoft . Mas, em geral, as ferramentas atuais precisam de muito desenvolvimento (incluindo seus aspectos SE e HCI) antes de se tornarem úteis para programadores gerais (e matemáticos).

Em relação ao parágrafo final da resposta de Neel sobre o crescimento do tamanho do programa para linguagens com apenas funções totais, é fácil provar ainda mais (se eu entendi direito). É razoável esperar que a sintaxe de qualquer linguagem de programação seja ce e o conjunto de funções computáveis ​​totais não seja ce; portanto, para qualquer linguagem de programação em que todos os programas sejam totais, há uma função computável total que não pode ser calculada por nenhum programa ( de qualquer tamanho) nesse idioma.


Para a segunda pergunta, eu respondi uma pergunta semelhante no blog de Scott há algum tempo. Basicamente, se a classe de complexidade tem uma boa caracterização e é representável computacionalmente (ou seja, é ce), podemos provar que alguma representação dos problemas na classe de complexidade é comprovadamente total em teorias muito fracas correspondentes à classe de complexidade. A idéia básica é que as funções comprovadamente totais da teoria contenham todas as funções e um problema que é A C 0AC0AC0-completo para a classe de complexidade, portanto, contém todos os problemas na classe de complexidade e pode provar a totalidade desses programas. A relação entre provas e teoria da complexidade é estudada na complexidade da prova; consulte o livro recente de SA Cook e P. Nguyen " Fundamentos lógicos da complexidade da prova ", se você estiver interessado. (Um rascunho de 2008 está disponível.) Portanto, a resposta básica é a de muitas classes "Provably C = C".

Isso não é verdade em geral, pois existem classes de complexidade semântica que não possuem caracterização sintática, por exemplo, funções computáveis ​​totais. Se por recursiva você quer dizer funções recursivas totais, então as duas não são iguais, e o conjunto de funções computáveis ​​que são comprovadamente totais em uma teoria é bem estudado na literatura da teoria da prova e é chamado de funções comprovadamente totais da teoria. Por exemplo: as funções comprovadamente totais de são £ 0 funções -recursive (ou equivalentemente funções no sistema de Gódel T ), as funções comprovadamente totais de P UmPAϵ0T são função no sistema de Girard F , as funções comprovadamente totais dePA2F são funções recursivas primitivas, ....IΣ1

Mas não me parece que isso signifique muito no contexto de verificação de programas, pois também existem programas que estão computando extensionalmente a mesma função, mas não podemos provar que os dois programas estão computando a mesma função, ou seja, os programas são extensionalmente iguais, mas não intencionalmente. (Isso é semelhante à Estrela da Manhã e à Estrela da noite.) Além disso, é fácil modificar um determinado programa comprovadamente total para obter um programa que a teoria é incapaz de provar sua totalidade.


Eu acho que as duas perguntas estão relacionadas. O objetivo é obter um programa verificado. Um programa verificado significa que o programa atende a uma descrição, que é uma declaração matemática. Uma maneira é escrever um programa em uma linguagem de programação e, em seguida, provar suas propriedades como satisfaz a descrição, que é a prática mais comum. Outra opção é tentar provar a afirmação matemática que descreve o problema usando meios restritos e depois extrair um programa verificado. Por exemplo, se provarmos na teoria correspondente a que, para qualquer número dado n, existe uma sequência de números primos cujo produto é igual a n , podemos extrair um PPnnPalgoritmo para fatoração da prova. (Também existem pesquisadores que tentam automatizar a primeira abordagem o máximo possível, mas verificar propriedades interessantes não triviais dos programas é computacionalmente difícil e não pode ser completamente verificado sem falsos positivos e negativos.)


3
Boa resposta! Você menciona que uma maneira é extrair programas das provas, o que pode ser feito automaticamente no Coq, por exemplo. Uma área relacionada é a mineração de provas , onde as pessoas (trabalhando normalmente em lógica matemática) tentam extrair informações de uma determinada prova. Por exemplo, em alguns casos é possível (automaticamente) encontrar uma prova intuicionista dada uma clássica.
Radu Grigore

11
@ Radu: obrigado por apontar que isso pode ser feito automaticamente no Coq. Como você mencionou, também é possível extrair algumas informações construtivas e, portanto, programas das provas clássicas do teorema, usando mineração de prova para algumas classes de fórmulas (as pessoas interessadas podem conferir os artigos de Ulrich Kuhlenbach para obter detalhes). Outro resultado possivelmente relacionado é que, se um teorema é comprovada em P A , é também provável construtivamente em H Uma pela tradução de Harvey Friedman. _Π20PAHA
Kaveh

11
Andrej Bauer tem um novo post interessante em seu blog, no qual ele comprova a interpretação Dialectica de Godel na Coq .
Kaveh

18

O que você está perguntando na primeira pergunta às vezes é chamado de "compilador de verificação" e, alguns anos atrás, Tony Hoare o oferecia como um grande desafio para a pesquisa em computação . Até certo ponto, isso já existe e está em uso ativo em ferramentas como o provador do teorema de Coq , que organiza o problema por meio da teoria dos tipos e do princípio das proposições como tipos (" Curry-Howard ").

Edição: só queria adicionar ênfase em "até certo ponto". Isso está longe de ser um problema resolvido, mas o sucesso da Coq dá esperança de que não seja um sonho.


8
Eu diria que a criação de software verificado é onde a criação de software antigo comum ocorreu em 1956. Já era óbvio que o software seria incrivelmente importante e já havia grandes histórias de sucesso. No entanto, ainda havia muitas pessoas faltando conceitos fundamentais (uma compreensão clara de quais procedimentos e variáveis ​​eram, por exemplo), e a distância da teoria à prática poderia ser tão curta quanto implementar o Lisp, programando o código em um teorema. Este é um momento INCRÍVEL para trabalhar com idiomas e verificação.
Neel Krishnaswami

12

Uma ferramenta que verifica se um programa está correto às vezes é chamada de verificador de programa. Nesse contexto, "correto" geralmente significa duas coisas: que o programa nunca produz certas saídas (pense em falha de segmentação, NullPointerException etc.) e que o programa concorda com uma especificação.

O código e a especificação podem concordar e ainda ser percebidos como incorretos. Em certo sentido, pedir aos desenvolvedores que escrevam especificações é como pedir a dois desenvolvedores que resolvam o problema. Se as duas implementações concordarem, você terá maior confiança de que elas estão OK. Em outro sentido, no entanto, as especificações são melhores que uma segunda implementação. Como a especificação não precisa ser eficiente ou mesmo executável, pode ser muito mais sucinta e, portanto, mais difícil de se errar.

Com essas advertências em mente, recomendo que você verifique o verificador do programa Spec # .


Tanto quanto eu entendo Spec # (e sua extensão Sing #), ele dá ao programador a capacidade de verificar estaticamente afirmações, mas não exige que o programador faça isso, nem fornece a capacidade de provar propriedades arbitrárias do código.
Alex10 Brink

As propriedades arbitrárias podem ser codificadas como afirmações, em princípio. Não tenho certeza do que você deseja que a ferramenta exija. Deseja que as especificações digam qual deve ser a saída para todas as entradas possíveis?
Radu Grigore

4

No caso geral, é impossível criar um algoritmo que confirme se um algoritmo é equivalente a uma especificação. Esta é uma prova informal:

Quase todas as linguagens de programação são completas em Turing. Portanto, qualquer idioma decidido por uma TM também pode ser decidido por um programa escrito nesse idioma.

Equivalence/TM é indecidible. Como conseqüência da integridade de Turing, o mesmo vale para os programas do idioma especificado. Em outras palavras, é indecidível saber se as entradas que você deseja que seu programa aceite e as que ele realmente faz são as mesmas.

Equivalence/TMNonemptiness/TMEmptiness/TMEmptiness/TMEquivalence/TMEquivalence/TMtambém é inaceitável. Portanto, você pode usar um algoritmo, independentemente de duas máquinas não serem equivalentes, mas não pode ter certeza se são equivalentes ou se não deu tempo suficiente ao seu algoritmo.

No entanto, isso é apenas para o caso geral. É possível que você decida se as especificações são equivalentes ou não ao programa, resolvendo uma versão mais relaxada do problema. Por exemplo, você pode examinar apenas várias entradas ou dizer que os dois programas são equivalentes a alguma incerteza. É disso que se trata o teste de software.

Quanto ao restante de suas perguntas:

Nota: Esta parte foi editada para esclarecimentos. Acontece que cometi o erro que estava tentando evitar, desculpe.

TrueRTrueRR

ProvableR=TrueRProvableRTrueRTrueRProvableRAϵTrueRAAAϵProvableR

Informalmente, isso pode ser resumido como: Você não sabe que um idioma é decidível até que você prove que é. Portanto, se em um sistema formal você tem o conhecimento de que um idioma é decidível, esse conhecimento também pode servir como prova disso. Portanto, você não pode ter simultaneamente o conhecimento de que um idioma é decidível e não pode ser provado, portanto, essas duas declarações são mutuamente exclusivas.

RProvableRProvableRRR

@Kaveh resume melhor: Provable sempre significa provable em algum sistema / teoria e não coincide com a verdade em geral.

O mesmo vale para qualquer outra classe de complexidade: para determinar a associação, você precisa primeiro de uma prova. É por isso que acredito que sua segunda pergunta é muito geral, pois contém não apenas a teoria da complexidade, mas também a teoria da computação, dependendo da propriedade que você deseja que a linguagem tenha.


11
RProvableRΣ30Σ10

11
Provável sempre significa provável em algum sistema / teoria e não coincide com a verdade em geral.
Kaveh

11
Vejo agora que, para que minha pergunta seja interessante, é preciso falar sobre o conjunto de máquinas de Turing interrompidas, e não sobre o conjunto de linguagens decidíveis.
Alex10 Brink

11
@ Alex Bem, você precisa de alguma maneira de falar sobre idiomas, mas há muitos. Portanto, se você quiser falar sobre idiomas conectados a algum objeto finito (como uma prova), precisará se restringir a idiomas identificáveis ​​por um objeto finito, como uma TM.
precisa saber é o seguinte

2
R

3

A seguinte monografia clássica estuda quase exatamente sua segunda pergunta:

Hartmanis, J. Computações viáveis ​​e propriedades de complexidade comprovável , Série de Conferências Regionais CBMS-NSF em Matemática Aplicada, 30. Sociedade de Matemática Industrial e Aplicada (SIAM), Filadélfia, Pa., 1978.

{L(Mi)|Ti(n)T(n) is provable in F}MiTi(n)Min

T(n)nlog(n)g(n)1FTIME[T(n)]TIME[T(n)g(n)]

T(n)FTIME[T(n)]TIME[T(n)] .

T(n)nlog(n)TIME[T(n)]={L(Mi)|F proves(j)[L(Mj)=L(Mi)Tj(n)T(n)]}

Para o espaço, no entanto, a situação é melhor controlada:

s(n)nSPACE[s(n)]=FSPACE[s(n)] .

SPACE[S(n)]=FSPACE[S(n)]S(n)ns(n)SPACE[S(n)]=SPACE[s(n)]


1

A questão deve ser colocada corretamente. Por exemplo, ninguém nunca quer saber se um programa real seria concluído com memória infinita e alguns meios de acessá-lo (talvez uma operação para mover o endereço base por algum número). O teorema de Turing é irrelevante para programar a correção em qualquer sentido concreto, e as pessoas que o citam como barreira à verificação do programa estão confundindo duas coisas bem diferentes. Quando engenheiros / programadores falam sobre a correção do programa, eles querem saber sobre propriedades finitas. Isso também é verdade para os matemáticos que estão interessados ​​em saber se algo é provável. A carta de Godel http://vyodaiken.com/2009/08/28/godels-lost-letter/ explica isso em alguns detalhes.

Nomeadamente, isso obviamente significaria que, apesar da indecidibilidade do problema de Entscheidung, o trabalho mental de um matemático sobre questões do tipo sim ou não poderia ser completamente substituído por uma máquina. Afinal, basta escolher o número natural n tão grande que, quando a máquina não produz um resultado, não faz sentido pensar mais sobre o problema.

Pode ser inviável examinar o imenso conjunto de estados de um programa em execução em um computador real e detectar estados ruins; não há razão teórica para que isso não possa ser feito. De fato, houve muito progresso nesse campo - por exemplo, consulte http://www.cs.cornell.edu/gomes/papers/SATSolvers-KR-book-draft-07.pdf (obrigado a Neil Immerman por me falando sobre isso)

Um problema diferente e mais difícil é especificar exatamente quais propriedades se deseja que um programa tenha para estar correto.

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.