javascript - 生成具有延续性的Javascript代码的诀窍是什么?

标签 javascript code-generation preprocessor continuations coroutine

我正在寻找一种向Javascript中添加非抢占式多线程的非常特定形式的方法。 Mozilla的Javascript 1.7使用yield支持本机协程,但我不希望使用特定于浏览器的解决方案。我看到了基于将带注释的Javascript代码转换为纯Javascript的延续或协程的几种实现。一些示例是StratifiedJSNarrative Javascriptjwacs

对于模拟的Javascript异步调用,我不需要功能全面的框架;我只需要将其用于我想实现的非常特定的用法。因此,上述库对我来说是一个过大的杀伤力。

有人可以指出我这种预处理器使用的基本“技巧”吗?是否有一些特殊的语言技巧可以使Java语言中的延续功能成为可能,但会产生一些额外的代码呢?欢迎任何相关引用。

最佳答案

这是延续传递样式

Javascript是Lisp,但在语法上却穿着C。

由于Javascript是一种功能语言,因此可能会有真正的疯狂技巧,例如延续传递样式。但是这些技巧令人头痛。

总结中,延续是下一步的概念-可以像函数一样作为可调用的东西使用。有时,我也将延续性视为已保存的调用框架堆栈:您可以将函数调用的堆栈保存为执行状态,然后返回或只是“调用”此状态。

有人演示了通过将代码转换为延续传递样式,您可以获得延续的力量。哇!确实令人印象深刻:

只需进行源代码转换,您就可以拥有连续的能力。

现在,Javascript的问题在于它的C语法。用C语法很难进行源代码转换。使用Lisp语法会更容易,但仍然乏味且容易出错。

我们很幸运,有一些非常有才华的人为我们辛勤工作。这项艰苦的工作需要使用Javascript解析器,因为此转换的真正含义是什么?概括而言,这意味着对操作顺序进行重新排序,以使首先真正完成的工作排在最前面。

f(g(a + x))

首先完成添加a + x,然后调用g(),然后调用f()。有三个子表达式。在CPS转换中,子表达式的结果传递给延续。这涉及创建许多内部辅助函数作为临时延续。如下所述,这可能变得复杂且乏味。

http://en.wikipedia.org/wiki/Continuation-passing_style中的一个示例函数
(define (pyth x y)
  (sqrt (+ (* x x) (* y y))))

转换为
(define (pyth& x y k)
  (*& x x (lambda (x2)
      (*& y y (lambda (y2)
               (+& x2 y2 (lambda (x2py2)
                          (sqrt& x2py2 k))))))))

这对应于Javacript
function pyth(x, y) {
    return Math.sqrt(x * x + y * y);
}

但是*,+和Math.sqrt()并不是CPS有意义的函数。

但是,为示例起见,假设*,+和Math.sqrt()是Web服务。这很重要,因为Javascript Web服务调用是异​​步。每个使用异步调用的人都知道将它们的结果组合起来会变得多么复杂。使用预处理库或生成器,可以更轻松地处理异步结果。

因此,让我们以不同的方式编写示例:
function pyth(x, y) {
    return sqrt(add(mul(x, x), mul(y, y)));
}

那么CPS转换可能如下所示:
function pyth_cps(x, y, k) {
  mul_cps(x, x, function(x2) {
    mul_cps(y, y, function(y2) {
      add_cps(x2, y2, function(x2py2) {
        sqrt_cps(x2py2, k);
      })
    })
  });
}

我们看到结果代码从内到外被撕毁,并使其变得绝对不可读。每个功能都进行了转换。它们都得到一个魔术参数k。这是延续。在javascript中,这是一个获取操作结果的函数。在调用堆栈k的深处某个地方被调用。在我们的示例中,此处未显示sqrt()的CPS转换。

另请注意,CPS转换后的函数永远不会返回。他们只将继续与计算的结果。这可能导致堆栈耗尽。所有Javascript CPS转换器都需要处理此问题。在Scheme中,这不是必需的,因为所有调用都是尾调用。尾部调用不需要其他调用框架。在Javascript中,需要蹦床或类似的技术。与其直接调用延续,不如调用一个助手并将结果和延续传递给它。辅助程序始终在无穷循环中运行,总是调用和返回,并避免了堆栈耗尽。

那么,为什么CPS赋予了我们延续的力量?那是因为继续只是下一步要做的事情。如果我们始终将下一步要执行的操作的概念作为附加参数k并始终将当前表达式的结果传递给它,那么我们已经在代码中实现了此概念。但是,正如我们所看到的,这种“总是随身携带”的实现很乏味。

即使我们让源代码预处理器来完成艰苦的工作,这也是付出的高昂代价。为什么要使用延续?可以抽象控制流。 Web应用程序框架Seaside使用延续来抽象化浏览器的无状态请求流。用户交互可以进行简洁建模-人们不再考虑请求,而是考虑交互流程。这只是延续力量的众多例子之一。对于许多人来说,这种力量似乎也很奇怪,有些令人恐惧。

关于javascript - 生成具有延续性的Javascript代码的诀窍是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9392235/

相关文章:

javascript - 如何从 Javascript 中的连续函数返回 boolean 值?

javascript - 有没有办法从 webpack 获取依赖树?

javascript - 如何使用 JavaScript 每 10 秒检查一次状态?

c# - NHibernate 代码生成

c - 为什么#pragma 被视为预处理器指令?

c - 禁止 C 预处理器在另一个中使用宏

javascript - 如何用D3画箭头(响应式)

testing - 编译器代码生成器验证

c# - Visual Studio : generating code upon xsd problems with integers

c - C 宏的可选 [] 方括号