ラベル AdventCalendar の投稿を表示しています。 すべての投稿を表示
ラベル AdventCalendar の投稿を表示しています。 すべての投稿を表示

2013年12月18日

Reblog,Tombloo,Tombfix

(この記事は tumblr reblogging enviroment Advent Calendar 2013 18日目の記事です。)

Reblog

いつもReblogしてる。
マウスでReblogしてる。
マウスボタン1が j
マウスボタン2が k
Tombfix の設定、ショートカット(マウス)が LEFT_DOWN + RIGHT_DOWN になってて、
マウスボタン3押すと、左クリック + 右クリックされる。

この設定がTumblr用マウスプロファイルで、Firefox のTumblr Profileと連動して勝手に切り替わる

マウスのプロファイルはアプリ/コマンド別にいくつか定義してて、
コード書く時は Ctrl, Shift, Alt, BS, Enterボタン、Ctrl+S の定期実行 切り替えボタン、 あと function とかテンプレートでぽちぽちやってる。
マウスカーソル、クリック、キー操作のマクロは Lua で組んでるけど、クリップも含めマクロの管理がひどいのでどうにかしたい。
最近だと JavaScript でクリップボードマクロ書けるようにした けど、実行環境どうにかならないかなって探ってる (Nodeで実行したかった…。SpiderMonkey, NILScript でいけたかも…)。
こんな環境は参考にならないだろうし、どうせハードに依存するなら、脳波Reblog してみたい。

Tombloo

Reblog 始めたと同じくらいに Tombloo を知って、 なにこれすごい、どんだけサービス対応してるの!?
そん
なに
ってTombloo のソースコード読んでたら、楽しくて。いつの間にか JavaScript のことばかり考えるようになった。 以前から ECMAScript が好きで、mozilla のソースコードとか見てたりしていたけど、 やっぱり Tombloo で衝撃受けて、普段利用するサービスがどんどん増えて、 パッチという存在を知って、なんとか書けそう、Reblog 環境, もっとこうしたい、ああしたいと、 よくわからんパッチばかり書くようになって 気付いた時には Tombloo が自分にとって一つの開発環境みたいなものになってた。
いつの間にか道を外れて迷子になることが多い。

今でも、ライブラリとかコード読んでる時、最終的に Tombloo (Tombfix) を見てることが多い。
1個1個の小さな処理、reduce とかの流れや deferred-chain, addBefore/addAround とか、 XPCOM を constructor にして自然に new して違和感なく扱ってたり, 今でも学ぶことが多いし発見がある。コードを追っていて楽しい。
あれだけ多くのサービスに対応できる機能が詰まっていて、 extractors でページ内容抽出、models でサービスにPOST。
大雑把に言えば、メンテナンスのほとんどは models と extractors だけみてれば済むようになってる。 service, ui, action、構造。アーキテクチャがきれい。 今までほとんど 1つの処理、1つの区切り、小さな粒ばかり見てきてしまっていた気がする。
本を読んでたつもりが、好きな単語やフレーズを見つけて満足し、ストーリーはもう頭の中になかった、 そんな Quote みたいな。

SoundCloudPlayer

最近、SoundCloudPlayer という Firefox アドオン を作っていて、 ある程度動くようになったものの、全体をあらためて見た時あまりにもひどい作りで 勝手に自分でガッカリした。
単に1個の Object にどんどん関数つっこんでるだけで、デザインパターンも何もない。
"とりあえず動くようにしよう" 的な作り方を, 改善したい
今は 別のブランチ で書き直し中。
自分でもたまにトラック作っているから (とってもチープなものだけど…)、 作り手からすると、SoundCloud 上で聴いてもらって再生数とかLikeが増えるのが嬉しい (もちろん DL されてローカル再生も嬉しい)。 どことなく Tumblr notes に似てる。 SoundCloudPlayer はあくまでサブのプレーヤで、メイン画面 (アクティブな SoundCloud のタブ) へのショートカット的な、そんなサポート的な位置にいられたらなって思う。

Tombfix

Tombfix で対応したいことは、Issues の解決、できそうなのあるかなって見てはいるけど、 多少構造を変えないと対応できなさそうだったり、単に pull req して close。といかなそうなのが増えてきて、 たいした考えは浮かんでない。けど、できる限り関わっていきたい。
あと、パッチの整理。できれば統合したい。
何も考えずに ちょっとこうしたいなって思ったらなんでもパッチにしてたものだから、 数が多すぎて中身も冗長で自分で書いたのがよくわかんなくて、もう手がつけらんない。
当時使ってたサービスも最近は使ってなかったりとかあって、動作チェックまでたどり着いてなかったりするのも多い。
需要がありそうなもの等で優先しながら、本体に取り入れるか、pixiv パッチのように最小限で分離するか、 tombfix/patch に入れるか、止まらない程度に進めたい


Models/Extractors

Tombfix において 各サービスを集約してる Models / Extractors について。

例えば Tumblr dsbd の UI が変わった時、ほんの少しの変化、div の className が変わっただけでも動かなくなる可能性があり、 そのたびに Extractors.Tumblr を修正して push してリリースしなければ対応できない現状。
ひどい例とすると icon, favicon の URL が変わっただけでも対応が必要で (ICON がボタンになったりしてるから)、 少なくとも応急パッチみたいので対応してきた。
この問題は以前から指摘されてる




Models と Extractors を外に出す。 syoichi さんも言っていたように、「日頃そのサービスを使ってる人が対応」できると理想的。 それには pull request して本体のリリースを待つより、簡単なステップで対応できるような何かが必要と思う。

Models と Extractors を本体のリリースに依存しないよう別の場所に置く

GitHub とかに公開されたパッチ (script) の URL は、そう変化するものじゃないとして、
  • インストールした script の URL を保持しておく
  • 起動時、script をローカルから読み込む前に、保持している URL を調べ、script が更新されてないか確認する
  • 更新されてたら script をアップデート。その際、必要に応じて確認ダイアログを表示
これによりアップデートが自動化され、利用者はサービスの UI の変化や、「また動かなくなった!」など気にしないで使用できる。 (動かなくなった時の対応が速ければ…)。
若干思っていることは、1つのレポジトリに集約しようとすると、Tombfix 自体の拡張性を縛ってしまっているんじゃないかなって。
「野良パッチ」と呼ばれるくらい、Tombloo/Tombfix のパッチはいろんなところにあって、 今更パッチのフォーマットを決めようとか、@updateURL どうのこうのとか、めんどくさいし、ルールが増えるほど書き手も減ると思う。 (自分で 一括インストールパッチ とかでフォーマット決めておいてアレだけど…)。
パッチフォルダ内の script は現状、ソート順に読み込まれる。 これを、例えばフォルダ内に .json とかで、サービス名と models / extractors の URL が書いてあるファイルがある場合、 読み込み順序や自動更新など扱いやすくなるかもしれない。
直列になってるパッチの読み込みを、Tree な構造にするとか、 script は addBefore/addAround により、CSS のようにカスケード状に扱えるようになっていて、まだ具体的にこうすればいいかもって浮かばないけど、いい方法はありそう。

とか書いてたら、Taberareloo Canary !? と、驚きと感謝を込めて

おしまい

なんだかこれといった内容のない記事になってしまいました…。
明日は poochin さんです。よろしくお願いします!

2011年12月17日

JavaScriptの無名関数の実行 (function(){})() と (function(){}()) の違い


JavaScript Advent Calendar 2011 (オレ標準コース) 17 日目、polygon_planet です。

ずっと Advent Calendar 参加してみたいなぁと思ってたんですが
ネタが思いつかない日々で半分諦めてたんですが、考え過ぎな気がしてきたので
別におもしろい記事でもないし、技術的にも参考になるのか不明ですがとりあえず書きます。

もしかしたら同じようなこと解説してる記事がすでにあるかも…(うまく検索できてない)


JavaScript で無名関数をその場で実行するとき、
(function() {
// 処理
})();
という書き方が主流っぽいですが、
(function() {
// 処理
}());
こう書いたらどう違うのか。

ちなみに、
function() {

}(); // エラー
これは式ではなく文として扱われるためエラーです。
ただし、以下のように実行する場合はエラーではありません。
var arr = [function(){}()];        // [undefined]
var obj = {foo: function(){}()}; // {foo: undefined}

(function(){})() vs (function(){}())

どちらも同じように実行され、結果的には同じなので
とりあえず、無名関数が生成され実行するまでの JavaScript コードの過程を追ってみます。

今回は手元に SpiderMonkey に付いてる JavaScript shell しかなかったので、それを使います。
わりと前にコンパイルしたやつですが JavaScript バージョンは 1.8 なので現行と大差ないと思います。
中間コードは js shell の dis で逆アセンブルして確認します。

まず (function(){})() から。
$ js
js> dis(function() { (function(){})() });
flags: LAMBDA INTERPRETED
main:
00000: anonfunobj (function () {})
00003: group
00004: pushobj
00005: call 0
00008: pop
00009: stop

Source notes:
0: 5 [ 5] pcbase offset 5
anonfunobj というは、無名関数を定義していると思われますが、
確認のため Mozilla のソースコードを見てます。

今回は js shell のバージョンに合わせるため
/mozilla-central/ ではなく /mozilla/ を参照します。

jsopcode.tbl
305 /* Auto-clone (if needed due to re-parenting) and push an anonymous function. */
306 OPDEF(JSOP_ANONFUNOBJ, 128, "anonfunobj", NULL, 3, 0, 1, 19, JOF_OBJECT)
このあたりの JSOP_* から参照できます。
JSOP は jsopcode.c あたりで確認できます。
3777               case JSOP_ANONFUNOBJ:
3778 #if JS_HAS_GENERATOR_EXPRS
3779 sn = js_GetSrcNote(jp->script, pc);
3780 if (sn && SN_TYPE(sn) == SRC_GENEXP) {
3781 JSScript *inner, *outer;
3782 void *mark;
3783 SprintStack ss2;
3784
3785 LOAD_FUNCTION(0);
3786 inner = fun->u.i.script;
// ... (長いので省略) ...
ここでは LOAD_FUNCTION が使われています。

JavaScript バイトコードインタプリタの jsinterp.c を参照して無名関数の生成から追ってみます。
5744           BEGIN_CASE(JSOP_ANONFUNOBJ)
5745 /* Load the specified function object literal. */
5746 LOAD_FUNCTION(0);
5747
5748 /* If re-parenting, push a clone of the function object. */
5749 parent = js_GetScopeChain(cx, fp);
5750 if (!parent)
5751 goto error;
5752 obj = FUN_OBJECT(fun);
5753 if (OBJ_GET_PARENT(cx, obj) != parent) {
5754 obj = js_CloneFunctionObject(cx, fun, parent);
5755 if (!obj)
5756 goto error;
5757 }
5758 PUSH_OPND(OBJECT_TO_JSVAL(obj));
5759 END_CASE(JSOP_ANONFUNOBJ)
ここでも LOAD_FUNCTION が使われているので定義を追ってみます。
jsinterp.c
2604 #define LOAD_FUNCTION(PCOFF)                                                  \
2605 JS_GET_SCRIPT_FUNCTION(script, GET_FULL_INDEX(PCOFF), fun)
JS_GET_SCRIPT_FUNCTION を実行するマクロになっています。

じゃあ JS_GET_SCRIPT_FUNCTION はどうなってるの
jsscript.h
143 #define JS_GET_SCRIPT_FUNCTION(script, index, fun)                            \
144 JS_BEGIN_MACRO \
145 JSObject *funobj_; \
146 \
147 JS_GET_SCRIPT_OBJECT(script, index, funobj_); \
148 JS_ASSERT(HAS_FUNCTION_CLASS(funobj_)); \
149 JS_ASSERT(funobj_ == (JSObject *) STOBJ_GET_PRIVATE(funobj_)); \
150 (fun) = (JSFunction *) funobj_; \
151 JS_ASSERT(FUN_INTERPRETED(fun)); \
152 JS_END_MACRO
JSFunction にキャストして JavaScript 関数オブジェクトになることが確認できました。
(これは現行の JavaScript だともっとてっとり早くなってる)

こんな感じで追ってみると、anonfunobj は無名関数の生成ということがわかります。

次の group は、
jsopcode.tbl で同じく定義されています。
317 /* Parenthesization opcode to help the decompiler. */
318 OPDEF(JSOP_GROUP, 131, "group", NULL, 1, 0, 0, 19, JOF_BYTE)
jsopcode.c では以下のあたり。
2066               case JSOP_GROUP:
2067 cs = &js_CodeSpec[lastop];
2068 if ((cs->prec != 0 &&
2069 cs->prec <= js_CodeSpec[NEXT_OP(pc)].prec) ||
2070 pc[JSOP_GROUP_LENGTH] == JSOP_NULL ||
2071 pc[JSOP_GROUP_LENGTH] == JSOP_DUP ||
2072 pc[JSOP_GROUP_LENGTH] == JSOP_IFEQ ||
2073 pc[JSOP_GROUP_LENGTH] == JSOP_IFNE) {
// ...(中略)...
2100 }

無名関数オブジェクトを生成し、それを括弧 () によって式とし、
そしてそれを call というような流れになっています。


続いて (function(){}()) のほうも同じように。
$ js
js> dis(function() { (function(){}()) });
flags: LAMBDA INTERPRETED
main:
00000: anonfunobj (function () {})
00003: pushobj
00004: call 0
00007: pop
00008: stop

Source notes:
0: 4 [ 4] pcbase offset 4

なんと、(function(){})() と比べると
group がないだけで、ほかは同じになってしまいました。

無名関数に対する Parenthesization が発生しないので、
ちょうど、上で書いた [function(){}()] のように実行されています。

この結果から、

(function(){})() よりも、 (function(){}()) のほうが
コストが減るとわかりました。


とは言っても、どちらを使用しても実行時間が変化するとしてもマイクロ秒単位などで
JavaScript でそこまでクリティカルな処理が存在するのかも微妙なところで
あまり気にしないでいい気がします。


でも一応ベンチマークをとってみます。
js> for (var t = +new Date, i = 0, f; i < 10000000; i++) f = (function(){}());
print(+new Date - t);
10437
js> for (var t = +new Date, i = 0, f; i < 10000000; i++) f = (function(){})();
print(+new Date - t);
10485

結果:
  • (function(){}()) : 10437 ms.
  • (function(){})() : 10485 ms.

たしかに (function(){}()) のほうが速かったんですが、
10,000,000 回ループして わずか 48 ミリ秒の違い。
これを懸念してる人って…(いるのでしょうか)


そういえばこの違いについてはちょっと前に Twitter で話題(?) になって気がします。
もしかしてもうまとめられてたのかな…。だとしたら……考えたくない。


とりあえずモヤモヤした感じがなくなったのでよかった。
たまにソースコード確認してみると新鮮デスネ!