BiwaSchemeでReactNativeする

というわけで、BiwaSchemeでReactNativeできるようになった。

左はMS Edgeで動作するreact-native-domで、右はWindows版のReactNative( https://github.com/Microsoft/react-native-windows )となっている。(React Nativeの方に出ている"000 000"はFPSカウンタ) ...どちらもChakraなので後でAndroidiOS(JavaScriptCore)でも試すことにする。
もっとも、まだViewとTextしか動作を確認していない https://github.com/okuoku/biwasyuni-editortest/blob/2342b33e818b7c0689208f958432b427e9c2381a/yunilib/appmain-rn.sls#L42 。通常のReactとコードを共有する上でどういう中間ライブラリを挟むのが良いのか考え中。。

"use strict"に対応する

React Nativeでは、標準のJavaScript bundlerとしてMetro( https://facebook.github.io/metro/ )を採用している。が、このMetroはJavaScriptのパースに直接Babelを使用しているため、非strictなコードを読むことができない。Babelは何もオプションを指定しないとソースコードをES6だと思ってパースすることになるが、Metro側にパーサのオプションを設定する方法がない。
通常のシチュエーションではあまり問題にはならないが、BiwaSchemeはライブラリ実装のためにstrictモードでは禁止されているwithを使用している( https://github.com/biwascheme/biwascheme/blob/2f69a3dfab57f8ec9b5c2c98702db703192bf8b7/src/library/extra_lib.js#L2 )ため、ReactNativeで直接使うことができない。
...どうしようか悩ましいところだが、今回はforkで解決することにした。つまり、withによってトップレベルにインポートすることが期待されるシンボルを全部ローカルスコープに導入してしまう( https://github.com/okuoku/biwascore/blob/d9ca63989332c758c3c7fde9b69898539aebe6bc/gen/r6rs_lib.js#L10 )ことで、withが無くても元のコードを直接実行できるようにした。
また、ついでに各拡張ライブラリはCommonJSのmoduleとして切り出している。これにより、必要なライブラリだけをアプリケーションに組込めるようになった。upstreamとのやりとりが簡単になるように、ファイル構成はそのままにしている。

未解決の問題

まだ色々と未解決の問題が残っている。まぁ本命はWebアプリ、というか実際のアプリをPWAで作るつもりなのでReactNativeは一旦置いておく。。
アセットをJS側にロードするためのプラットフォーム共通手法が無い。ReactNativeではアセットはコンポーネントレンダリングされるものという想定で、Imageのようなアセットをレンダリングするための(組込みの)コンポーネントに渡すことしかできない。今回の場合はSchemeソースコードコンポーネントではなくJavaScript側に渡したいが、ReactNativeのアセットシステムではURLしか基本的に取れない。このためHTTPサーバを前提にできるデバッグ実行時でしか動作しない -- ただしreact-native-domではそもそもブラウザで動作しているのでアセットは常にfetch() APIで取得できる。react-native-windowsの場合は、パッケージをビルドするとアセットのURLがHTTPではなく"ms-appx://"のURLになるためfetch() APIで処理させることはできない。
ロジックを外部に持っていきたい場合はTransformerとして書くことになるが、これも基本的にJavaScript言語へのコンパイルを想定しているので今回のユースケースには合わない。たとえばS式をJSONに変換するだけのTransformerを用意して実行すれば良さそうでは有るが。。
(ちなみに、本当にマジでblobをサポートする必要があるならrn-fetch-blobが有る https://github.com/joltup/rn-fetch-blob これはios/androidのために専用のプラットフォームサポートを持っている。)
ロジックをプラットフォーム毎に切り分ける方法が無い。ReactNativeはソースコードやアセットを(R6RS処理系で見られるような)拡張子ルールで切り分ける方法を採用している(hoge.windows.js を代わりに読む)。今のところyuniではプレフィックスルールを採用しているため、まるでエンディアン論争のように逆になってしまっている。yuniはそもそも対応処理系が多いのでプレフィックスルールにしないと大量のファイルを撒くことになる -- それよりは、移植層を環境毎のディレクトリに纏めた方が自然だと考えている、が、(R6RSも含め)世間的には逆というわけでちょっとどうしようか悩むところ。。
また、ReactNative自体も複雑なプラットフォーム解決ができないという問題がある(MSのプロポーサル https://github.com/facebook/metro/issues/135 )。つまり、ios/android/windowsのような1段階目の派生のみがビルドシステムではサポートされており、windowsが本来持つであろうxboxとかwpfのような派生を取り入れる余地がない。
プラットフォームの切り分けは本来:

  1. ビルド時の切り分け。React-nativeの場合最終的なパッケージはプラットフォーム間で共用されないためこちらを採用できる。
  2. 動作時の切り分け。Webで動作する場合、例えばテスト/本番とかモバイル版/デスクトップ版のような実行時の切り分けも発生する可能性がある。

のようなバリエーションが有るため仕様含めちょっと真面目に考える必要がある。。
yuniの重要なデザインチョイスはScheme界隈では比較的一般的なcond-expandを捨てたことで、ライブラリ名の差し替えのみのサポートとした。これにより環境毎のバリエーションの出方を固定することにしている。これをやり切れるかどうかはちょっとなんとも言えないが。。

BiwaSchemeでReactする


諸般の事情でMithrilではなくReactに乗り換えた。 ...単にMaterial-UI( https://material-ui.com/ )が使いたかっただけだけど、最近のReactNativeにはreact-native-domというかなりアツいプロジェクトが有り( https://github.com/vincentriemer/react-native-dom )、試してみたい気持ちがある。

ビルド済のサイトをNetlifyに置いてみた。実装内容は前回( http://d.hatena.ne.jp/mjt/20180802/p1 )と同じく、ボタンを押すとカウントアップするだけ。F12で開くコンソールにSchemeからデバッグ出力もしている。
NetlifyはGitHub pagesのようにgitリポジトリにpushすることでサイトをビルドし公開することができる。GitHub pagesより強力なのはビルドにnpmやyarnがそのまま使用できる点で、yuniのビルドも含めて(ビルド成果の中間リポジトリを用意することなく、)無料で配置できている。

そもそもBiwaSchemeでReactにインターフェースできるのか問題

BiwaSchemeのようなJavaScript言語にとって、Reactのようなここ数年のJavaScriptインフラストラクチャを前提としたプラットフォームとのアクセスは難しい問題になる。ただ、Reactはちゃんと素のJavaScriptからライブラリにアクセスする方法をドキュメントしていて:

これらを駆使すればBiwaSchemeからReactコンポーネントを定義して使うことが一応できる。
JSXが無い場合は、MithrilのようにHyperScript(HTML要素を挿入するための手続きインターフェース)を使用してコンポーネントを記述することになる。ES6クラス構文が使用できない場合は、オプションライブラリである create-react-class を使用して通常のJavaScriptオブジェクトからクラスを生成できる。

this問題

Reactのコンポーネントを記述する上で問題になったのは、BiwaSchemeはスクリプト内からthisにアクセスできないという点で、エレガントな解法が全然浮かばない。
典型的なReactのコンポーネントは、イベントハンドラ中でthis.setState()を呼び出して自身の状態を更新し、再描画をトリガする:

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

しかし、BiwaSchemeではScheme側のクロージャからJavaScript文脈のthisにアクセスすることができないため、何らかの方法でthisを参照するメカニズムを実装する必要がある。
今回はヘルパ手続き thiswrap をJavaScript側に実装し、クロージャにthisを引数として渡す方式を取ってみた。

var thiswrap = function(cb){
    return function(){
        return cb(this);
    };
};

Scheme側はsyntax-rules(yuniが実装している擬似的なもの http://d.hatena.ne.jp/mjt/20180521/p1)で適当にラップしてrenderやhandleClickといった手続きに設定する:

(define %thiswrap (yuni/js-import "thiswrap"))
(define-syntax wrap-this   ;; Yuni generic-runtime requires 
  (syntax-rules ()         ;; EVERY syntax-rules as top-level form
    ((_ this form ...)
     (js-call %thiswrap (js-closure (lambda (this) form ...))))))
- snip -
      (define (counter-object)
        (js-call 
          createReactClass
          (js-obj
            "render" (wrap-this this
                                (js-call e Button
                                         (js-obj "color" "primary"
                                                 "onClick" 
                                                 (js-ref this "handleClick"))
                                         (number->string
                                           (js-ref (js-ref this "state")
                                                   "count"))))

            "handleClick" (wrap-this this
                                     (count++)
                                     (PCK 'COUNT count)
                                     (js-invoke this "setState" theCounter))
            "getInitialState" (js-closure (lambda () theCounter)))))

もうちょっとクールに実装できるような気がする。。

BiwaSchemeでWebアプリを作る の2


とりあえずMithrilでイベントをScheme側に渡し、値を更新してビューをアップデートするところまで実装できた。 https://github.com/okuoku/biwasyuni-editortest/tree/175a298e14ee2e530cbb3ff197573ba8f4c92f23
リポジトリにはyuniとbiwasyuniをsubmoduleとして入れてあるので、クローンしてsubmoduleをinit、updateし、

cd biwasyuni && yarn
yarn
yarn start

でポート8080にexpressが上がるようにしている。
ONCLICKのログはScheme側から出力されている。

(define (counter-object m) ;; m はmithril.jsのHyperScriptオブジェクト
  (define counter 0)
  (define (onclick e) ;; onclickイベントハンドラ
    (PCK 'ONCLICK counter e)
    (set! counter (+ counter 1)))
  (let ((handler (js-closure onclick)))
   (js-obj "view"
           (js-closure (lambda ()  ;; m("div", {onclick: function(...)}, counter);
                         (js-call m "div.alert.alert-primary[role=alert]" 
                                  (js-obj "onclick" handler)
                                  (number->string counter)))))))

ビルドシステム

ParcelのCLIを使うのは諦め、Parcelのライブラリを使ってビルドする方式を取った。Parcelのビルドシステムはexpress互換のmiddlewareとして使用できるため、Parcelがビルドするファイル以外にSchemeソースコードをserveする都合。
Scheme側は簡易的なyuniのブートストラップを実装している。BiwaScheme自体はR6RSライブラリをサポートしていないためライブラリシステムとしてyuniを使うが、yuniのリポジトリをどうやってビルドするかが課題になる。これを機に、yuniはビルド不要で使えるように、つまり、チェックアウトしたままの状態で使えるように変更していくことにした。
現状、yuniは使用するためにbootstrap schemeいづれか(Chez、Racket、SagittariusGauche または Chibi-scheme)を使用してブートストラップを行う必要が有るが、これを不要にした方が取り回しが良いと考えられる。今回のケースだと、本来bootstrap schemeでないBiwaSchemeだけの状態でもyuniを使うことができる。

アプリケーションの記述とロード

アプリケーションはyuniのR6RS-lite形式で記述する。アプリケーションのロードは普通にHTML5のfetch APIを使用している。今のところビルドシステムにファイルの結合やminifyの類は実装していないので、ファイルは1つ1つダウンロードされる。

動的なライブラリのロードは未対応で、今のところライブラリファイルは事前にloadしておく必要がある。これはBiwaSchemeがexpand中にPauseできないという制約があるため( https://github.com/okuoku/yuni/issues/109 )。ビルドシステムは事前にどのファイルをloadすべきかを把握できるので、アプリケーションを実装する上ではそんなに障害にはならない。。はず。
今のところMithrilのAPIを直接使用しているが、当然SXMLか何かでwrapしたAPIを出すべきで、どうすれば良いのかは考え中。

展望

重要なポイントは、yuniで書いたアプリケーションを動的なホスティングなしでWebアプリにできる点な気がしている。つまり、ビルド済のyuniのランタイムと今回作成したWeb版biwasyuniを適当にGitHub pagesかどこかに入れておけば、(原理的には、)GitHubリポジトリ上に配置したyuniアプリを直接起動できることになる。
...別に今クラウドに払っている金額を考えれば(営業のために)動的にアプリをビルドして配信するようなサーバを作ることもコスト的には大したことはないが、組織内での使用などインターネット上にあっても仕方ないケースもある。
当然、見ず知らずのWebアプリを自分のドメインで動かすというのは本質的にXSS脆弱性なので、何らかの対策を入れる必要は有る。例えば、Scheme側からのJavaScript evalを禁止するとか、DOMは信頼したライブラリにしか見せないといった対策は必要だろう。

"アプリケーションのビルド"をSDKとして抽象化できるのか問題

追記: Racket 7は--embed-dllsで完全にstandaloneな.exeを生成できるようになった https://blog.racket-lang.org/2018/07/racket-v7-0.html
実は意外に多くのScheme処理系が単体アプリケーションのビルドを機能としてサポートしている。
Gaucheは最新リリースの0.9.6でbuild-standaloneユーティリティ( http://practical-scheme.net/gauche/man/gauche-refe/Building-standalone-executables.html#Building-standalone-executables )を提供し、スクリプトとライブラリを文字列の形で取り込んだexecutableを作ることができる。ツールは(まだ実験的な)precomp( https://github.com/shirok/Gauche/blob/master/doc/HOWTO-precompile.txt )を活用しない。build-standaloneはSchemeソースコードをC文字列に変換し、簡単なmainスタブとともにコンパイルすることでアプリケーションをビルドする。コンパイラgauche-config --ccのものがそのまま使用される。
ChezSchemeはmake-boot-file手続きでbootファイルを作り、それとバンドルされているstatic library形式の処理系を組み合わせて単体アプリに仕立てることができる。解説( http://cisco.github.io/ChezScheme/csug9.4/use.html#./use:h8 )は商用版の名残りでpetit chez schemeを使っているが、同じことはコンパイラ入りの完全版でも可能なはず。これと言ったフロントエンドの類いは用意されていない、つまり、ユーザは自分でCコードを書く必要がある
Guileは元々Extension languageとして作られたので当然ライブラリ(libguile)を提供し、ドキュメントも存在する場合はC APIScheme APIを常に併記している。が、逆、つまりSchemeで書かれたアプリケーションを配布するためにアーカイブする仕組みは備えていない。一応バイトコードコンパイラを直接呼ぶことはできる( https://www.gnu.org/software/guile/manual/html_node/Compilation.html )が、バイトコードを直接ロードする良い方法は無い。...じゃぁ相当量のコードがGuileで実装されているLilypondのようなアプリケーションはどうしてんのかというと、システムにインストールされているlibguileに直接依存する形を取っている。
RacketはたぶんScheme処理系では最も考察が進んでいて、パッケージングツールであるracoがライブラリを含め単体実行ファイルを生成するraco distribute( https://docs.racket-lang.org/raco/exe-dist.html )を持っている。実際の実行ファイルを生成するraco exeコマンド( https://docs.racket-lang.org/raco/exe.html )はテンプレートになる.exeやmacOSアプリケーションのアイコンの差し替えまでサポートしている(UNIXでは単にテンプレートとなるexecutableのコピーになる)。更に、静的ライブラリも同時に提供し、アプリケーションへの組込みも可能になっている( https://docs.racket-lang.org/inside/embedding.html )。

最小公約数としての静的ライブラリ提供

というわけで、Gauche、ChezScheme、Racketは静的ライブラリとして処理系を配布しており、バイトコード化した(または、C文字列化した)プログラムを起動するための簡便なインターフェースを持っていると言える。なので、yuniのSDKとして何か提供するとするならば、

  1. 各処理系向けの簡単なmain()関数
  2. 各処理系でのバイトコードコンパイラやライブラリパッケージャを呼ぶCMakeモジュール

端的に言うとChezSchemeの方式が基準になる。GacuheはCコードの生成、Racketはカスタマイズされた.exeの生成までサービスしているが、アプリケーションに追加の静的リンクライブラリをリンクしたいケース等を考えると、C側のmain()の準備とアプリケーションのリンクは自前でやってしまった方が融通が効くような気がしている。
... 処理系を静的リンクライブラリで配るのか動的リンクライブラリで配るのかはちょっと悩ましい問題で、どちらもpros/consが有る。WindowsmacOSのように、実行ファイルがPIC(位置独立コード)であることが前提のプラットフォームであれば、静的リンクライブラリで配ってしまって利用者に委ねる方式も取れなくはないが。。特にWindowsの場合、処理系をビルドするのに使用したC言語ランタイムがMinGWなのかMSなのかという問題まであるため、動的リンクライブラリのメリットもそれなりに有ると言える。
yuniが他と比べて特殊なのは、yuniを使うアプリは殆どがネイティブコードを含んでいるという事実で、どうしてもアプリケーションのビルドには追加のビルドツールの存在を想定することになる。このため、ネイティブコード側の処理をScheme処理系に任せたいモチベーションがあまり無い。
Guileのような処理系や、Gambit、ChickenのようなSchemeコンパイラでも同じような方針で単体executableの生成は実装できる。ただGuileのような方式、つまりシステムに処理系をインストールしなければならない方式では、アプリケーションを単体で配布することができない - が、そもそもGuileはWindowsでよく動作しないのであまり大きな問題では無いはず。

hashtablesとserializeライブラリ

久々にyuniに新しいライブラリを足すことにした。どちらも他所でライブラリにしていたけどyuni本体に有った方が何かと便利なんじゃないかということで移動。

hashtables

yuniは基本的に"R7RS smallにあるものはR7RS、そうでなくてR6RSにあるものはR6RS"という方針で選択している。というわけでhashtablesはR6RSベース。
また、R6RSのものを更にサブセットしている:

  • ハッシュ関数は無し。
  • インスペクションは無し。つまり、ハッシュ関数やequality手続きを既存のハッシュテーブルから取り出すことはできない。
  • キーの型限定hashtablesを提供する。integer / string / symbol の3種。

この限定はScheme処理系を既存のインタプリタ言語で実装するケースを想定していて、integerやstringに関しては、それぞれのホスト言語ネイティブのハッシュテーブルを活用できる可能性があることから。
hashtableかhash-tableかは微妙なポイントで、SRFI-125ではhash-tableを推しているが( https://srfi.schemers.org/srfi-125/srfi-125.html )、yuniではプリミティブオブジェクトは1単語原則でhashtableにした。(e.g. byte-vectorでなくbytevector)
実はオブジェクトのハッシュは言語上に言語を載せる場合には良く実装できない可能性があり、可能な限り型付きのハッシュテーブルを想定した方が移植性は高くなる。ハッシュ関数は普通に使えても良いかもしれないが、移植性の問題と、eq/eqvに関しては再現性の問題がある(eq? の成立するオブジェクトに異なるハッシュ値を振る可能性がある)ため実はあんまり役に立たない。R6RSのRationaleでは:

The make-eq-hashtable and make-eqv-hashtable constructors are designed to hide their hash function. This allows implementations to use the machine address of an object as its hash value, rehashing parts of the table as necessary if a garbage collector moves objects to different addresses.

serialize

serializeライブラリは要するにFASLだが、"バイナリ形式のwrite/read"という位置付けとなる。API名は良いものが全く浮ばない。。いっそのこと fasl-read とか fasl-write にしてしまった方が良いかもしれない。。
オブジェクトのシリアライズ機能は処理系によってマチマチで、GambitやChezScheme等のようにちゃんとFASLとして使用できるものもあれば、そもそもシリアライズ機能自体を提供していない処理系もある。
最低でも、全てのread-write invariantなオブジェクトは正常にシリアライズできるという要求で良いと考えているが、問題は"writeできないがシリアライズはできた方が良いもの"をどうするかという問題だろう。
chibi-scheme、chicken、Gauche、Guile、Sagittarius などにはシリアライズの直接的なサポートはない。これらでは自前のWriter/Readerで対応することになる。

... 今見てみるとyuniのターゲット処理系でネイティブのFASLを持つ処理系って3つしか無いのか。。
これらのネイティブサポートは、一般にread/write invariantであるものの他に、ハッシュテーブルやrecordの入出力も可能となっている。ただし、yuniではハッシュテーブルの入出力は上記のinteger/symbol/stringの3種に限定しようとしている。固有のハッシュ関数やequality手続きを持ったハッシュテーブルを入出力するためには、これらの手続きもシリアライズする必要があるが、クロージャシリアライズは処理系のサポートが無いと困難なため。
ネィティブサポートの無い処理系の扱いは悩みどころだが、とりあえずScheme側の実装でどの程度のパフォーマンスになるかを見たいところ。おそらく殆どのケースで、テキスト形式のread/writeには勝てないのではないだろうか。
...じゃぁシリアライズなんて要らないじゃんということになりそうだが、

  1. read/writeだとyuniのプログラムでは頻出するbytevectorのI/Oに使えない
  2. 大きなデータセットを繰り返し処理するようなケースで必要になる - 今のユースケースだとFASL形式で100MiB 〜 500MiB程度のデータを処理しているのでテキスト形式に落してしまうとI/Oオーバヘッドが大きくなってしまう。(Twitterに挙げたgccのログデータセットは、インデックス処理後でも500MiBを超えるくらい https://twitter.com/okuoku/status/1018833935797645312 )
  3. スレッド非サポートの処理系では、並列処理時に結果を受け取る際に使用する。

というあたりで、個人的にはyuniのような位置付けのライブラリにはどうしても必要なんじゃないかと考えている。

BiwaSchemeで同期I/Oもしたい

ゲームの細かいデータハンドリングはSchemeで書いているので、Webアプリ側でも同じロジックを使いたい。というわけでBiwaSchemeにyuniを適当に移植した。で、適当に移植してみると、やっぱり普通のScheme処理系としても使えた方が便利...というわけでbytevectorや同期ファイルI/Oのような欠けている機能もyuniで使っているものは一通り実装した。
BiwaSchemeへのupstreamも意識して機能は基本的にJavaScript側に実装している。
非常に悩ましいのはJavaScriptは文字列がimmutableなため、R7RSで標準となっているmutable-stringsが実装できない点。専用の文字列ハンドルを導入して無理矢理実現するか、そもそも健全なプログラムはmutable-stringsを使うことは無いので実装しないかが悩みどころ。個人的にもmutable-stringsは殆ど使わない。
bytevectorはUint8Arrayで実装した( https://github.com/okuoku/biwasyuni/blob/5e93dc14f9901e0e20b90c56f4e037526f29b1d8/biwasyuni.js#L309 )。同期I/Oはどうせnode.jsでしか使わないのでBufferで良いような気もするが。。yuniではbytevectorのreadは必須でない(R6RSとR7RSで構文が違うので移植性が無い)ため、reader側はサポートを入れていない。
バイナリポートはget_bytes_at(bytevector指定箇所への読み取り)とget_bytes_all(内容全部の読み取り)を拡張している(https://github.com/okuoku/biwasyuni/blob/5e93dc14f9901e0e20b90c56f4e037526f29b1d8/biwasyuni.js#L137 )。openだけはyuniの側でScheme実装としている( https://github.com/okuoku/yuni/blob/7726eb4a0126c9585806160321f9e629e2a1ce2b/lib-runtime/biwascheme/prelib.scm#L12 )。これはbrowserfsがnode.jsと同じようなfsインターフェースをIndexedDBのようなブラウザ上のストレージに対して提供しているため、(current-port等と同じような感じで、)current-fsをオーバーライド可能にした方が良いかなという気がしていることに因る。
いくつかのBiwaScheme標準手続きはoverrideしている。例えばR6RSからR7RSで拡張されたstring-copy等の手続きはr6:string-copyにリネームしてyuniの標準ライブラリ側でR7RS版を実装した。また、yuniではloadは常にPauseを返し最終結果でresumeを呼ぶように修正している。

biwas.define_libfunc("load", 1, 1, function(ar){
    // Override: (load fn)
    // NB: Override load because we may return a Pause on load'ed code.
    // FIXME: Parhaps it's same for scheme-eval...
    var pth = ar[0];
    var src = fs.readFileSync(pth, "utf8"); // FIXME: Make this async.
    return new biwas.Pause(function(pause){
        var interp2 = new biwas.Interpreter(interp, this.on_error);
        interp2.evaluate(src, pause.resume);
    });
});

標準のloadは手続きがPauseを返すことを想定していないため、loadしたスクリプトがPauseを返すと異常な挙動になってしまう。このようにすることで、browserfs等を使ってブラウザ上でもファイルのload等をローカルストレージから実施できるように拡張できる可能性がある。PauseオブジェクトはBiwaSchemeの便利な機能で、継続をwrapしたJavaScriptオブジェクトとしてPauseオブジェクトを生成でき、手続きから返すことでVMの実行を一時中断できる。これによりJavaScript界ではよくある、コールバック駆動の非同期処理を同期処理のように記述することができる。

Isomorphic プログラム環境として実は有望なのではないか

yuniにとって、BiwaSchemeはs7に続く2つめのGeneric runtime処理系となっている。というわけで、yuniのランタイムをロードした状態であれば、擬似的なSyntax-rules( http://d.hatena.ne.jp/mjt/20180521/p1 )やyuniのR6RS-liteライブラリ機構を使えるので、BiwaSchemeと他のScheme処理系で同じように動作するプログラムを容易に組むことができる。(まだFFIが無いので現状実用的でもないが)
Scheme環境の他所に無い特徴として、Gambitのような実用的かつ静的な最適化コンパイラの存在がある。ランタイムサイズを極限まで絞った環境と同じコードがJavaScriptJavaでも動作するので、今までに無いIsomorphicアプリケーションが考えられるかもしれない。

各種オーディオエンジンの調査

ゲームで使うオーディオエンジンのポータビリティを高めるために、WebAudioのCバインディングを考えて、その移植レイヤを各種オーディオエンジンに対して実装するのが良いような気がしている。
...というよりは、固定機能で実現されることが多いOpenALのオーディオエフェクトと、BiquadFilterとかIIRFilterのようなフィルタプリミティブだけを提供するWebAudioではどうやってもセマンティクスが合わないので、どちらに合わせるかというのが単に問題になる。Emscriptenのように、WebAudioをOpenALでwrapし、OpenALを移植層として使う方が簡単かもしれないが、如何せんOpenAL自体があまりメジャー実装に恵まれていない。。
特に近年のVRの流行でオーディオエンジンもリバーブのbakeや、シーンのジオメトリ表現によるオーディオポータルの生成といった専用ゲームエンジンにあるような機能を提供しつつあり、これらを良く抽象化するAPIデザインはなかなか難しい。

LabSound

LabSoundはChromiumのWebAudio実装を抜き出してC++ライブラリにしたもので、機能性としてはWebAudioのものとほぼ同一になっている。グラフの構築はAudioContextを使用して行うためどちらかと言うと用法はOpenALに近い。拡張機能としてPureDataとリンクするためのPdNode等が追加されている。
ライセンスはBSD2で、当初はこれをwrapしてOpenAL実装にできないか検討していた。

Google Resonance Audio

GoogleのResonance AudioはWebやAndroidを含めた各種環境向けの空間オーディオSDKで、Ambisonicsを中心に据えたデザインになっている。OpenALではBlueRippleの実装が同様のデザインを持ち、OpenAL Softも近いAmbisonics合成パスを最近実装している。
バーブに関してはVRでは一般的になったshoeboxモデル(直方体の部屋を仮定し、部屋のサイズと壁材質に合わせたリバーブを設定するモデル)を採用している。またオープンソースのライブラリでは多分初めてバーブのbakeに対応しており( https://developers.google.com/resonance-audio/develop/unity/developer-guide )、Unityのゲームシーンにprobeを配置することでAPIのパラメタ設定を行わせることができる。残響パラメタは一般的なRT60を採用。
公開APIにはなっていないものの、各種フィルタやグラフAPIが有り、WebAudio同様のフィルタを備えている。

Windows Sonic

Windows Sonicは最近のWindows 10やXboxで使用できる空間オーディオAPIで、Dolby Atmosのトランスポートをサポートする等外部/ハードウェアレンダラをサポートしているのが特徴。ただしXboxでハードウェアレンダリングした場合16オブジェクトに制約され、ソフトウェアレンダリングの場合のAPI制約も128オブジェクト(含bedチャンネル)となっている。
8.1.4.4のようなチャンネルフォーマットをサポートする一方、Ambisonicsをサポートしていない。Barcoはチャンネルベースオーディオを推すホワイトペーパーを以前出していて( http://d.hatena.ne.jp/mjt/20140504/p1 )Dolby Atmosと対決姿勢を見せていた一方、Dolbyに支持されたこのAPIがAmbisonicsをサポートしていないのはちょっと気になる傾向と言える。
Ambisonicsの非サポートに見られるように、Windows SonicのAPIセット自体はAtmosのようなレンダリングシステムとのインターフェースに特化していて、フィルタやリバーブ等の機能性を持たない。何を組み合わせるべきなのかは調査中。

Steam Audio

Steam Audio はValveに買収されたImpulsonicのオーディオSDKを無償化したもので環境のbakeやTrueAudioによる畳み込みオフロード等の高機能を誇る。特に無償のミドルウェアで直接的にTrueAudioをサポートしているのは珍しい気がする。Ambisonicの再生やHRTFレンダリングもサポートしているものの、フィルタやオシレータのような機能性は無く、どちらかというとOpenALに近い機能性と言える。
また、つい昨日リリースされたbeta14で、IntelのEmbreeレイトレーサ( http://embree.github.io/ )を使用したオーディオシミュレーションに対応している( https://steamcommunity.com/games/596420/announcements/detail/1674659226616741624 文中のphononはSteam Audioにおけるオーディオエンジンの名称)。
サイトはgithub.ioに有るもののオーディオエンジン部分のソースコードは公開されていない。バイナリはAndroid/Linux/OSX/Windows向けに提供。

Oculus Audio

ココで最初にとりあげた( http://d.hatena.ne.jp/mjt/20150308/p1 )際はネイティブAPIは公開されていなかったが、現在はC APIも公開されている。Ambisonicsの再生に対応しており、機能性は比較的普通。
同時発音数等をモニタするプロファイラ( https://developer.oculus.com/documentation/audiosdk/latest/concepts/audio-profiler-using/ )を提供している。Wwiseの統合ミドルウェアでは比較的よく見られる機能だが、単体のspatializerで提供されるのは比較的珍しい。