「まるごとJavaScript & Ajax! Vol.1」2007年02月02日 23時20分

まるごとJavaScript & Ajax! Vol.1」の執筆に参加させていただきました。ええっと、まあ、その、なんと言うか……書きすぎました。「にわかな奴ほど語りたがる」の典型です。半分とは言わないまでも 3 分の 2 くらいで収めるべき内容だったような気がします。

一応言い訳をしておくと、雑誌に寄稿するのは初めてで分量の見当がつかなかったからで。桁数行数は示されていたのだから、ワープロソフトで整形すればページ数の目安が一目瞭然のところを、そんなことすら思いつかなかったわけで。すっかり余裕なしで書き上げてしまった原稿をひとつの記事にまとめ上げてくださった編集の方々には感謝するばかりです。

ちなみに書いていて思ったことといえば Opera 9 のアグレッシブさ。記事中では触れられませんでしたが Web Forms 2.0 の一部実装など、IE 3 の CSS 実装の二の舞にならないかと思わず心配してしまうほどです。

遅くなりましたが Special 2 Part 1、ジェネレータを用いたアニメーションのサンプルです。また、インプレスのサポートページにて訂正情報が出ています。

getElementsByClassName on Gecko2007年02月04日 14時29分

Gecko に Web Applications 1.0 の getElementsByClassName メソッドが実装されました (Bug 357450)。Firefox 3 から使用可能になります。document.getElementsByClassName で文書全体から、または element.getElementsByClassName である要素の子孫要素から、特定のクラス名を含む要素を探し出すことができます。

引数は空白文字区切りでクラス名のリストを表す文字列。複数のクラス名が指定された場合はそれらすべてを持つ要素のリストが返ります。現時点の Web Applications 1.0 仕様では、複数のクラス名を収めた配列も引数として使えることになっていますが、それは実装されていません (一口に「配列」といってもプログラミング言語ごとに実際のデータ構造が異なるから?)。

なお、返されるリストは「生きて」いる、すなわち、文書の変更に伴ってリストの内容も変化します。ですから、以下のコードは期待通り動きません。

var elements = document.getElementsByClassName("foo");
for (var i = 0; i < elements.length; i++)
  elements[i].className = "bar";

この場合、たとえば以下のようにする必要があります (またはリストの末尾からなめる、elements = Array.slice(elements, 0) として静的な配列にするなど)。

var elements = document.getElementsByClassName("foo");
while (elements.length)
  elements[0].className = "bar";

同様のことが XUL 文書、XUL 要素に対して使用可能な getElementsByAttribute メソッドにもいえます。(userChrome.js 用スクリプトを書いているときこれにはまりました。)

ところでこの記事のタイトルは「getElementsByClassName in Gecko」のほうが正しいのでしょうか? どうもそこら辺よくわかりません。

再帰クイックソートの可視化2007年02月06日 18時10分

いやなブログ - JavaScript でソートアルゴリズムを可視化」より。何も考えずに再帰処理のクイックソートの様子を逐次描画しようとするとこうなります。

function quickSort(data, begin, end, log) {
  if (begin >= end) return data;

  var pivotPos = begin;
  var pivot = data[pivotPos];

  for (var i = begin + 1; i < end; i++) {
    if (data[i] < pivot) {
      var temp = data[i];
      data[i] = data[pivotPos + 1];
      data[pivotPos + 1] = data[pivotPos];
      data[pivotPos] = temp;
      pivotPos++;
    }
  }

  log(data);

  quickSort(data, begin, pivotPos, log);
  quickSort(data, pivotPos + 1, end, log);
  return data;
}

var array = [9, 5, 7, 3, 2, 4, 0, 1, 6, 8];
quickSort(array, 0, array.length, print);
// 5,7,3,2,4,0,1,6,8,9
// 3,2,4,0,1,5,7,6,8,9
// ...
// 0,1,2,3,4,5,6,7,8,9

しかし、ブラウザ上でアニメーション表示するためには、setTimeout などのタイマーを使う必要があります。ところが、タイマーを使って処理を分けると、その処理が終わった後で元の処理に戻ることができません。上のクイックソートでいうと、データの前半を並び替えた後、戻って後半を並び替えないといけないため、そのままではアニメーション表示ができないのです。

ではどうするか。戻らなくてはならないのが問題ならば、戻らなければいいのです。呼び出し先から戻って続きの処理をやるのではなく、続きの処理 (継続) も呼び出し先に丸投げしてしまいましょう。JavaScript ではクロージャが使えるので、続きの処理をまとめたクロージャを引数として渡すことができます。このような書き方を継続渡しスタイル (CPS, continuation passing style) というそうです。「ヒビノキロク - Javascriptで継続渡し」を参考に上のクイックソートを継続渡しスタイルに書き換えてみます。

function quickSortCPS(data, begin, end, log, cont) {
  if (begin >= end) return cont(data);

  var pivotPos = begin;
  var pivot = data[pivotPos];

  for (var i = begin + 1; i < end; i++) {
    if (data[i] < pivot) {
      var temp = data[i];
      data[i] = data[pivotPos + 1];
      data[pivotPos + 1] = data[pivotPos];
      data[pivotPos] = temp;
      pivotPos++;
    }
  }

  log(data);

  return quickSortCPS(data, begin, pivotPos, log,
    function (partiallySortedData) {
      return quickSortCPS(partiallySortedData, pivotPos + 1, end, log,
        function (entirelySortedData) {
          return cont(entirelySortedData);
        });
    });
}

function nop() {}
quickSort(array, 0, array.length, nop, print);
// 0,1,2,3,4,5,6,7,8,9

return 文に式が指定されていますが、この返り値は意味を持ちません。処理結果は関数呼び出しの返り値として受け取るのではなく、継続として渡したクロージャが引数として受け取ります。

さて、ここまで来れば後は簡単です。返す値に意味がない、すなわち戻る必要がなくなったのですから、大手を振ってタイマーが使えます。Gecko では setTimeout の引数として関数実行時の引数も渡せるので、以下のように書き換えられます。

function quickSortCPSWithTimer(data, begin, end, interval, log, cont) {
  ...

  return setTimeout(quickSortCPSWithTimer, interval,
    data, begin, pivotPos, interval, log,
    function (partiallySortedData) {
      return setTimeout(quickSortCPSWithTimer, interval,
        partiallySortedData, pivotPos + 1, end, interval, log,
        function (entirelySortedData) {
          return cont(entirelySortedData);
        });
    });
}

function plot(data) {
  ...
}
quickSortCPSWithTimer(array, 0, array.length, 20, plot, nop);

これで 20 ミリ秒ごとにソート途中のデータがプロットされることになります。実際は IE だと setTimeout で引数を渡せないので、Gecko の setTimeout と同じ動作をする関数 doLater を使います。

function doLater(func, interval) {
  var args = Array.prototype.slice.call(arguments, 2);
  return setTimeout(function () { func.apply(this, args); }, interval);
}

以上をまとめた再帰的なクイックソートの可視化です。継続渡しとは関係ありませんが、元記事のものと比べると描画がかなり軽快になっていると思います。

なお、Shibuya.js Technical Talk #2 で「ActionScript でクロージャ、継続渡し」を発表なさったとおる。さんが、「まるごとJavaScript & Ajax! Vol.1」で「FlashのActionScriptで関数型風プログラミング」という記事を書いています。継続渡しについてもいろいろと解説してくださるようで、読むのを楽しみにしています。と自分の宣伝もかねて :-)

おまけで非破壊的なクイックソートとその継続渡しスタイル版。「maoeのブログ - 末尾再帰のクイックソート」で見た Haskell のクイックソートを基にしたものです。短いけど何かごちゃっとしてしまいました。

function quickSortND(data) {
  if (!data.length) return [];
  var pivot = data[0], lt = [], gte = [];
  for (var i = 1; i < data.length; i++)
    ((data[i] < pivot) ? lt : gte).push(data[i]);
  return quickSortND(lt).concat([pivot], quickSortND(gte));
}

function quickSortND_CPS(data, cont) {
  if (!data.length) return cont([]);
  var pivot = data[0], lt = [], gte = [];
  for (var i = 1; i < data.length; i++)
    ((data[i] < pivot) ? lt : gte).push(data[i]);
  return quickSortND_CPS(lt, function (sortedLt) {
    return quickSortND_CPS(gte, function (sortedGte) {
      return cont(sortedLt.concat([pivot], sortedGte));
    });
  });
}

quickSortND_CPS(array, print);
// 0,1,2,3,4,5,6,7,8,9

元記事にトラックバックを送ろうとしたら 403 Throttled エラーなるものではじかれました。この場合、時間をおいてもう一度送りなおせばいいのかな? 時間をおいたらうまくいった。

JavaScript shell の readline2007年02月20日 23時27分

blog.8-p.info : SpiderMonkey でフィルタを書く」で触れられていた SpiderMonkey 付属の JavaScript shell の readline 関数で空行と EOF の区別がつかない問題。

しかも UNCONFIRMED の略が UNCO って!確認してやれよ!どう見てもこれじゃ困るじゃん! UNCO-! SM! UNCO-!

はじめてのにき('-')(2007-02-20) より

確認はできるけど確認しかできないから確認していいのかわからない。それとも確認 (ステータスを UNCONFIRMED から NEW に) した上でレビュー申請を出してもらえば見てもらえるのかな?

にしても UNCO はずっと「アンコ」と読んでたからなんとも思わなかったけど、そう言われるともう「ウ○コ」としか読めない >_<

こんなもんマトモなプログラム言語じゃないなぁ…

はじめてのにき('-')(2007-02-20) より

悪いのはシェル部分の実装であってコア言語の実装は悪くないよ! と言ってみたりする。特に E4X 周りで使い勝手の悪いバグを抱えてたりするけど……。

まあそれでも個人的には Rhino (ついつい「りの」って読んじゃう……) より SpiderMonkey のほうが好き。SpiderMonkey にあって Rhino にない機能がたくさんあるから。逆は……継続と末尾再帰の最適化と……あと何かあったっけ?

XHTML 1.1 と target 属性2007年02月21日 02時27分

XHTML 1.1 Second Edition 2007 年 2 月 16 日付草案で target 属性が追加されたという話が出ていますが、本当に使えるんでしょうか? 確かに DTD には Target Module が含まれていますが、XML Schema 定義には含まれてないし、何より本文中で何も触れられていないんですよね。

適合性を見ると、The document MUST conform to the constraints expressed in Appendix C. と DTD に準拠しなくてはいけない旨は書いてありますが、The root element MAY also contain an schemaLocation attribute as defined in the [XMLSCHEMA]. と XML Schema に関しては MAY 扱い。schemaLocation 属性を書くなら target 属性は使えないけれど、必ずしも書く必要はなく、またその場合は target 属性を使えるという風にも読み取れます。

しかし、DTD 上問題ないからといって HTML/XHTML として正しいとは限りません。いずれにせよ本文中に記述がないというのがつらすぎます (読み落とし、誤解の指摘大歓迎)。

何か HTML 4 の script 要素における for 属性と event 属性を思い出します。いずれも将来のための予約として DTD では定義されていましたが、仕様書本文中での言及はありませんでした。XHTML 1.1 での target 属性がそのような中途半端な状態にならないよう、新たな草案ではっきりさせてくれることを望みます。