愛と誠の構造


無味乾燥な内容にバカなタイトルをつけてごめんなさいね。

Lisp には Pretty Printer という便利なものがあります。Lisp のデータを画面にきれいに整形して表示してくれるものです。いわゆる printf デバッグをするのにとても便利です。JavaScript でも似たようなことができないかというのがこの記事の話題です。

以下は ypsilon Scheme のプログラム(pp.scm)です。

(library (x)
(export)
(import (rnrs)
(rnrs r5rs)
(only (core) pretty-print))

(let ((p (open-input-file “pp.scm”)))
(pretty-print (read p))
(close-input-port p)))

このプログラムは自分自身を整形して画面に出力します。これを実行すると以下のような出力が得られます。

(library (x)
(export)
(import (rnrs) (rnrs r5rs) (only (core) pretty-print))
(let ((p (open-input-file “x.scm”))) (pretty-print (read p)) (close-input-port p)))

まあまあ、きれいに出力してくれますよね。

Scheme の場合、プログラムをデータとして扱えるので、このような見事なことができるのですが、ほかの言語でもこういうことが簡単にできるといろいろ便利です。

では、JavaScript でやってみましょう。まず、自分で作ったオブジェクトを画面に出力してみるとしましょう。

var i = { i: ‘Love’ };
print(i);

このスクリプトを実行すると以下のような、あまりにそっけない出力が得られます。

[object Object]

出力されたオブジェクトの内容に関する情報は一切、得られません。こういうときのために JavaScript の Object.prototype には toString というメソッドが用意されています。このメソッドはオブジェクトを文字列に変換するときに呼ばれます。そこで、まずは自分で作ったこのオブジェクトに toString メソッドを以下のように定義した上で出力してみます。

i.toString = function () {
return ‘{ i: ‘ + this.i + ‘ }’;
};
print(i);

toString メソッドをカスタマイズしたおかげで、今度は以下のような感じのよい出力が得られます。

{ i: Love }

ただ、このような操作を個別のオブジェクトごとに施すのはあまりに大変です。そこでもっと一般的にやる方法を検討してみましょう。まずは、個別のオブジェクトの toString を上書きするかわりにすべてのオブジェクトのプロトタイプとなる Object.prototype の toString メソッドを変更します。

ひとつの方法としては、Object.prototype.toString のなかで ownPropertyNames などを参照してがんばって文字列を作成する方法です。でも、これは案外面倒なのでパス。ここでは JSON の機能を使って、文字列に変換します。JSON.stringify はオブジェクトのプロパティを列挙してくれる上に、オブジェクト参照の木構造をきれいにネストした出力をしてくれますから、ここでの目的にぴったりです。

ごちゃごちゃ言ってるよりも書いてしまえばすぐです。こんな感じ:

Object.prototype.toString = function () {
return JSON.stringify(this, null, 2);
};
print(i);

こうすると、以下のような感じの出力が得られます。

{
“i”: “Love”
}

Lisper にはやや間延びしたように見えるかもしれないけれども、十分に見易いと思います。以下のような入れ子構造を持ったオブジェクトの場合も大丈夫です。

print({ i: ‘Love’, u: { love: ‘Me’ })

{
“i”: “Love”,
“u”: {
“love”: “Me”
}
}

さて、これでシメシメと思っていたのですが、ひとつ落とし穴がありました。文字列に日本語を使うと、その部分が16進表記に置き換わってしまうのです。たとえば、

i.i = ‘愛’;
print(i);

とやると、以下のようになってしまいます。

{
“i”: “u611b”
}

間違ってはいません。文字化けしているわけでもありません。ただ”愛”の文字コードが16進数で”611b”なので、ユニコードのエスケープシーケンスを使って、”u611b” と表記されています。でも、日本人には厳しいです。さて、なんとかならないものか。。。

しばらくは面倒だったので放置していたのですが、昨晩、Twitter で Chihiro Ito さんに [[String.fromCharCode というものがある:http://ken-wakita.net/ja/2010/02/my-broken-love-problem/%5D%5Dと教えていただいて解決しました。

要は、”JSON.stringify” して作成した文字列のなかのユニコードエスケープシーケンスに相当する部分を該当する文字に置き換えればよいわけです。プログラムで書くと以下のようになります。

(function () {
var fix = function (utext) {
return String.fromCharCode(‘0x’ + utext.slice(2));
};

Object.prototype.toString = function () {
return JSON.stringify(this, null, 2).replace(/\u[0-9a-f]+/ig, fix);
};
})();

これを使って、さきほどの例題を試してみましょう。

print(i);
i.m = ‘誠’;
print(i);

を実行した出力が以下です。

{
“i”: “愛”
}
{
“i”: “愛”,
“m”: “誠”
}

うまくいっているみたいです。

ということで、私は ”~/lib/javascript/v8init.js” に以下を追加しました。

(function () {
var fix = function (utext) {
return String.fromCharCode(‘0x’ + utext.slice(2));
};

Object.prototype.toString = function () {
return JSON.stringify(this, null, 2).replace(/\u[0-9a-f]+/ig, fix);
};
})();

愛と誠の構造」への1件のフィードバック

  1. utext に u0000 のような表示できない文字のコードが指定されたときに問題となりそうです。以下のように修正しました。

    var fix = function (utext) {
      var c = parseInt('0x' + utext.slice(2));
      if (c < 0x20) return utext;
      else return String.fromCharCode(c);
    };
    

コメントは受け付けていません。