2005-10-30

Emacsの本を買った。

Emacsの本を買ったけど1時間で挫折して萌ディタを使っている。

萌ディタTips : Saveと同時にSleipnirを更新

f.onSave = function(){
    var pnir   = new ActiveXObject("Sleipnir.API");
    var tabid  = pnir.GetDocumentID(pnir.ActiveIndex);
    var window = pnir.GetWindowObject(tabid);
    window.location.reload(true);
}

srcfile.javascript.txtなんかに加えるとファイルを保存するたびにSleipnirのアクティブタブをリロードするようになる。CSSとかいじってるときにとっても便利。

Emacsとかダメだ、キモい。vimとかもっとキモい。無理。

2005-10-29

Amazonのカートが仕様変更してるっぽい

http://www.drk7.jp/MT/archives/000942.html

エラー画面がしばらくほっとけば直るようにも読めるんだけど多分仕様変更だと思う。なんかAmazletのカートに入れるボタン動かなくなってるし、はてなのも動かなくなってるな。他にも動かなくなってるアプリがちらほら。GoogleのデフォルトエンコーディングがUTF-8になったときのことが思い出されるような。

あくまでフォームを勝手に呼び出してるだけなので、いつ動かなくなってもおかしくはないんだけど、こういう些細な変更でも告知とか出してくれたほうが親切な気がするんだけど単に担当者がいないんだろうな。

Amazonとしては「API公開してるんでそっち使ってね、仕様変更はこっちの勝手」とか、そういう立場なんじゃないかと思う。似たような事例としてはRSS配信してるからデザインがダサくてもオーケーとか。そういう。

外部と連携するようなサービスで、フォーマットを変更する可能性がある場合には互換性を保ちつつ仕様変更できるようにURLにバージョン番号や日付なんかの識別子を含めるようにするのが良いと思う。AmazonのECSなんかはすでにそうなっているし、円滑にバージョンアップするためにはとても重要なことだと思う。

Amazon最速検索を作るときに参考にしたのは

チュートリアルのPDF
これ印刷すれば知りたいことは大体全部載ってるんだけどこのファイルの場所がわかりづらすぎる。
http://forums.prospero.com/n/mb/message.asp?webtag=am-assocdevxml&msg=6951.2&ctx=4096

カートに入れるフォームの作り方。サンプルはGETになってるけどPOSTでも動く。
http://aws.typepad.com/aws_jp/2004/12/ecs.html

ここらへんか。

2005-10-18

Winnyの技術のPDFがやっと公開

公式に流すっていうアナウンスがあったので読んでから買おうかと思って待ってたんだけどなかなか流れて来ないからもうすでに丸善津田沼店で買っちゃったんだけど。

PDF(一般書籍) [金子勇] Winnyの技術 [05-10-03].zip 1,900,259 9d2dd618c580e38ea6869c51d9ed1107

サイズは小さいけど本物。テキストデータなのでこんなもんだろう。
Amazonで品薄状態なので今すぐ読みたいって人はPDFで読むと良いかも。
まあ買ってから気づいたんだけど、今すぐ急いで読まなくちゃいけないというような本ではなかった。

もうしばらく待ってる。

2005-10-17

Fasterfoxが酷すぎる件

Fasterfox
http://fasterfox.mozdev.org/

Fasterとか見るとつい試してみたくなる衝動に駆られて試してみたんだけど、これはひどいなと思った。
リンク先読み機能ってのがデフォルトで有効になっているんだけど



先読み機能は他にダウンロードしている場合はそちらに優先権を譲るので競合して帯域幅をパンクさせることはありません。それどころか使用していない帯域を有効活用する機能です。

どっちも自分のことじゃないか、相手のサーバーのことはどうでもいいのか。まあ俺は基本的には帯域幅よりサーバー負荷より人間の1分1秒のほうが貴重だとは思ってるんだけど。

del.icio.us開いたら片っ端から先読みし始めるし、既読のページにもかかわらず先読みするし節操が無い。読みもしないページを片っ端からダウンロードすることが有効活用と言えるのか?
もしもローカルネット内でキャッシュプロキシが利いているという前提ならばこういう実装でもいいとは思うが。
このブログにもたまに物凄い勢いでアクセスしてくるFirefoxがいるんだけど、多分この拡張が原因だろう。

少し前にGoogle Web Acceleratorというソフトが公開されてすぐに配布停止して今また配布再開しているんだけども、
Google Web Acceleratorは

- 次に読む確率が高いブログの前後のエントリ
- リンクに対してマウスオーバーした瞬間

なんかに先読みをしていた。さらには相手のサーバーに負荷をかけないようにGoogleがクロールしたキャッシュを使う。それに比べるとFasterfoxはひどいっていうか知性が足りないように思う。

とまあ、ここまで書いといてアレなんだけどレンダリング速度の測定に使えるので結構便利。

高速化に関しては何にもしなくてもFirefox1.5 beta2で随分速くなってるし、それでもまだOperaのほうがレンダリングや戻る進むは速いかって感じだったり、そんなことよりもRSSリーダーを使ったり食生活の改善に取り組んだりしたほうが効果的なのではないか、と思った。

一個しか更新してないのにBloglinesで上がりまくる件に対処

多分これで直ってると思うのでテスト。

「俺も原因がよくわからんのだよね」などと言ってたのだけどテキスト整形するときに上から何番目に表示されているかでIDを振っていて、それがRSSにも含まれていたのが原因。

差分を強調表示してくれるRSSリーダーとかあれば早く気づいたんだろうけど。
たまに文字化けしたりするのはFeedburnerのせいだと思う。

2005-10-09

[速報] GoogleのRSSリーダー(β版)、早速真似して作ってみた

GoogleからRSSリーダーがリリースされましたね。
http://www.google.com/reader/

ソース読むのとか面倒なんで真似して作ってみました。だいたい6時間ぐらいで出来た。
http://la.ma.la/roll.html

このBlogで使ってるjsファイルをちょっといじって使ったので、キャッシュ利いてて動かない場合はリロードしてみてください。

Firefoxだとアニメーションがやや重い。アニメーションを無くせばもっとサクサク動くんだけどGoogleReaderよりは軽いんじゃないかと思う。
Operaはなんかスクロールしていくと強制終了します。アニメーションさせつつ、上下の要素を継ぎ足すようにしてやれば多分大丈夫だとは思うけど面倒なので現時点ではそこまでやってない。

あとはIEとFirefoxではホイールを使ってページ切り替えられるようになっています。
Googleの人はホイール知らないんだろうか。

2005-10-06

実践JavaScriptリファクタリング

同じ事をやるにも、いろんな書き方があるわけでいかにして短くてわかりやすいコードを書くかというノウハウを紹介します。
例として"abcde"を80回繰り返した文字列を作るとして実際に自分のコーディングスタイルがどんな風に変化していったのか、という。

短くなるのは確かなんだけどわかりやすいかというと、人によるかもしれない。

グローバル関数を定義

2年前なら、多分こういう具合だった。
//ふつうに関数として定義する
function x(str,num){
    var tmp = "";
    for(var i=0;i<num;i++){
        tmp += str;
    }
    return tmp;
}
x("abcde",80)

Stringのメソッドとして定義

1年前だとこんな感じ。
//Stringのメソッドとして定義する
String.prototype.x = function(num){
    var tmp = "";
    for(var i=0;i<num;i++){
        tmp += this
    }
    return tmp;
}
"abcde".x(80)

最近になって短くかけるところはなるべく短く書くようにしている。
//forの初期化ついでにtmpも宣言、{}を省略
String.prototype.x = function(num){
    for(var i=0,tmp="";i<num;i++) tmp += this;
    return tmp
}

こんな具合。見た目がすっきりする。

配列を使うようにする

ちょっとパフォーマンスが気になるので、高速化してみる。
文字列を加算していくのは、計算の途中で、
abcde、abcdeabcde、abcdeabcdeabcde
という文字列オブジェクトがその都度生成されていくのでメモリにやさしくない。
巨大な文字列の連結にはjoinを使ったほうが良い。数が大きくなると速度に差が出てくる。
//配列にpushしてjoin
String.prototype.x = function(num) {
    var tmp = [];
    for(var i=0;i<num;i++){
        tmp.push(this)
    }
    return tmp.join("")
}
//短く書くとこうなる
String.prototype.x = function(num){
    for(var i=0,tmp=[];i<num;tmp[i++]=this);
    return tmp.join("")
}

メソッドを使いまわす←いまここ

配列を埋めるArray#fillを作って、使いまわすようにする。
Array.prototype.fill = function(v){
    for(var i=0;i<this.length;this[i++]=v);
    return this
}
String.prototype.x = function(num){
    return Array(num).fill(this).join("")
}
"abcde".x(80)

これでString.prototype.xは、一行に収まった。
長さ80のArrayを"abcde"で埋めて連結する、と、意味そのままのコードになる。

もしも、あらゆるforループや一時変数を排除したいならば、次のように書くこともできる。
Array.prototype.fill = function(v){
    return this.map(function(){return v})
}
String.prototype.x = function(num){
    return Array(num).fill(this).join("")
}

まあ、どのみちArray.prototype.mapを自前で定義するならforループが必要ってことにはなるんだけど。
これは"abcde".x(10000)だったら1万回function(){return v}が呼ばれるので、動作が遅くなる。

※Array.prototype.mapはFirefox1.5でサポートされる新しいArrayのメソッドを参照。

まとめ

ビルトインオブジェクト拡張してしまうというのは、まあ行儀悪いと言えば行儀悪いんだけど、JavaScriptのprototypeベースとかオブジェクト指向とかの仕組みを理解するのには多分一番の近道なんじゃなかろうか。と思う。

実践JavaScriptリファクタリング、その2

連載すんの?
リファクタリングとか嘘で実は実践ビルトインオブジェクトハックなんだけど。

例題
配列 a = [3,5,4,2,1] から一番小さな値と、一番大きな値を取り出すにはどうすればいいか。

多分昔はこんな風に書いてたと思うんですよ。
a = [3,5,4,2,1];
for(i=0;i<a.length;i++){
    if(i == 0){
        min = a[0];
        max = a[0];
    }
    if(min > a[i]){min = a[i]}
    if(max < a[i]){max = a[i]}
}


模範解答として、後先考えないやり方を提示しておく。
a = [3,5,4,2,1];
min = a.sort().shift();// 1
max = a.sort().pop();  // 5

短い。ただし、これをやるとaの内容は並べ替えられて最初と最後の要素が取り除かれる。
a // 2,3,4

内容を破壊してもかまわないなら、こういうやり方がある、ということを踏まえて次に進む。

配列の内容をコピーする

ありがちな間違いとして次のようなものがある。
a = [1,2,3,4,5];
b = a; // コピーしたつもり
b.shift() // 1
a // 2,3,4,5

aもbもメモリ上のどっかにある[1,2,3,4,5]という配列への参照でしかない。
b = a は別の名前でアクセスできるようにしただけであって、コピーではない。
だから、bの内容を破壊するとaの内容も破壊される。

aと同じ内容の、別の配列を作るには次のようにする。
// ごくふつうの、配列のコピー
a = [1,2,3,4,5];
b = [];
for(var i=0;i<a.length;i++){
    b[i] = a[i]
}

Arrayのメソッドとして定義してやると、次のようになる。
Array.prototype.clone = function(){
    var tmp = [];
    for(var i=0;i<this.length;i++){
        tmp[i] = this[i]
    }
    return tmp
}
a = [1,2,3,4,5];
b = a.clone();
b.shift() // 1
// bのみ破壊される
a // 1,2,3,4,5
b // 2,3,4,5

配列のクローンを作成するのには、実はもっとスマートで速い方法がある。
これに気付いたのは最近なんだけど。
Array.prototype.clone = function(){
    return Array.apply(null,this)
}

[1,2,3,4,5].clone() は Array(1,2,3,4,5) を呼び出す。

これを使って最初の例題を解くとこうなる。
Array.prototype.clone = function(){
    return Array.apply(null,this)
}
a = [3,5,4,2,1];
min = a.clone().sort().shift();// 1
max = a.clone().sort().pop();  // 5
a // 3,5,4,2,1

オリジナルの配列の内容は変更されない。

破壊しないソート

そもそも、sortが破壊的なのがおかしいんじゃないのか、とかいう疑問を感じる。
http://namazu.org/~satoru/blog/archives/000043.html

Rubyではsortが非破壊、sort!が破壊的、という区別がある。
Perlでも「@a = sort(@a)」と明示的に代入しなければ元の配列の内容は書き換えられない。
// JavaScriptのsortは破壊的である
a = [5,4,3,2,1];
a.sort();
// aの内容が置き換わる
a // 1,2,3,4,5

Array#sortの機能を置き換えて、オリジナルの配列が変更されないようにするには、次のようにする。
// オリジナルのsortメソッドを保存
Array.prototype.sortIt = Array.prototype.sort;
// コピーしてソートする
Array.prototype.sort = function(){
    var tmp = this.clone();
    return tmp.sortIt.apply(tmp,arguments)
}
a = [5,4,3,2,1];
b = a.sort();
a // 5,4,3,2,1
b // 1,2,3,4,5

これはかなり邪悪なハックだ。
sortが破壊的であることを期待しているコードは動かなくなる。もはや全然リファクタリングではない。

ソートが非破壊だと、cloneを省略して次のように書けるようになる。
a = [3,5,4,2,1];
min = a.sort().shift();// 1
max = a.sort().pop();  // 5
a // 3,5,4,2,1

firstとlastを定義

shiftとpopが少し気持ち悪いので最初の要素と最後の要素を参照するメソッドを定義してやる。
// Rubyのfirstとlastメソッド移植
Array.prototype.first = function(size){
    return (size == undefined) ? this[0] : this.slice(0,size)
}
Array.prototype.last = function(size){
    return (size == undefined) ? this[this.length-1] : this.slice(this.length-size)
}
a = [3,5,4,2,1];
min = a.sort().fisrt(); // 1
max = a.sort().last();  // 5

これで、minは並べ替えた最初、maxは並べ替えた最後、と、定義そのままのコードが完成する。
実際のところ配列が巨大になるとソートが非常に遅くなってしまうのだけど、まあ100や200の配列なら問題は無いだろう。

まとめ

Array.prototype.clone = function(){
    return Array.apply(null,this)
}
Array.prototype.sortIt = Array.prototype.sort;
Array.prototype.sort = function(){
    var tmp = this.clone();
    return tmp.sortIt.apply(tmp,arguments)
}
Array.prototype.first = function(size){
    return (size == undefined) ? this[0] : this.slice(0,size)
}
Array.prototype.last = function(size){
    return (size == undefined) ? this[this.length-1] : this.slice(this.length-size)
}
Array.prototype.min = function(){
    return this.sort().first()
}
Array.prototype.max = function(){
    return this.sort().last()
}
a = [3,5,4,2,1];
min = a.min(); // 1
max = a.max(); // 5

なんか、最初より長くなってるけどArray.prototypeは別のファイルに書いておけば後は使い回しが利くので、コードの記述量はどんどん減るようになる、というわけ。

あとはruby.jsのソースなんかを読むと良い。メソッドチェーンを使うことで殆どの機能は、より簡潔に書くことができる。
http://www.advogato.org/proj/Ruby.js/

番外編

中身を数値に限定するならばこう書ける。実はこれが一番高速だったりする。
Array.prototype.min = function(){
    return Math.min.apply(null,this)
}
Array.prototype.max = function(){
    return Math.max.apply(null,this)
}

ただこれは、配列が空だとInfinityを返してしまうとか、そういう問題はある。

2005-10-05

JavaScriptによるQRコード生成ライブラリ

ってのを作りました。
http://la.ma.la/misc/qrcode/

ネタのつもりで作ってたんだけど意外と大変だった。というか時間かけすぎた。
なんの役に立つのかと言われたら何の役にも立たないと自信を持って言える。

Ruby用QRコード生成クラスからの移植です。ライセンスはオリジナルに準拠します。
http://www.swetake.com/qr/

QRコードの仕様とかアルゴリズムとかそういうのは全然わかりません。挙動が同じになるようにしてみただけです。
表示にはテーブルタグを使っています。画像オフでも表示できます。

動作テストには
http://www.psytec.co.jp/docomo.html
を使いました。

アーカイブにdatファイル同梱したのでファイルサイズが2MB超えてます。
ソースだけ見たい人は、これをどうぞ。当然これだけじゃ動かないけど。
http://la.ma.la/misc/qrcode/qrcode.js

動作について


遅いです。最初は文字入力されるたびにリアルタイムで生成するようなのを考えていたのですが、それだと多分10GHzぐらい必要です。
遅いから何の役にも立たないねっていうことなんだけど、速かったら役に立つのかと言われても疑問。

IE、Firefox、Opera8.5で動作確認しています。Firefox推奨。
生成には1GHzのマシンで5秒から10秒ぐらいかかると思います。テーブルタグの描画ではなく、実際の計算に時間がかかってます。

速度はOperaがやっぱ速いんだけど、なんか出力結果が他のブラウザと違う。
読み取れてるので細かいことは気にしない、さすがQRコードだ、とかいう。
まあそういう、すげーいい加減な具合なので変換結果を信頼しないでください。

日本語は変換できません。できるんだけど対応してるリーダーがないとか(後述)。

Rubyからの移植にあたって


あまり最適化とかはしないで、なるべくオリジナルのコードからの変更点が少なくなるようにしています。
セミコロン省略なんかは、本来あまりやらないほうが良いです。
ruby.jsからString#scan と MatchDataオブジェクトを使っています。
String#countを作ってありますが、これはRuby互換ではありません。とりあえず動くようにやっつけ仕事。
Fileクラスも作ったが、これはopen、readのみ。
String#packとString#unpackはオプションとか良くわかんないのでこれも適当。

最初は慣れ親しんだPerlから移植しようかと思ってたんだけど、Rubyからの方がずっと楽っぽい。
ボトルネックは正規表現オブジェクト作成とか、そこらへんだと思う。いちおう、高速化の余地はある。

工夫したところ


Rubyっぽく書いたコードをJavaScriptに変換する関数を作っています。
ruby_slice_supportってのを書いて
// before
hor[k * max_modules_1side,0] = (170).chr()
// after
hor = hor.ruby_slice(k * max_modules_1side,0).eq((170).chr())

こんな具合に置換するようにした。

Rubyだと、string[offset,length]で部分文字列の切り出しや置き換えができるんだけど、JavaScriptだとstring[(offset,length)]と見なされてstring[length]と同じ意味になる。
さらにはstring[/regexp/]なんかができてすげーカッコいいんだけど、これはJavaScriptだとstring[/regexp/.toString()]になる。つまりstring["/regexp/"]と同じ意味になる。
[]の中身が[offset,length]の形式だったら.ruby_slice(offset,length)に置換して、切り出し結果にeqメソッドくっつけて左辺に使われた時の挙動をエミュレート、っていう具合。

あとはforcePrivateってのを書いた。これは関数内で使われている変数名を拾ってきて強制的にvarで宣言してプライベート変数にする、というのもの。
some_function = some_function.forcePrivate();

とかやると、その関数内で使われてる変数が全てプライベート変数になる。
ただし関数をnew Functionで再構築するため、変数のスコープとか一切関係なくなってしまう(クロージャとしては使えない)。
スタティックな関数に適用するなら、まあそれなりに便利かもしれない。

関数オブジェクトを文字列に落として正規表現でゴニョゴニョフィルターかけてやれば色々できるね、って言う話なんだけど。
ただ、IEだと大きめの関数を生成すると死ぬほど重かったり、Operaだとなんか良くわからないエラーで失敗したりするので、あらかじめFirefox使って変換した結果を使うようにしたりしている。
function.toStringは、なんかブラウザごとに勝手に整形されてたりして個性があるので、あんまり特定の動作を期待して使うべきじゃないと思われる。

数値から文字列


Number.prototype.chr = function(){
    return String.fromCharCode(this)
}

(170).chr()はRubyだと170.chrなんだけど、数値からメソッド呼び出すときに小数点と区別付かないからカッコでくくらないとエラーでるとか(170).chrはfunctionで(170).chr()でchrメソッド実行とか、そういう違いがある。
このあたりも正規表現置換でなんとかしようかとも思ったんだけど、JavaScriptだと関数オブジェクトと関数の実行を明示的に区別することは重要なので手動で書き直した。
chr(170)の方が普通に楽だな、と思ったんだけど、なるべくRubyのコードいじらないというポリシーだったのでこんな具合になったり。
Firefoxだとgetter/setterが使えるので、(170).chrまでならなんとかできそうだけど。
String.fromCharCodeで生成した文字列は0-65535までなら、その文字コードに対する文字の割り当てが存在していなくてもきちんと文字コードを保持してくれる。

バイナリファイルの読み込み


他の言語のバージョンと同じように計算済みのdatファイルというのを使います。これは計算時にXMLHttpRequestを使って受信しています。
ただし、XMLHttpRequestのresponseTextは、バイナリファイルを文字化けしたテキストデータとして認識するので、うまくいかない。
なのでバイナリデータの読み込みには、あらかじめ16進数ダンプしたテキストファイルを作っておいて、読み込み後にpackするという動作になっています。

仕様:画像保存できません


QRコードの描画にはテーブルタグを使っています。画像での出力はできません。
Firefoxに限れば将来的にcanvas要素とdataスキームを使って画像保存とかできるようになるかもしれません。

仕様:京ぽんで動きません


XMLHttpRequest使えないので。そのほかにも色々問題あると思う。
使うデータをあらかじめscript本体に記述するようにすればイケルとは思うんだけどデバッグが大変なのと現実的な速度で動作するとは思えないのでやる気なし。

仕様:マルチバイト文字について


基本的に対応してません。URLやメールアドレスなんかはOKですが日本語は変換できません。
一応、UTF-8文字列を文字列単位でなくバイト単位で区切って渡すようにはしてあるので、「UTF-8を読めるQRコードリーダー」もしくは「読み取り結果をバイナリファイルで保存できるQRコードリーダー」があれば読めるはず。
ShiftJISが実質標準っぽいので、気が向いたらShiftJIS変換ルーチン組み込んだのを作ります。
気が向いたらというのは気が向いたらやるということで、気が向いたらやります。期待しないでください。