É possível resolver o problema de parada se você tiver uma entrada restrita ou previsível?

18

O problema da parada não pode ser resolvido no caso geral. É possível criar regras definidas que restringem as entradas permitidas e o problema de parada pode ser resolvido para esse caso especial?

Por exemplo, parece provável que uma linguagem que não permita loops, por exemplo, seria muito fácil de dizer se o programa seria interrompido ou não.

O problema que estou tentando resolver agora é que estou tentando criar um verificador de script que verifique a validade do programa. O problema da interrupção pode ser resolvido se eu souber exatamente o que esperar dos roteiristas, o que significa entradas muito previsíveis. Se isso não puder ser resolvido exatamente, quais são algumas boas técnicas de aproximação para resolver isso?

Ken Li
fonte

Respostas:

10

A resposta intuitiva é que, se você não tem loops ilimitados e não tem recursão e não tem que ir, seus programas terminam. Isso não é bem verdade, existem outras maneiras de se infiltrar na não terminação, mas é bom o suficiente para a maioria dos casos práticos. É claro que o inverso está errado, existem linguagens com essas construções que não permitem programas que não terminam, mas usam outros tipos de restrições, como sistemas de tipos sofisticados.

Recursão

Uma restrição comum nas linguagens de script é impedir dinamicamente a recursão: se A chama B chama C chama ... chama A, o intérprete (ou verificador, no seu caso) desiste ou sinaliza um erro, mesmo que a recursão possa realmente terminar. Dois exemplos concretos:

  • O pré-processador C deixa uma macro intacta enquanto a expande. O uso mais comum é definir um wrapper em torno de uma função:

    #define f(x) (printf("calling f(%d)\n", (x)), f(x))
    f(3);
    

    Isso se expande para

    (printf("calling f(%d)\n", (3)), f(3))
    

    A recursão mútua também é tratada. Uma conseqüência é que o pré-processador C sempre termina, embora seja possível criar macros com alta complexidade de tempo de execução.

    #define f0(x) x(x)x(x)
    #define f1(x) f0(f0(x))
    #define f2(x) f1(f1(x))
    #define f3(x) f2(f2(x))
    f3(x)
    
  • Os shells Unix expandem aliases recursivamente, mas apenas até encontrarem um alias que já está sendo expandido. Novamente, o objetivo principal é definir um alias para um comando com nome semelhante.

    alias ls='ls --color'
    alias ll='ls -l'
    

Uma generalização óbvia é permitir uma profundidade de recursão de até , com talvez sendo configurável.nn

Existem técnicas mais gerais para provar que as chamadas recursivas terminam, como encontrar um número inteiro positivo que sempre diminui de uma chamada recursiva para a seguinte, mas essas são consideravelmente mais difíceis de detectar. Eles geralmente são difíceis de verificar e muito menos inferir.

rotações

Os loops são encerrados se você pode vincular o número de iterações. O critério mais comum é que, se você tiver um forloop (sem truques, isto é, aquele que realmente conta de a ), ele executa um número finito de iterações. Portanto, se o corpo do loop terminar, o próprio loop será finalizado.mn

Em particular, com loops for (além de construções de linguagem razoáveis, como condicionais), você pode escrever todas as funções recursivas primitivas e vice-versa. Você pode reconhecer funções recursivas primitivas sintaticamente (se elas forem escritas de maneira não ofuscada), porque elas não usam loop while ou goto ou recursão ou outro truque. É garantido que as funções recursivas primitivas terminam e a maioria das tarefas práticas não vai além da recursão primitiva.

Gilles 'SO- parar de ser mau'
fonte
4

Veja Terminator e AProVe . Eles tendem a confiar em heurísticas, e não tenho certeza se descrevem claramente a classe de programas para os quais trabalham. Ainda assim, eles são considerados de última geração, portanto devem ser bons pontos de partida para você.

rgrig
fonte
4

Sim, pode ser possível. Uma maneira comum de resolver esses problemas é considerar um parâmetro extra (monotônico) não computável, dependendo do código como parte da entrada. A complexidade do problema com esse parâmetro pode ser severamente reduzida.

Não podemos calcular o parâmetro, mas se você souber que as instâncias de entrada com as quais você está lidando têm valores pequenos, pode corrigi-lo para um número pequeno e usar o algoritmo.

Este e outros truques semelhantes são usados ​​em métodos formais para lidar com a indecidibilidade de interrupções e problemas semelhantes. Mas se o que você deseja decidir é complicado, é improvável que a complexidade de seus algoritmos seja melhor do que executar o algoritmo nessas instâncias.

Em relação à outra pergunta, se você restringir as entradas o suficiente, o problema de parada pode ser fácil. Por exemplo, se você souber que as entradas são algoritmos de tempo polinomial, a decisão do problema de parada para eles é trivial (já que todo algoritmo de tempo polinomial é interrompido).

Os problemas que surgem nos métodos formais geralmente são indecidíveis. Você pode verificar a literatura sobre como eles lidam com esses problemas na prática.

Kaveh
fonte
4

Não é uma resposta formalmente rígida, mas aqui está:

O problema em determinar se ele para ou pára para sempre. Fazer um loop em coleções finitas, um elemento por vez ou entre um intervalo de números, está ok. EDIT: Obviamente, isso só funcionará se a coleção ou o intervalo iterado forem proibidos de mudar (por exemplo, por imutabilidade) quando estiver sendo iterado (ou pelo menos, proibido crescer).

A recursão provavelmente não é boa, a menos que você defina uma regra artificial para torná-la finita, como permitir uma profundidade máxima da pilha ou forçar que um parâmetro não negativo diminua a cada iteração.

Gotos arbitrários são geralmente ruins. É muito provável que os gotos reversos levem a loops que podem ser infinitos.

Instruções whiles e do-whiles são um problema, porque dependem de uma condição que não garante a alteração ou não durante a execução. Uma maneira possível (mas provavelmente muito insatisfatória) de restringir isso é fornecer o número máximo de iterações possível.

Victor Stafusa
fonte
2

Você precisa fornecer uma definição de sua linguagem de script e o que você quer dizer com "esperar" dos escritores de script.

O(nω)

Existe um resultado semelhante para uma classe de programa polinomial de Aaron R. Bradley, Zohar Manna e Henny B. Sipma. Porém, no AFAIK (posso estar errado aqui), o tempo de execução é duplamente exponencial (essencialmente o tempo necessário para calcular uma base de Groebner).


fonte