生成带有延续的 Javascript 代码背后的技巧是什么?

发布于 2025-01-08 03:24:56 字数 590 浏览 3 评论 0 原文

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

我不需要一个功能齐全的框架来模拟 Javascript 异步调用;我只是需要它来实现我想要实现的非常具体的用途。因此,上述库对我来说是多余的。

有人可以向我指出此类预处理器使用的基本“技巧”吗?是否有一些特殊的语言 hack 可以在 Javascript 中实现延续,但代价是生成一些额外的代码?欢迎任何相关参考。

I am looking for a way to add to Javascript a very specific form of non-preemptive multithreading. Mozilla's Javascript 1.7 supports native coroutines using yield, but I prefer not to use a browser-specific solution. I saw that there are several implementations of continuations or coroutines, based on transforming annotated Javascript code into plain Javascript. Some examples are StratifiedJS, Narrative Javascript, and jwacs.

I don't need a fully-featured framework for simulated Javascript asynchronous calls; I just need it for a very specific usage that I would like to implement. Therefore, the above libs are an overkill for me.

Can someone point me to the basic "trick" (or tricks) that such pre-processors use? Is there some special language hack that makes continuations possible in Javascript, at the cost of generating some extra code? Any relevant reference is welcomed.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

方觉久 2025-01-15 03:24:56

这是连续传球风格

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 并始终将当前表达式的结果传递给它,那么我们就在代码中实现了这个概念。然而,正如我们所看到的,这种“总是随身携带”的实现起来很乏味。

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

It's continuation passing style.

Javascript is Lisp but wears as syntax the clothes of C.

Because Javascript is a functional language at its core, really crazy tricks are possible, like continuation passing style. But these tricks are headache-inducing.

In a summary, a continuation is the concept of what to do next -- made available as something which you can invoke, exactly like a function. I also sometimes view the continuation as a saved stack of call frames: You can save a stack of function calls as an execution state and return to or just "invoke" this state later.

Someone demonstrated that by transforming code into continuation passing style you can get the power of continuations. Wow! It is really impressive:

Just a source code transformation and whooosh you have the power of continuations.

Now, the problem with Javascript is its C syntax. It is difficult to do the source code transform with C syntax. It would be easier with Lisp syntax but still tedious and error-prone.

We are lucky that some really ingenious people did the hard work for us. This hard work entails the use of a Javascript parser, because what does this transform really mean? In a summary it means reordering the sequence of the operation such that what is really done first comes first.

f(g(a + x))

The addition a + x is done first, then the function call g() then f(). There are three sub-expressions. In the CPS transform the result of the sub-expressions are passed to a continuation. This involves the creation of many inner helper functions as temporary continuations. This can get complicated and tedious, as we will see below.

In http://en.wikipedia.org/wiki/Continuation-passing_style an example function

(define (pyth x y)
  (sqrt (+ (* x x) (* y y))))

is converted to

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

This corresponds to Javacript

function pyth(x, y) {
    return Math.sqrt(x * x + y * y);
}

but *, + and Math.sqrt() are not functions where a CPS would make sense.

But let assume for the sake of the example that *, + and Math.sqrt() are web services. This is important because Javascript web service invocations are asynchronous. Everybody who has worked with asynchronous invocations knows how complicated it can get to combine the results of them. With a preprocessing library or generators it gets easier to cope with asynchronous results.

So let's write the example in a different way:

function pyth(x, y) {
    return sqrt(add(mul(x, x), mul(y, y)));
}

then a CPS transform could look like this:

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);
      })
    })
  });
}

We see that resulting code is torn inside-out and made positively unreadable. Each function is transformed. They all get a magic parameter k. That's the continuation. In javascript it is a function which gets the result of the operation. Somewhere deep down in the call stack k is invoked. In our example, in the CPS transform of sqrt() not shown here.

Also note that CPS transformed functions never return. They just call the continuation with the result of the calculation. This can lead to a stack exhaustion. All Javascript CPS transformers need to handle this. In Scheme this is not neccessary because all invocations are tail calls. A tail call doesn't need an additional invocation frame. In Javascript a trampoline or a similar technique is needed. Instead of invoking the continuation directly, invoke a helper and pass the result and the continuation to it. The helper runs in an endless loop always invoking and returning and avoids the stack exhaustion.

So, and why does this CPS give us the power of continuations? That is because a continuation is just something to be done next. If we always carry around this concept of something to be done next as an additional parameter k and always pass the result of the current expression to it, then we have realized this concept in code. However, as we have seen, this «always carrying around» is tedious to realize.

It's a steep price to pay, even if we let do source code preprocessor do the hard work. Why should we use continuations? It is possible to abstract control flow. Seaside, a web application framework, uses continuations to abstract away the browser's stateless request flow. User interaction can be modeled concisely - one does not think in requests anymore but in interaction flows. This is just one of the many examples of the power of the continuations. This power seems also odd and somewhat scary to many people.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文