どうやら
おれは、どうも、中身より、API のデザインのほうが気になるみたいだ。中身はもちろん、すごくおもしろいけど、中身がどうよくても、API がよくなければ使えない。そのうえで、中身、と、それと API をふくめた哲学が一体となって、美しいライブラリ/フレームワークになるんだなぁ。
これは別に比喩でもなんでもなくて、他のことに言いかえると意味が剥れてしまうようだ。中身より外見のほうが気になるってのはちがうし
そして、頭がわるいこともあって、できるだけシンプルなデータ構造と、できるだけシンプルな動作がすきだ。だから、そのために多少の高速性が犠牲になっても、べつにいいと思ってる。そうもいかないこともかなり多いけど、やっぱりそういうところをいじろうとすると、戸惑う。むずかしい。高速性より、理解のしやすさ、読みやすさ、がおれのなかでは、かなり優先順位が高い。綺麗なソースがすきだ。どうしてもそうだ。
関連エントリー
- 疎結合 やっぱ、疎結合のほうがいいなぁ。中身が見えない、中身を知らない。インターフェイスだけ。必要なものは API に。API にしてないところは触...
- はてな認証API コメント欄の認証で実装したけど、メンテ中でテストできない>< API 自体は Flickr と殆ど同じなので、Flickr クラスをコピペっ...
- Acrobat Reader 高速化 via 適宜覚書はてな異本 - AdobeAcrobat7のロードをスピードアップする方法 紹介された方法だと何かうまく行かないので自力で ...
- mbed InterruptIn はネストするか 答: デフォルトではしない ARMのNVIC (Nested Vector Interrupt Controller) は名前の通りネスト可...
- はてブと del.icio.us を同時に使うように CGI 設置するのとかめんどいので xpost-del-hatena.user.js, GM スクリプトにした。 はてなの認証方法がややこし...
あああ
もっとあたまがよければ、もっとよくいきれたのになぁ……
JS のドキュメンテーションツール
JSDoc Toolkit がよさそうなんだけど、どっちにしても Java Doc 形式のコメントが好きになれないので使う気がおきなかった……(ここでまた慣れるか、オレオレ文法でいくかで悩む。でも @return とか @param とかすごいダサく感じる……)
MochiKit は python の docutil (?) とかいうのをつかってるみたいだ。スクリプトのほうには id だけつけて、rst ? っていうのにドキュメントがまとまってる。よくわかんない。
あと MochiKit はスクリプトの圧縮に rhino をつかってるっぽい (custom_rhino.jar/js.jar) けど詳細がよくわからない。Rhino/Spidermonkey は JS 内部から JS トークナイザにアクセス (Ripper みたいに) できたらいいのに……Rhino のほうは内部メソッド叩いたらできるんかなぁ。
それとドキュメントをソースコードに書くべきかどうかがいまいち迷う。RDoc はソースコードにまぜて書くけど、見通しが悪くなるんだよなぁ。でも最低限の使いかたはコメントとして近くに書いておきたいし、挙動を変更したらすぐ書きかえたいから、近くにあるのは便利だ。
あとドキュメントに実装を簡単に見れるしくみが絶対必要だと思う。長い英語よむぐらいならソース見たほうが理解できるし、ソース見てからドキュメント読むとよめるようになるし……
Twisted
正直にいうとごくごく最近まで MochiKit Deferred の由来が Twisted (Pythonの非同期ライブラリ) からきていると知らなかった……
Python のマルチスレッドってどうなってるんだろう。threading/thread とかがヒットする。
Ruby のスレッドはなんか頭つかわなくても書けるなぁ。なんでだろう……あと Ruby で Deferred は必要か、っていうのが微妙に最近ひっかかる。なんで Ruby には Deferred がないんだろう (つまり、なんでなくてもいいやって思えるんだろう)
JS だとスレッドがなくて、しかもいちいちブラウザに処理をもどさないと固まってしまうから Deferred がすごい威力を発揮するけど、スレッドがあったときに、Deferred はどう活躍するんだろう。(Python にはスレッドがあるっぽいけど、なんで Deferred が必要になったんだろう)
Ruby だと非同期な HTTP GET をやるなら
require "open-uri" # net/http はめんどいから
t = Thread.start do
open("http://example.com/") {|f|
f.read
}
end
p t.valueみたいになって、複数あつめてやるのをスレッドつかってかくと
require "open-uri"
t1 = Thread.start do
open("http://example.com/") {|f|
f.read
}
end
t2 = Thread.start do
open("http://example.com/") {|f|
f.read
}
end
[t1.value, t2.value].each do |i|
p i.length
endさらにつなげていこうとすると大変? そうでもない気がする。うーん
最初から非同期なやつだと (ぱっとでてこない)、めんどいかなぁ……
いやいやそもそも Deferred は非同期なやつをほげほげするやつなんだ。
Ruby だと非同期なメソッドがあんまりないから気にならないのかなぁ。
Greasemonkey スクリプトの構成
なんでこんなふうに書いてるの? って感があるのかもしれないから、いちおうかいとくよ!
(function () { // なんか GM ではいらなくなってるっぽいけど、一応かこう
// メインの処理
foobar...
// ユーティリティ関数たち
function $X () {
}
})();って感じで書いてる。メインの処理、その GM で一番重要なところをできるだけ上に書きたい (ただし、うえから順に読んでも理解できるように)。なのでユーティリティ関数とかは関数式ではなく関数宣言で書く。宣言で書いた場合はあとに書いても前のほうで参照できる。
foo();
function foo () {
foobar();
function foobar () {
}
}
// baz(); //=> これはできない
var baz = function () {
};
baz();でも、これだと prototype への代入とかをするのをうしろに書けない。そういうときは、
with (F()) {
var baz = new Foo();
}
function F () {
function Foo () {
return (this instanceof Foo) ? this.init() : new Foo();
}
Foo.prototype = {
init : function () { }
};
var foo = {};
foo.Foo = Foo;
return foo;
}とかやるとうまいこと分離できるぽい。
JSDeferred の jsdeferred.userscript.js は、コピペ用のコード ( http://svn.coderepos.org/share/lang/javascript/jsdeferred/trunk/jsdeferred.userscript.js ) を生成していて、これを最後にコピペして with をつかえば綺麗にかけるようにようにした。
with (D()) {
next();
}
// コピペ
function D () {
}
jQuery Deferred
名前がやっぱよくないよなぁ。General Deferred とかにすればいいのかなぁ (jQuery 部分は分離して Rakefile で一括生成)。
- JSDeferred
- General Deferred
- jKuery Deferred
- JSContinuation (Deferred って名前がウケ悪い説 でもおかしいからだめだな)
JSDeferred にしよう。作業かいし
http://svn.coderepos.org/share/lang/javascript/jsdeferred/trunk/
mini (簡易圧縮バージョン) と nodoc (コメント全削除) もコミットするようにした。rake すれば生成される。mini と nodoc は見た目の差の割にサイズがそんなに変わらない (タブインデントだし) ので nodoc だけでもいい気もする。
Rhino memo
なんか、Rhino 上で setTimeout エミュレートしてテストしようとおもったら、そもそも言語的な部分の非互換を踏んでだめすぎる……
$ rhino js> a = undefined || 1 true <= expect 1 js> a true js> a = undefined || 0 js> uneval(a) // (undefined は表示されないっぽいので uneval) undefined <= expect 0 js> undefined instanceof Object js: "<stdin>", line 1: The undefined value has no properties. <= expect false
あと Rhino のバージョンってどうやってみるんだろ。apt-cache show rhino すると
Version: 1.6.R1-0.0ubuntu3
だけど、これってめっちゃ古くて実は上の問題は新しいのだと解決していたりするのかな
setTimeout のエミュレートはよくわからんけど spawn と sync つかってかいた。けど、べつに使わなくていいよなと今読みなおしておもった……
http://coderepos.org/share/browser/lang/javascript/jsdeferred/trunk/test-rhino.js?rev=2673#L94
最新にしたらいけた……sudo apt-get remove rhino
$ wget ftp://ftp.mozilla.org/pub/mozilla.org/js/rhino1_6R7.zip
$ unzip rhino1_6R7.zip
$ cp js.jar ~/bin
$ cd ~/bin
$ cat rhino
#!/bin/sh
if [ -z "$JAVA" ]; then
if [ -n "$JAVA_HOME" ]; then
JAVA="$JAVA_HOME/bin/java"
else
JAVA=/usr/bin/java
fi
fi
"$JAVA" -jar ~/bin/js.jar "$@"
JavaScript のコンストラクタ関数
new つけてもつけなくてもいいようにするにはどう書くのがベストか的なんだろう。Deferred ではこうしたけど
function Deferred () { return (this instanceof Deferred) ? this.init(this) : new Deferred() }
// prototype.init では this をかえしてる。
// でもコンストラクタは Object をかえした場合にしかその返り値を使わないので undefined でもべつにいい。汎用的にするなら
function Foo () {
if (this instanceof arguments.callee)
this.init(this);
else
return new arguments.callee;
}かな。ただ、これだと任意の数の引数がとれない (new は括弧をつけなくていいことからも解るように関数呼びだしではないので)。
function Foo () {
if (this instanceof arguments.callee) {
this.init.apply(this, arguments);
} else {
// new のエミュレート
var f = function () {};
f.prototype = arguments.callee.prototype;
var o = new f;
arguments.callee.apply(o, arguments);
return o;
}
}どうみても素直に引数リストを書いたほうがよみやすくていい。(instanceof (がつかっている [[HasInstance]]) は [[Prototype]] をみる。new 以外に [[Prototype]] をセットする方法はない。)
てかはてなで
[[Prototype]] [] [[Prototype]] [] <- リンクはされないけど括弧が消える。
を地で書くにはどうしたらいいんだ……
ちなみに global ( (function () { return this })() ) と、this をくらべるのはよくないことがある。
jQuery Deferred だと $.deferred = Deferred とやっているわけだけど、この場合 this は $ (jQuery) になってしまうのでだめ。
Deferred
jQuery Deferred とか言っていますがコアは全く jQuery に依存しないので (いい名前がおもいつかないから jQuery バインディングのほうのなまえでよんでる) http://svn.coderepos.org/share/lang/javascript/jquery-deferred/jquery-deferred.js の Deferred 関数と必要な関数をコピペしたら GM でもうごきます。setTimeout/clearTimeout にだけ依存してるのでその実装があるならどこでもうごくはず。
他の非同期なやつを Deferred 化するのは jQuery Deferred だと
function wait (n) {
var d = new Deferred(), t = new Date();
var id = setTimeout(function () {
clearTimeout(id);
d.call((new Date).getTime() - t.getTime());
}, n * 1000)
d.canceller = function () { try { clearTimeout(id) } catch (e) {} };
return d;
}こんな感じになってます。Deferred#call がコールバック起動で、Deferred#fail がエラーバック起動です。
MochiKit のコードだと (機能がちょっと違いますが)
/** @id MochiKit.Async.wait */
wait: function (seconds, /* optional */value) {
var d = new MochiKit.Async.Deferred();
var m = MochiKit.Base;
if (typeof(value) != 'undefined') {
d.addCallback(function () { return value; });
}
var timeout = setTimeout(
m.bind("callback", d),
Math.floor(seconds * 1000));
d.canceller = function () {
try {
clearTimeout(timeout);
} catch (e) {
// pass
}
};
return d;
},引数 (value) をわたすためにチェインつかってるのがおもしろいすね。
ついでに jAutoPagerize でつかっているやつ。
function CachedResource (uri, convertfun, expire) {
var d = Deferred(); // new なしでいける。
var key = uri;
var v = {};
try { v = eval(GM_getValue(key)) || ({}) } catch (e) { log("parse error: may be uneval bug") }
d.clear = function () {
GM_setValue(key, "");
return this;
};
if (v.time && v.time > (new Date).getTime() - expire) {
log("Cache Hitted: " + key);
setTimeout(function () { d.call(v.body); }, 10);
} else {
log("Cache expired; getting... " + key);
GM_xmlhttpRequest({
method : "GET",
url : uri,
onload : function (req) { try {
var res = convertfun(req.responseText);
GM_setValue(key, uneval({time:(new Date).getTime(), body:res}));
log(key, uneval({time:(new Date).getTime(), body:res}));
log("Cached: " + key);
d.call(res);
} catch (e) { d.fail(e) } },
onerror : function (e) {
d.fail("HTTPError:"+e);
}
});
}
return d;
}
