2008-01-10
■ [biwascheme] 文字を実装した
Char関係のコードが動かなくなっていたのを修正。 ついでに文字を扱う関数(string->listとか)も実装。ああ素晴らしき現実逃避。
てかさ、
>>> "Σ".toLowerCase(); "σ"
JavaScriptすごくね?
■ [javascript] 関数を返す関数っていつ使うの?
JavaScriptでは関数を返す関数を書くことができる。
そんなのいつ使のかって?例えばこんな時に。
例
例えば俺がJavaScriptでScheme処理系を作っていたとしよう。
Schemeでは「string<?」という(変な)名前の関数を使って文字列の比較ができる。
(string<? "a" "b" "c") ;=> 真を返す
もちろん「<」だけじゃなくって、「string>?」「string<=?」「string>=?」「string=?」もあるよ。
さて、この機能をJavascriptで実装するとどうなるだろうか?
こんな感じかな。
Env["string<?"] = function(ar){ for(var i=1; i<ar.length; i++){ if(!(ar[i-1] < ar[i])) return false; } return true; }
ユニットテストも通った、大丈夫そうだ。 じゃあ次は「>」だ。
Env["string>?"] = function(ar){ for(var i=1; i<ar.length; i++){ if(!(ar[i-1] > ar[i])) return false; } return true; }
よしできた。
…って、ちょっと待って。これ、ほとんど上のコードのコピペじゃないかね?「>=」や「<=」に対しても同じことを繰り返すわけ?
もし俺がもっと貧弱な言語を使ってるならそれも致し方ないかも知れない。でもこれはJavascriptだ。 似たような関数がいくつもあるなら、自動生成すればいい。どうやるかって?そう、関数を返す関数を書けばいいんだ。
やってみよう
まず、それぞれの関数でどこが違うのか考えてみる。上の例なら、文字列どうしを「<」で比較するか、「>」で 比較するかが違うね。
というわけで、この比較処理から、上のような関数を自動生成するmake_string_comparerという関数を書いてみる。
function make_string_comparer(test){ return function(ar){ for(var i=1; i<ar.length; i++){ if(!(test(ar[i-1], ar[i]))) return false; } return true; } }
「return function(){...}」ってのがポイントだね。
これを使うと、「string>?」や「string<?」は以下のようにとってもシンプルに書ける。
Env["string>?"] = make_string_comparer(function(a,b){ return a > b }); Env["string<?"] = make_string_comparer(function(a,b){ return a > b });
なにが嬉しいか
嬉しいこと一つ目。まず、単純に、コードの行数が減る。
まだ「string>=?」「string<=?」「string=?」を実装してないけど、自動生成方式ならたった3行追加するだけで済む。
Env["string>=?"] = make_string_comparer(function(a,b){ return a >= b }); Env["string<=?"] = make_string_comparer(function(a,b){ return a <= b }); Env["string=?"] = make_string_comparer(function(a,b){ return a = b });
関数全体をコピペする方式なら、何行増えるか…考えたくもないね。 一般に、コードが増えるほど全体を把握するのが難しくなり、メンテが大変になり、バグの温床が増える。 コードをシンプルに保てるなら、それに越したことはない。
嬉しいこと二つ目。機能の追加が容易になる。
例えば、引数が文字列じゃなかったらエラーを出すように改造したくなったとしよう。 自動生成方式なら、おおもとの関数、make_string_comparerを書き換えるだけで済む。
function make_string_comparer(test){ return function(ar){ assert_string(ar[0]); //←型チェック for(var i=1; i<ar.length; i++){ assert_string(ar[i]); //←型チェック if(!(test(ar[i-1], ar[i]))) return false; } return true; } }
コピペ方式だと、5つの関数全てについて型チェックを行うように書き換えないといけない。
まとめ
- 似たような関数は自動生成することで、コードをシンプルに保てる
- JavaScriptでは、関数を返す関数を書くことによって、関数の自動生成ができる
蛇足
というようなテクニックはLisp/Schemeではごく普通の技だけど、 Rubyでは「関数の自動生成」って(できるけど)あんまりやらない気がしね?という話に 繋げたかったんだけど力尽きたので略。
修論のプログラムで、Rubyで<br>関数の自動生成使いまくってます。