週刊mosh - case拡張 / 非同期処理構文再び / ライセンス

諸事情によりあまりコードを動かせないので年内は0.2.8にならないかも。。
moshは先端を追いかけるのがかなり大変(Git + Subversion + Gauche + Autotoolsが必須)なのでリリースの比重は一般的なプロジェクトより重いのだけど、非同期I/OやR7RSのような規模の大きい機能追加を入れないでリリースしてしまうと、リリースブランチと開発ブランチのダブルメンテがかなり大変という事情も。
リリースのタイミングはなかなか難しい。

case拡張

R7RSで拡張されたcaseの構文 => の存在意義がわからないという話を前回かいたけれど、これは

... letが一つ減らせます。 ...

という絶妙な効能が有るらしい(いつもコメントありがとうございます。。)
実際、R7RS-bridgeでは一旦letを経由してこれを実装している:

(define-syntax case
  (syntax-rules ()
    ((_ key clause ...)
     (let ((obj key))
       (%r7case-clause obj () (clause ...))))))

この構文のサポートはchibi-schemeにも最近入った( http://code.google.com/p/chibi-scheme/source/detail?r=d680070de4385c1225fb7dd31ed8eaae4593db26 )。

非同期処理構文再び

seq構文ですが、モナドのbindとdoの関係そのまんまなので、モナド風味に合わせたらそっち方面から来た人には馴染みやすいかなとちょっと思いました。
(do* [res <- (output target bv len)] [res <- (output target bv-next len-next)] ...) とか、 (letM* ([res (output target bv len)] [res (output target bv-next len-next)] ...) body ...) とか。

このへんは、"驚き最少原則"(というよりは"裏切り最少原則")であのような形にしました。。一般的にうまく動くcallbackプロトコルが思い当たらなかったので。
ライブラリ(yuni *)ではleft to rightの構文をmosh固有の構文として区別できるようにしています。(他にhoge.fuga.a = 100 のような代入を(~ hoge 'fuga 'a := 100)と書く等)
実際にseq構文を使ってみて気付いた点 :

  • Callbackが呼ばれないケースが困る

seq構文は基本的に"I/Oオペレーションをenqueueして、結果を受け取って次の動作をする"というプロトコル処理に良くあるケースを抽象化している。
が、enqueueは常に成功するとは限らないので、callbackが絶対に呼ばれないケースが存在することになる。(readリクエストの発行に失敗したらread callbackは呼ばれない)
1つの解決策は、callback自体が行われることを保証することがあるけど、エラーの戻り値と通常のcallbackは普通違うので難しい。。
手続きの呼び出し部を拡張して、

(seq (=> [output] target bv len => [res]) ...)

のように、手続きをカッコでくくると(callback同様に)その戻り値をチェックするようにすると良いかもしれない。
戻り値が偽だったら有無を言わせず失敗と見做す手もあるが、この場合エラーコードのような情報をエラーハンドラに渡すのが難しくなってしまう。もちろん多値を使えばいいんだけど、Schemeの多値はLispと違って正確な数を受け取らないといけないのでパフォーマンスインパクトが心配。

  • 複数回のcallbackがあるケースを救えない

基本的にseqで扱われるcallbackは一度だけ呼び出されることが前提となる。例えば↑のoutputがcallbackを複数回呼びだしてしまうといったケースではかなり理解しづらい現象が起こる。
複数回呼び出しをチェックすること自体は簡単なので、デバッグ時にはそういうコードを挿入するのも有りかもしれない。ログも取れて一石二鳥だし。

  • 脱出が面倒

seqの一部を選択的に実行したり、以降の実行を中断したりを行う上手い方法は無い。もちろん構文的に工夫すればラベルによるgoto等を実装できるけど、けっこう間違えやすい機能なので導入には慎重にならざるを得ない。。(通常のケースでは単にseqを分割すれば事足りる)
seq呼び出しにpredicateを付けられるようにするといいかも。

(define (talk enable-extended? data)
  (seq (=> output target init-message => [res])
       (enable-extended? => output target init-extended => [res])
       (=> output target data => [res])
       ...))
  • joinにあたる操作がない

2、3のI/Oリクエストを並行して投げて、それらが完了してから次というケースは非常に多いが、いまのところこれをうまく書く構文を思いついていない。

  • exitをよくわすれる

libuvのloopとちがって、nmoshのI/O LoopはI/O待ちオブジェクトが存在しなくても終了しないので、ちょっとしたテスト用のスクリプトを書いて"なかなか終了しないなぁ"というのをしょっちゅうやっていた。。
明示的にexitを書かせるスタイルが正しいかどうかは絶妙なところ。

  • FFIの負荷が無視できない

FFIの呼び出しはかなりアロケーションが発生するようで、特にWindowsではR6RS portにパフォーマンス的に負けてしまったりする。。
nmoshの非同期I/Oはpsyntax-mosh側への影響を避けるために"Cで書いた補助関数の関数ポインタを受け取り、FFI経由で自身を呼び出す"という非常に絶妙な方法で実装されている。
もちろん、他のR6RS手続きと同様にCProcedureとして呼び出せるようにstubを生成すれば良い話だが、

  • CProcedureは序数で管理されているので追加/削除するとFASLの互換性が崩れる
  • FASLの互換性が崩れるとpsyntax-moshのバイナリを更新しないといけない
  • psyntax-moshが動かないとmoshをビルドできない

という難しい状況。

ライセンス

Moshを構成しているソフトウェアのライセンスはCOPYINGファイルに書かれているけど、これが不正確という指摘を受けて再調査中。
Moshソースコードの形式で再配布するときはソースコード側に必要なライセンスが記述されているが、Moshはアプリケーションに組み込まれたりするケースがあるのでCOPYINGをone stopのライセンスリストにするようにしている。そういう目的があるので、バイナリ配布には通常含まれないtests/以下等のコードはCOPYINGでカバーされていない。
同様にpsyntax-moshもバイナリ配布が困難なのでCOPYINGにpsyntax関連の記述は無い。

  • Boehm GC

Boehm GCはコードが修正されていることは明記しないといけないライセンスになっている。修正事項はextlibs/READMEに書いているけどCOPYING側に反映されていなかった。

SRFIからのコードが微妙にそれぞれ違うライセンスなのが罠。
SRFI標準のライセンスは

THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

という免責noticeが付いているけれど、SRFI-99のライセンスは微妙に違っていて、

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. REMEMBER, THERE IS NO
SCHEME UNDERGROUND
. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

という一節が追加されている。
この手の細かい違いがあるので、権利表記とライセンスが完全に一致しないかぎりは全文を毎回コピーするようにした。(たとえばMoshはcompilerとPEGライブラリをGaucheから持ってきているけど、それぞれ権利表記が違うので別のライセンス扱いしている。)