首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >生成带有延续的Javascript代码背后的诀窍是什么?

生成带有延续的Javascript代码背后的诀窍是什么?
EN

Stack Overflow用户
提问于 2012-02-22 17:33:51
回答 1查看 1.4K关注 0票数 3

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

我不需要一个全功能的框架来模拟Javascript异步调用;我只需要它用于我想要实现的非常特定的用法。因此,上面的libs对我来说有点过头了。

有人能告诉我这些预处理器使用的基本“技巧”(或技巧)吗?有没有一些特殊的语言技巧,使得Javascript中的延续成为可能,而代价是生成一些额外的代码?欢迎任何相关的参考资料。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2012-02-22 17:47:48

它是延续传递风格的

Javascript是Lisp语言,但它的语法是C语言。

因为Javascript本质上是一种函数式语言,所以可以使用非常疯狂的技巧,比如延续传递风格。但这些技巧是令人头疼的。

总而言之,延续是关于下一步要做什么的概念--作为可以调用的东西来使用,就像函数一样。我有时还将继续视为已保存的调用帧堆栈:您可以将函数调用堆栈保存为执行状态,并在以后返回或仅“调用”此状态。

有人演示过,通过将代码转换为延续传递样式,您可以获得延续的强大功能。哇!这真的令人印象深刻:

只需一次源代码转换,呼呼一声,你就拥有了延续的力量。

现在,Javascript的问题是它的C语法。用C语言进行源代码转换比较困难。使用Lisp语法会更容易,但仍然很繁琐且容易出错。

我们很幸运,一些真正有创造力的人为我们做了艰苦的工作。这项艰苦的工作需要使用Javascript解析器,因为这种转换真正意味着什么?总而言之,它意味着重新排序操作的顺序,以便真正首先完成的操作排在第一位。

代码语言:javascript
运行
复制
f(g(a + x))

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

http://en.wikipedia.org/wiki/Continuation-passing_style中一个示例函数

代码语言:javascript
运行
复制
(define (pyth x y)
  (sqrt (+ (* x x) (* y y))))

被转换为

代码语言:javascript
运行
复制
(define (pyth& x y k)
  (*& x x (lambda (x2)
      (*& y y (lambda (y2)
               (+& x2 y2 (lambda (x2py2)
                          (sqrt& x2py2 k))))))))

这对应于Javacript

代码语言:javascript
运行
复制
function pyth(x, y) {
    return Math.sqrt(x * x + y * y);
}

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

但为了便于示例,我们假设*、+和Math.sqrt()都是web服务。这一点很重要,因为Javascript web服务调用是asynchronous.所有使用过异步调用的人都知道组合异步调用的结果会有多复杂。有了预处理库或生成器,处理异步结果会变得更容易。

所以让我们用一种不同的方式来写这个例子:

代码语言:javascript
运行
复制
function pyth(x, y) {
    return sqrt(add(mul(x, x), mul(y, y)));
}

然后,CPS转换可能如下所示:

代码语言:javascript
运行
复制
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转换后的函数永远不会返回。它们只是调用带有计算结果的continuation。这可能会导致堆栈耗尽。所有的Javascript CPS转换器都需要处理这个问题。在Scheme中,这不是必需的,因为所有的调用都是尾部调用。尾部调用不需要额外的调用框架。在Javascript中,需要一个弹床或类似的技术。不是直接调用延续,而是调用一个帮助器并将结果和延续传递给它。帮助器在无限循环中运行,总是调用和返回,从而避免堆栈耗尽。

那么,为什么CPS给了我们延续的能力呢?这是因为继续只是下一步要做的事情。如果我们总是将下一步要做的事情的概念作为附加参数k,并总是将当前表达式的结果传递给它,那么我们就在代码中实现了这个概念。然而,正如我们所看到的,这种“总是随身携带”的实现是乏味的。

这是一个高昂的代价,即使我们让do源代码预处理器来做这些繁重的工作。为什么我们要使用延续呢?抽象控制流是可能的。Seaside是一个web应用程序框架,它使用continuations来抽象出浏览器的无状态请求流。用户交互可以简明扼要地建模--人们不再考虑请求,而是考虑交互流程。这只是延续强大的众多例子中的一个。对许多人来说,这种力量似乎也很奇怪,也有些可怕。

票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/9392235

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档