Javascript 程序化无尾递归消除
我正在用JavaScript制作一个玩具Lisp解释器。JS没有尾部递归消除(TRE),所以我在JS中使用while循环实现了TRE(伪代码): 所以我很高兴,像下面这样的尾部递归函数不会耗尽堆栈:Javascript 程序化无尾递归消除,javascript,recursion,lisp,scheme,Javascript,Recursion,Lisp,Scheme,我正在用JavaScript制作一个玩具Lisp解释器。JS没有尾部递归消除(TRE),所以我在JS中使用while循环实现了TRE(伪代码): 所以我很高兴,像下面这样的尾部递归函数不会耗尽堆栈: (define + (lambda (n m) (cond ((zero? n) m) (else (+ (sub1 n) (add1 m)))))) (+ 10000 1) ;=> 10001 但是,不是尾部递归的函数会耗尽JS堆栈(因为JS代码在eval
(define +
(lambda (n m)
(cond ((zero? n) m)
(else (+ (sub1 n) (add1 m))))))
(+ 10000 1) ;=> 10001
但是,不是尾部递归的函数会耗尽JS堆栈(因为JS代码在eval\u操作数上递归太多了)
:
如何处理非尾部递归函数?有哪些选择?什么是好的资源?我读过一些关于蹦床、堆栈外部化和连续传递样式的书,但我所能找到的只是如何用这些样式编写代码,而不是如何使用这些技术编写解释器。如果您能够在其他地方保存调用帧信息,则始终可以将调用转换为跳转。这就是“堆栈外部化”所指的
对于您的解释器,您的调用帧数据需要保存非尾部调用的延续(它本身可能保存进一步的引用,例如它需要访问的任何变量)。每个活动的非尾部呼叫将需要一个呼叫帧
当然,所有这些都是用堆栈空间交换堆空间。最后,这样做并不会真正节省内存。:-) 您是否可以编写一些伪代码(例如,以我的eval伪代码为基础)?由于您正在实现一个解释器,性能对您来说应该不是问题,因此您可以先执行CPS转换(然后所有调用都将是尾部调用)。解释CPSed代码很容易,只需使用链表传递延续。请务必阅读FUNARG问题。:)
(define +
(lambda (n m)
(cond ((zero? n) m)
(else (+ (sub1 n) (add1 m))))))
(+ 10000 1) ;=> 10001
(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