Parece que eu encontrei uma maneira genérica de converter qualquer procedimento recursivo em recursão de cauda:
- Defina um subprocedimento auxiliar com um parâmetro "resultado" extra.
- Aplique o que seria aplicado ao valor de retorno do procedimento a esse parâmetro.
- Ligue para este procedimento auxiliar para começar. O valor inicial para o parâmetro "result" é o valor do ponto de saída do processo recursivo, para que o processo iterativo resultante comece de onde o processo recursivo começa a encolher.
Por exemplo, aqui está o procedimento recursivo original a ser convertido ( exercício 1.17 do SICP ):
(define (fast-multiply a b)
(define (double num)
(* num 2))
(define (half num)
(/ num 2))
(cond ((= b 0) 0)
((even? b) (double (fast-multiply a (half b))))
(else (+ (fast-multiply a (- b 1)) a))))
Aqui está o procedimento recursivo de cauda convertido ( exercício 1.18 do SICP ):
(define (fast-multiply a b)
(define (double n)
(* n 2))
(define (half n)
(/ n 2))
(define (multi-iter a b product)
(cond ((= b 0) product)
((even? b) (multi-iter a (half b) (double product)))
(else (multi-iter a (- b 1) (+ product a)))))
(multi-iter a b 0))
Alguém pode provar ou refutar isso?
algorithms
logic
recursion
lisp
nalzok
fonte
fonte
b
de ser uma potência de 2 mostra que a configuração inicialproduct
de 0 não está certa; mas alterá-lo para 1 não funciona quandob
é ímpar. Talvez você precise de 2 parâmetros diferentes de acumulador?Respostas:
Sua descrição do seu algoritmo é realmente muito vaga para avaliá-lo neste momento. Mas, aqui estão algumas coisas a considerar.
CPS
De fato, existe uma maneira de transformar qualquer código em um formulário que use apenas chamadas de cauda. Essa é a transformação do CPS. O CPS ( estilo de passagem de continuação ) é uma forma de expressar código, passando para cada função uma continuação. Uma continuação é uma noção abstrata que representa "o restante de uma comparação". No código expresso no formato CPS, a maneira natural de reificar uma continuação é como uma função que aceita um valor. No CPS, em vez de uma função retornar um valor, ela aplica a função que representa a continuação atual ao ser "retornado" pela função.
Por exemplo, considere a seguinte função:
Isso pode ser expresso no CPS da seguinte maneira:
É feio e geralmente lento, mas tem certas vantagens:
TCO
Parece-me que a única razão para se preocupar com recursão de cauda (ou chamadas de cauda em geral) é para fins de otimização de chamada de cauda (TCO). Então, acho que uma pergunta melhor a ser feita é "minha transformação gera código que é otimizável por chamada de cauda?".
Se considerarmos novamente o CPS, uma de suas características é que o código expresso no CPS consiste apenas em chamadas de cauda. Como tudo é uma chamada final, não precisamos salvar um ponto de retorno na pilha. Portanto, todo o código no formato CPS deve ser otimizado para chamada final, certo?
Bem, não exatamente. Você vê, embora possa parecer que eliminamos a pilha, tudo o que fizemos foi apenas mudar a maneira como a representamos. A pilha agora faz parte do fechamento, representando uma continuação. Portanto, o CPS não magicamente otimiza todo o nosso código de chamada final.
Portanto, se o CPS não pode fazer tudo TCO, existe uma transformação específica para recursão direta que pode? Não, não em geral. Algumas recursões são lineares, mas outras não. Recursões não lineares (por exemplo, árvore) devem simplesmente manter uma quantidade variável de estado em algum lugar.
fonte