できる気がしてきた。

僕の興味のあることを共有します。

JavaScriptのAwaitはcall/ccだということ。

Schemeの継続について調べると、なんだか「簡単」って言いつつ難しい説明をしてる記事が多いですが、実は普段から僕達は継続を使っています。 たとえば次のコードはES7とSchemeで書かれた”Hello"と表示するプログラムです。

(define (say)
  (display (call/cc (lambda (c) (c "Hello")))))
(say)
async function say() {
  console.log(await new Promise(c => {
    c("Hello");
  }));
}
say();

どちらのcも関数です。そしてcは継続と呼ばれていて次の処理を意味しています。 しかし、JavaScriptの場合はこのcresolveなどと書いて、成功した時にreturnのように扱えるものとして紹介されていますね。

ところで、残念ながら(?)JavaScriptの継続はSchemeの継続に完全な互換性を持っていません。というものSchemeの継続は変数に保存してあとから呼び出すことが可能ですが、JavaScriptのcは変数に保存することができません。より正確言うと、変数に保存することはできますが呼び出すときにエラーを吐きます。

これが「Schemeの継続は死んだスタックを黄泉の国から引きずり出す」なんて言われている所以です。

それはさておき、JavaScriptでも継続が使われていることがわかったところで、JavaScriptの継続の使い方をSchemeでもしてみましょう。例えばJavaScriptのawaitの醍醐味といえば非同期プログラミングを同期的に書きなおす機能でしょうか?

function slowFunctionA() {
  return new Promise(c=>c("Hello,"));
}

function slowFunctionB() {
  return new Promise(c=>c("World!"));
}

async function say(){
  const a = await slowFunctionA();
  const b = await slowFunctionB();
  console.log(a + b);
}
say()
(define (slowFunctionA c) (c "Hello,"))
(define (slowFunctionA c) (c "World!"))
(define (say)
  (let ((a (call/cc slowFunctionA))
        (b (call/cc slowFunctionB)))
    (display (+ a b))))

ほかにも、JavaScriptのyieldのnextも継続と捉えることができます。

function* say(){
  const w = yield "Hello,";
  console.log(w);
}

w = say()
console.log(w.next("World"));

この場合メソッドnextが呼ばれると、yieldされた値を返し、nextの引数をwへの代入操作という継続に渡しています。では、Schemeだとどう書くのでしょうか?ぜひ、継続の理解確認のために書いてみてください。

このように、Schemeの継続は継続として勉強すると難しいですが、普段から私達は便利に継続を扱っています。継続を理解する方法として僕は、意味を理解せずに使い方を覚えていって、それらを抽象化した時に継続を理解すれば良いのではと思いました。