Sto realizzando un interprete Lisp giocattolo in JavaScript. JS ha alcuna eliminazione ricorsione in coda (TRE), così ho implementato TRE utilizzando ciclo while a JS (pseudocodice):Eliminazione programmatica della ricaduta senza coda
function eval (exp, env)
while true
if exp is self evaluating
return exp
else if ...
...
else if exp is a function call
procedure = eval(car(exp), env)
arguments = eval_operands(cdr(exp), env)
exp = procedure.body
env = extend_env(procedure.env, env)
continue # tail call
, quindi sono felice, e le funzioni di coda ricorsive come la seguente non a corto di pila :
(define +
(lambda (n m)
(cond ((zero? n) m)
(else (+ (sub1 n) (add1 m))))))
(+ 10000 1) ;=> 10001
Tuttavia, funzioni che non sono ricorsiva in coda, a corto di JS pila (perché il codice JS ricorre troppo su eval_operands
):
(define +
(lambda (n m)
(cond ((zero? n) m)
(else (add1 (+ (sub1 n) m))))) ; not proper tail-recursive
(+ 10000 1) ;=> JS: Maximum call stack size exceeded
Come faccio a trattare con i non-tai funzioni ricorsive? Quali sono le opzioni? Quali sono le buone risorse? Ho letto un po 'di trampolino, stack esternalizzazione e stile di passaggio continuo, ma tutto quello che ho potuto trovare è come scrivere codice in quegli stili, non come usare quelle tecniche per scrivere interpreti.
Dato che stai implementando un interprete, le prestazioni non dovrebbero essere un problema per te, quindi puoi eseguire prima una trasformazione CPS (e tutte le chiamate saranno quindi chiamate tail). Interpretare un codice CPSed è facile, basta usare liste collegate per passare continuazioni. –
Assicurati di leggere il problema FUNARG. :) –