Linguagens de programação comuns são muito firmemente concluídas em Turing, se você tiver uma visão idealizada de onde os programas podem usar uma grande quantidade arbitrária de memória. Normalmente, não há um único recurso que você possa isolar sem o qual o idioma não seria completo em Turing.
Intuitivamente falando, a integridade de Turing requer duas coisas:
- Uma maneira de os programas manipularem uma quantidade de memória que não depende apenas do tamanho da entrada. Qualquer forma de criação do objeto que pode construir objetos maiores de objetos menores é adequado: objetos, estruturas, pares, listas,
malloc
, new
, etc. Mesmo alocação baseada em pilha é suficiente, desde que haja uma forma de objetos de acesso a qualquer distância do topo da pilha (apenas com alocação baseada em pilha e apenas acesso ao topo da pilha, você obtém apenas um autômato de empilhamento). Muitas formas mais fracas de manipulação de dados também são adequadas: por exemplo, em uma linguagem como Lisp ou Python, onde os números inteiros não têm tamanho limitado, as operações inteiras básicas por si só são suficientes para a integridade de Turing.
- Uma maneira de os programas continuarem em execução ou pararem com base em alguma parte dos dados. Enquanto loops, recursão mais alguma forma de condicional (por exemplo,
if
ou case
/ switch
) e goto
mais alguma forma de condicional são as três formas mais comuns, mas existem outras.
Penso que a melhor maneira de entender o que torna uma linguagem completa em Turing é observar exemplos de linguagens poderosas, mas não completas em Turing. Vou dar dois exemplos que ilustram as duas famílias principais desses idiomas (exceto os que operam na memória limitada).
Considere uma linguagem imperativa com operações e matrizes inteiras, mas apenas para loops ( for i = 1 to n
onde i
não pode ser modificado durante o loop), não para loops arbitrários (while) e sem recursão. BlooP e versões anteriores do FORTRAN são exemplos. Nesse idioma, você só pode expressar funções recursivas primitivas - funções em que a quantidade de computação é limitada no tamanho da entrada. O idioma não está completo em Turing - não pode expressar funções recursivas arbitrárias - devido ao limite no tempo de computação.
Uma linguagem funcional pode ser tornada não completa de Turing, restringindo a recursão por meio de um sistema de tipos que restringe todas as funções a serem encerradas. Se o sistema de tipos for decidível (ou seja, se for decidível se o programa está bem digitado), esse idioma não poderá ser concluído por Turing (porque o problema de parada é indecidível). Idiomas usados para a prova de teoremas, como Coq e Agda , são desse tipo.
Veja também O que Idris não pode fazer renunciando à totalidade de Turing?
Em uma nota lateral, os provadores de teoremas exigem que o usuário insira provas de término (ou seja, eles não os encontram). Assim, você poderia fazer um teste de teoremas com um sistema de tipos indecidíveis: se você receber uma prova correta, estará feliz, caso contrário, desistirá após um tempo. Mas não seria fácil de usar. A maioria dos provadores de teoremas tem um sistema de tipos decidível: você digita um termo e ele é aceito ou você recebe um erro de tipo de volta. O que os provadores de teoremas não têm é uma inferência de tipo decidível : se você digitar um termo com informações de tipo parcial e for rejeitado, não saberá se é porque o termo não pode ser digitado ou porque o mecanismo de inferência não era suficientemente inteligente.