javascript – 程序化非尾递归消除

我在 JavaScript中制作玩具Lisp解释器. JS没有尾递归消除(TRE),所以我在JS(伪代码)中使用while循环实现了TRE:
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

所以我很高兴,像下面这样的尾递归函数不会耗尽堆栈:

(define +
  (lambda (n m)
    (cond ((zero? n) m)
          (else (+ (sub1 n) (add1 m))))))

(+ 10000 1) ;=> 10001

但是,没有尾递归的函数会耗尽JS堆栈(因为JS代码在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

我如何处理非尾递归函数?有什么选择?什么是好资源?我已经阅读了一些关于trampolining,stack externalizing和continuation-passing风格的内容,但我能找到的是如何用这些样式编写代码,而不是如何使用这些技术来编写解释器.

解决方法

如果您能够将呼叫帧信息保存在其他位置,则可以随时将呼叫转换为跳转.这就是“堆叠外部化”所指的内容.

对于您的解释器,您的调用帧数据需要保持非尾调用的延续(它本身可以包含其他引用,例如它需要访问的任何变量).每个活动的非尾部呼叫需要一个呼叫帧.

当然,所有这些都是堆空间的交易堆栈空间.最后,你并没有真正以这种方式保存任何内存.

相关文章

前言 做过web项目开发的人对layer弹层组件肯定不陌生,作为l...
前言 前端表单校验是过滤无效数据、假数据、有毒数据的第一步...
前言 图片上传是web项目常见的需求,我基于之前的博客的代码...
前言 导出Excel文件这个功能,通常都是在后端实现返回前端一...
前言 众所周知,js是单线程的,从上往下,从左往右依次执行,...
前言 项目开发中,我们可能会碰到这样的需求:select标签,禁...