【问题标题】:Flat evaluation of Lisp s-expressionsLisp s 表达式的平面求值
【发布时间】:2012-10-11 07:58:30
【问题描述】:

我正在尝试弄清楚如何实现 Lisp 评估 非递归。我的基于 C 的评估器是 Minimal Lisp 文件 l1.c。然而 那里的几个函数再次递归到 eval 中:eval、apply、 evargs、evlist 以及 Lisp Ops defineFunc、whileFunc、setqFunc、ifFunc...

我正在尝试找出一个平坦的评估。 我可以想出一些可能的方法:

  1. 转换为字节码并在VM中执行
  2. 实现一个扁平的 Forth 求值器并在 Forth 中实现 Lisp 求值,这就是 lf.f 所做的。
  3. 另一种可能是将 l1.c 中的所有递归函数加入到一个大的 switch 循环中。局部变量将连接到基于堆的结构中,对递归子函数的调用将由基于堆的返回堆栈实现。

我的问题是:是否有算法/论文/实现 以不同方式进行平面评估。我正在寻找 一个不转换为字节码的实现 类似于使用 a 的无递归“深度优先遍历” 下推堆栈。我想对原来的 s 表达式进行操作。

答案:在 c 中实现评估器时,您需要实现整个 在一个平面循环中,手动实现返回堆栈和堆栈帧,使用 goto 和 switch() 对控制流进行建模。这是一个示例:flat

【问题讨论】:

  • 一般来说,你已经回答了你的问题:有一个下推堆栈(在堆上)并运行一个while循环,以正确的顺序将要评估的东西推到该堆栈上,连同他们的相关信息,可能定义您自己的操作码来指导您如何处理弹出的实体。您是否阅读过 SICP 或“Lisp in Small Pieces”或“EOPL”或“Lisp 1.5 手册”或 McCarthy 的原始论文或“Lambda 论文”或“FUNARG 问题”论文?你决定 Lisp-1 还是 Lisp-2 了吗?环境堆栈树与浅绑定?动态作用域还是词法作用域?
  • 感谢您的评论。我不知道这种问题有一个名字:“FUNARG 问题”。感谢您的许多参考,现在我有一些指导方针。理性是好的:基于 c 的评估器中的堆栈是由 c 编译器创建的。要摆脱堆栈,您需要将 c 编译器重写为基于堆的(这不是我能做的)或手动编写控制流。也许没有人想到过,一个不使用堆栈的 c 编译器。完成后,我将发布我的基于堆的 c-evaluator 实现。评估器基于 Ian Piumarta 的 lysp.c。 Lisp-1,树,词法。
  • 太棒了!顺便说一句,Scheme 是 Lisp-1,环境树,词法。 :)
  • 这是用 c 编写的 Lisp 求值器,重写为平面循环:flat evaluator。 (整个事情是一个实验,我不打算写像Scheme这样完整的东西,它在MinimalLisp中有解释)。 “如果你不理解 Unix,你注定要重新实现它” 可以重写为:“如果你自己不(重新)实现 Unix,你就注定不理解它” ...也许这就是我写这篇文章的主要原因。
  • 顺便说一句,如果没有其他答案令人满意,您可以发布自己的答案并接受它。 :)

标签: c lisp eval s-expression


【解决方案1】:

Lisp 的一个非常重要的方面,事实上也是随后许多函数式语言的一个重要方面,就是它是组合的。这意味着表达式的含义是使用其子表达式的含义来定义的——或者换句话说,求值的定义本质上是递归的。在非函数式语言中,表达式与语句之间存在一些差异,但即使存在表达式也不受某种限制,因此递归也被纳入定义。可能语言定义的递归性不那么明显的唯一情况是汇编语言。 (尽管即使有意义的定义,当然也需要归纳。)

因此,与eval 的某些递归定义的斗争是您将失去的东西。如果您将编译为机器代码,则该代码将是递归的(并且生成的代码也将是递归的)。如果您通过 Forth 求值器进行求值,那么该求值器仍然是递归的。即使您在另一个答案中采用 CPS 建议,您最终也只会获得另一个堆栈编码。

所以最重要的是,你能得到的最好的结果是一些不直接使用机器堆栈的堆栈编码——没有本质区别,但你通常会损失性能(因为 CPU 处理 堆栈非常有效,在堆上对其进行编码会更慢)。

【讨论】:

  • 我不想改变 Lisp 的语义。只有我尝试在 C l1.c 中编程的底层评估器我不想使用 C 编译器生成的堆栈,而是基于堆。如果您丢弃 c 编译器隐式堆栈,您将获得的最大好处是:线程变得微不足道。简单比性能更重要。我能够在 Forth 中评估 s 表达式,那里的 c 编译器生成的堆栈永远不会比几个子调用大,当然你有 FORTH(基于堆的)堆栈增长。
  • 如果您的唯一目标是避免使用 C 堆栈,那么您的问题表述不正确。更具体地说,这是一个适用于您想避免它的任何情况的一般问题,Lisp 评估器只是一个例子。这也适用于实际实现这一点的方式:蹦床循环或类似的方法是在所有这些情况下执行此操作的标准方式。例如,CPS 只是一种自然的方式,可以将 C 调用堆栈换成仍然代表相同堆栈的堆分配闭包。
【解决方案2】:

看到这个话题:Continuation Passing Style

【讨论】:

  • C 中的延续样式可能是“while(op) { op=op(ctx); }” 类型的执行循环。我需要考虑如何重写(拆分)l1.c 的 eval() 和 apply() 以适应...
  • 我对上面的“继续传递样式”进行了更多思考,但它是应该评估的 Lisp 表达式的转换。我正在为用 C 编写的底层评估器寻找这样的转换。不过,谢谢。
【解决方案3】:

在 C 中实现 Lisp 求值器时,C 编译器使用堆栈 生成子程序调用的控制流。实现无堆栈 C 中的评估器,您需要使用 goto 和手动编写控制流 开关():

v *
evargs(ctx *cctx, v *l, v *env)
{
    struct v *r = 0;
    if (l) {
        r = eval(cctx, car(l),env);
        r =  mkCons(r,evargs(cctx, cdr(l),env));
    }
    return r;
}

得到

case EVARGS_0:
    S_SET(0,0);                         /* EVARGS_0: r = 0; */ 
    if (!(v=S(2)))                      /* if (l) */
        goto ret;
    RCALL_EVAL(EVARGS_1, car(v));       /* r = < eval(cctx, car(l),env); > */
    break;    
case EVARGS_1:
    S_SET(3,S(1));                      /* EVARGS_1: < r = ... > */
    RCALL_EVARGS(EVARGS_2, cdr(S(2)));  /*  r =  mkCons(r, < evargs(cctx, cdr(l),env) > ); */
    break;
case EVARGS_2:
    S_SET(0,mkCons(S(3),S(1)));         /* EVARGS_2: < r =  mkCons(r,  evargs(cctx, cdr(l),env)  ); > */
    goto ret;

【讨论】:

    【解决方案4】:

    我认为您可能缺少的关键见解是,在 lisp 解释器中,最小的函数集被实现为非递归的原语。原语的确切集合各不相同,但包括 cons、car、cdr 和 apply 的“最原始”版本,它执行这些实际功能之一,而不是其自身的解释版本。

    您应该查阅 John McCarthy 的原始论文和/或 John Allen 的 Lisp 1.5

    【讨论】:

      【解决方案5】:

      我记得有一本关于 HOPE 编程语言的书。它是一种类似于 ML 的函数式语言。它对其编译器进行了全面的概念描述,并总体上对函数式语言编译器进行了思考。它提出的一个观察结果是关于 Y-combinator 的争论。作者建议处理递归函数的一种可能方法是实现 Y-combinator 并将每个递归函数转换为可以使用 Y-combinator 进行递归的非递归函数。

      这将是您在if 特殊形式中拥有的类似技巧,它提供(通常就足够了)人们在一种语言中可能需要的各种惰性求值。以类似的方式,您可以限制所有函数递归,但引入了一个特殊的 Y 函数,允许开始递归。

      【讨论】:

      • 谢谢,我会努力理解这个的。
      猜你喜欢
      • 1970-01-01
      • 2012-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-26
      相关资源
      最近更新 更多