2005-08-22

beyond.jsってなに?の巻

こどもてれびに対抗して巷のJavaScripterの間で最新流行のbeyond.jsってライブラリの解説をします。

beyond.jsとは


知らないやつはモグリといっても過言ではないぐらい有名なライブラリです。
嘘です。どれぐらい使われているのかは良くわかりません。
Ajaxとか全然関係なくて、純粋にJavaScriptのライブラリです。

beyond.jsはここからダウンロードできます。
http://w3future.com/html/beyondJS/

Beyond JS is a Javascript library that lets you write Javascript unlike anything you've ever written. Your code will never be the same again. It contains both useful, functional, tricky and freakish extentions and additions to Javascript. It can make your code more readable and less debugable, and even the other way around.


- BeyondJSを使うとアナタが今まで書いたことの無いようなやり方でJavaScriptを書けます。
- 一度使ったら元には戻れません。
- JavaScriptに便利で実用的でトリッキーで奇抜な拡張を加えます。
- アナタのコードをより読みやすくデバッグしやすく、なったりならなかったり。

多分こんな意味だと思いますが、ドキュメントがろくに無いので全般的に何をやっているのか意味不明です。

大体公式ページに張ってある唯一のサンプルがチェックボックスを入れるとマウスの動きに合わせて星が動くとかいう、とてつもなくしょぼいデモンストレーションで、しかもIE限定です。いまどきFirefoxで動かないなんてダメじゃん、と9割ぐらいが立ち去ります。

このサンプルが動かないのはイベントハンドリングのやり方が違うからであってBeyondJS自体はクロスプラットフォームです、と言い訳が書かれています。

ネイティブオブジェクトを拡張する「beyond.js」自体は汎用性のある部分のみ記述されていて、プラットフォーム個別の処理は、WSHで使うためのbeyondWindows、Rhinoで使うためのbeyondRhino、ブラウザで使うためのbeyondBrowserなどに分割されています。
遅延評価の配列を作成するbeyondLazyなんかも面白そうですが、今回は触れません。

サンプルを読み解く


一応アーカイブにいくつかサンプルなども同梱されているのですが、とりあえずトップページ張ってあるデモの解説をしてみます。多少見やすく整形したつもりです。

Function.from("star", "moveTo").delay(1000).using(
    "+".using(
        Function.from(event, "x"), 
        "*".using(
            "radius",
             Function.from(Math, "sin", "angle")
        )
    ),
    "+".using(
        Function.from(event, "y"), 
        "*".using(
            "radius",
            Function.from(Math, "cos", "angle")
        )
    )
).curry({
    radius: 30,
    angle : function() {return (new Date)/50;}.asValue()
}).gate(
    Function.from("doStar".element(),"checked")
);


これは一体何をしているのかというと

マウスが動いたら
「"star"というstringのmoveToというメソッドから関数を生成、
 それを1000ミリ秒遅らせて実行する関数を生成し、
  その関数の第一引数には
   eventのxというプロパティと
   radiusとMath.sin(angle)を掛け合わせたもの
   を合計したものを渡す
  その関数の第二引数には
   eventのyというプロパティと
   radiusとMath.cos(angle)を掛け合わせたもの
   を合計したものを渡す
  ような関数を生成し、
 その関数の引数として
  radiusに30、
  angleに現在時刻を50で割った結果を返す関数の実行結果
 を関連付ける」
という関数を、
 doStarという要素の"checked"プロパティを返す関数を作って
 それの実行結果がtrueの場合にのみ
実行する、という関数を生成しています。

ざっと数えたところ、15個ぐらいの関数を生成しています。
内部的にはもっと多いかもしれません。

ああなるほど「functional」って関数的って意味だったのか。

JavaScriptの標準の機能だけで書き直す


if(!document.getElementById("doStar").checked) return;
var x = event.clientX; // Firefoxでも動くように
var y = event.clientY;
setTimeout(function(){
    var angle = (new Date)/50;
    var star = document.getElementById("star");
    star.style.left = (30*Math.sin(angle))+x + "px";
    star.style.top  = (30*Math.cos(angle))+y + "px"; 
},1000);


というわけで、なんか、普通に書いたほうが短くなりました。
普通に書いたものをここに置いておきます。

http://la.ma.la/misc/js/star.html

マウスの動きに1秒遅れて、円を描きながら星が追いかける、というデモのようです。

Function.from


個別に解説。
Function.from(a,b,c)はオブジェクトaのbというプロパティから関数を生成して、cという引数を使う、という意味らしいです。

Function.from("doStar".element(),"checked")
// →function(){return "doStar".element().checked}

Function.from(Math, "sin", "angle")
// →function(angle){return Math.sin(angle)}



多分こんな感じです。内部的にはもっと複雑になっていて、いまひとつ意図がつかめません。
とにかく引数を元に関数を生成する関数みたいです。

function.using


function.usingは引数の束縛に使います。
引数を固定して、残りの引数を受け取る関数に変形します。

function plus(a,b){
    alert(a+b)
}
plus.using(3,5)(); // 8
plus.using(3)(5);  // 8
plus.using()(3,5); // 8

function.curry


function.curryは任意の引数を固定します。内部のいたるところで使われています。
すごいのは引数の順番がどうなっていようと、きちんと固定してくれる点です。

function plus(a,b){
    alert(a+b)
}
plus.curry({a:3})(5); // 8
plus.curry({b:3})(5); // 8


----
引数を減らすことをカリー化(currying)と呼ぶらしく、これはHaskellの用語だそうです。
function.asValue()っていうのはカリー化するときに、関数ではなく、関数の実行結果を関連付けよ、っていう意味です。多分。

string.using


string.usingは演算子を関数に変形することが出来ます。

まず文字列を関数に変形するstring.toFunctionというメソッドがあって、さらにその関数にusingを使って引数を束縛する、という実装になっています。

three_plus_what = "+".using(3);
alert(three_plus_what(5)); // 8

"+".using(3,5)() // 8
"+".using(3)(5)  // 8


演算子を先に書くのは、lispで使われるポーランド記法っぽいです。

応用:演算子のオーバーロード


今までに無いというのは確かですが、読みやすいとかデバッグしやすいかというと良くわかりません。もはや別の言語と考えて良さそうです。なんだか簡単なことをわざわざ難しく書いているように思えますが、代わりに、本来出来ないようなことが可能になります。

例えば、String.prototype.usingを書き換えることで演算子のオーバーロードが可能になります。

// 全ての足し算の結果を50増やす
String.prototype.using_ = String.prototype.using; // オリジナルを保存
String.prototype.using = function(){
    var self = this;
    var args = arguments;
    if(self == "+"){
        return "+".using_(
            50,
            String.prototype.using_.apply(self,args)
        )
    }else{
        // +以外の時は本来の動作を呼び出す
        return String.prototype.using_.apply(this,arguments)
    }
};

これで、"+".usingを使った全ての足し算の結果が50ずれます。
そんなわけで星の座標が本来より50ドットずれて表示されるサンプル。
http://la.ma.la/misc/js/beyond1.html

対応するのが面倒なのでIE限定で。たいして面白いわけでもない。

応用:パラメータを外部から変更する


星がマウスを追いかける速さを変更してみましょう。
ふつうはdelay(1000)の部分を変更しますが、別の方法でも可能です。

Function.prototype.delay = Function.prototype.delay.using(10);


これで全ての遅延関数の引数が強制的に10になります。
マウスを動かしてすぐに星が後をついてくるようになります。
これも面倒なのでIE限定で。
http://la.ma.la/misc/js/beyond2.html

まとめ


beyond.jsでわかるJavaScript(というよりECMAScript)の特徴を挙げるとするならば、

-prototypeを変更することで広範囲にわたって挙動を変更することが出来る。
-関数は任意の数の引数を受け取れる、引数の数や型に応じて挙動を変更できる。
-関数自体がオブジェクトで、関数を変形させて新しい関数を作成できる。

この三点ではないかと思います。

なんだか無茶なことをやっているように見えるけれど、このライブラリは多分、そういう無茶なことをやるために存在するのです。

「JavaScriptって演算子のオーバーロードも出来ねえのかよ?」
そんなあなたにbeyond.js。
もっとも、使いこなすにはHaskellとかlispを勉強しないといけなさそう。

もはや誰をターゲットに書いてるのかよくわかりませんが、
まあとりあえずbeyond.jsは何かとすごいです。

おわり。

2005-08-14

JavaScriptのデザインパターン - Singleton

JavaScriptじゃねえと書けねえよ、ってやり方でデザインパターンを実装してみるコーナー。とはいってもデザインパターンとか良くわからないので適当に覚えながら作る。

間違ってる箇所あったらつっこんでくれるとありがたいです。

わかりやすい文章を書く能力が欠如してるのでデザインパターンって何だとかそういうのはこっち参照。
http://d.hatena.ne.jp/naoya/20050813/1123924312


JavaScriptのコンストラクタはPerl同様自在に定義できます。returnでobjectを返してやれば、newの結果としてそいつを使います。

普通にシングルトンなクラスを実装するにはこんな感じだと思います。

function Singleton(){
    var self = arguments.callee;
    if(self.instance == null){
        this.initialize.apply(this,arguments);
        self.instance = this;
    }
    return self.instance;
}
Singleton.prototype = {
    initialize : function(o){
        this.foo = o;
    }
};
var a = new Singleton("aaa");
var b = new Singleton();
var c = Singleton.instance;
alert(a.foo); // aaa
alert(a==b); // true
alert(b==c); // true 全部おんなじ



arguments.calleeは実行中の関数を参照します。コンストラクタ関数のinstanceプロパティに生成したオブジェクトをキャッシュしておいて、同じオブジェクトを使いまわすようにします。これで、いくらnewしても同じオブジェクトが出来上がるようになります。

newを禁止したい?できないこともない。

せっかくなのでnewを禁止して、getInstanceでオブジェクトを取得するように関数を変形するFunction.prototype拡張を作ってみよう。

Function.prototype.toSingleton = function(){
    var ctor = this;
    var Dummy = function(){};
    Dummy.prototype = ctor.prototype;
    var new_func = function(){
        if(new_func.__allow_create__ == null){
            throw "this is SingletonClass. use getInstance()";
            return null;
        }
        return ctor.apply(this,arguments)
    };
    new_func.getInstance = function(){
        if(new_func.instance == null){
            var tmp = new Dummy;
            var res = ctor.apply(tmp,arguments);
            new_func.instance = (typeof res == "undefined")?tmp:res;
        }
        return new_func.instance;
    };
    new_func.prototype = ctor.prototype;
    return new_func;
};

// やっぱシングルトンじゃなくする。
Function.prototype.removeSingleton = function(){
    this.__allow_create__ = true;
}



getInstanceはnewを使わないのでthisキーワードが使えない。thisを使ったらnew_funcへの参照になってしまった。なので、空のオブジェクトを生成して、そいつをthisとしてコンストラクタ関数に引渡し、返値もしくはthisをインスタンスにセット、というnewの挙動を再現するようにした。これで大体問題ないはず。

空のオブジェクトを作成するのにDummyを使うのは、元のコンストラクタ関数のprototypeを継承するため。これをやらないとprototype.jsのthis.initializeみたいな処理が呼べない。

__allow_create__っていうフラグが立っていない場合は関数としての呼び出しは許可されないようにする。newでインスタンスを生成しようとしたらthrowでgetInstanceを使えとエラーメッセージを投げてやる。

シングルトンパターンはDBのコネクションを一つに絞ったりするのに使うらしいです。
まず、複数のインスタンスが生成されることを全く考慮しないで設計したConnectionクラスを作って、Connectionをシングルトンに変換させたConnectを実際に使用する、といった具合になります。

全然具体的じゃないサンプルですが一応。

// ベースになるクラス定義
function Connection(option){
    this.name = option;
}
// シングルトンに変形
Connect = Connection.toSingleton();
var db = Connect.getInstance("myname");
var db2 = Connect.getInstance("hogehoge"); // 引数無意味
alert(db==db2); // true

// 短く書くとこう
var Connect = function(option){
    this.name = option;
}.toSingleton();

// 上書きしても問題ない
Connection = Connection.toSingleton();
// やっぱnewで使う
Connection.removeSingleton();
var hoge = new Connection;

// prototype.js使うならこう。
var MyClass = Class.create();
MyClass.prototype = {
    initialize : function(option){
        this.name = option
    }
}
MyClass = MyClass.toSingleton();
var huga = MyClass.getInstance();



こんな具合で既存のコンストラクタ関数を何でもシングルトン化できる。

Class.create().toSingleton();と書きたいところだけど、そうするとMyClass.prototypeがシングルトン化された関数に対して関連付けられてしまうので上手くいかない。

いったんインスタンスを作ったら、getInstanceに引数を渡しても無視するようにした。なんかキーとか指定できるようにしたほうがいいのかな。それじゃシングルトンじゃないか。よく知らん。

デザパタとかオブジェクト指向のなんたるかとかは良くわかってないが、JavaScriptに関してはけっこう真面目に勉強しなおしたのでバッチリだ。今まで書いたコード全部書き直したいが単純に時間が足りない。

2005-08-08

XSLエディタを作ってみた

XSLをエディタで編集してはブラウザでプレビューなんてことを何千回とやっているので、なんか良さげなXSLエディタがあるなら使いたいんだけど探しても見つからないので作ってみた。

http://la.ma.la/misc/xsltedit/

左にXSLを入れて右にXMLを入れて、XSLを編集しつつCtrl+EnterでXSLT変換。

Google AJAXSLTを使ってみました。というか同梱のサンプルのスタイルをちょっといじっただけ。なんかIE6で動かないというので、フォーラム見てちょこちょこ直して動くようにしました。次のバージョンで直るとかなんとか。

とりあえずそのまま圧縮したのを置いておきます。
http://la.ma.la/misc/xsltedit/xslt.zip

ブラウザの機能で変換させたほうが速いんだろうけど、そんなに遅いわけでもない。ただ、文法的にエラーがあっても変換してくれたりするので、開発に役に立つかというと微妙かも。

XSLを書く人は普段どういうツールを使っているのだろうか。というかXSLを使いこなせる人って存在するんだろうか。