「JavaScript のクロージャ と オブジェクト指向 」のつづき
Ruby の Enumerable
Ruby ではコンテナの役割を持つクラスに Enumerable モジュールをインクルードし、その要素を順にブロックに与えるメソッド each を定義することにより、要素に対する便利なメソッドがいくつか使えるようになる。
例えば、「人」は `名前' と `年齢' を属性として持ち、 「グループ」 に所属しているとする。
# 人 class Person attr_reader :name, :age def initialize(name, age) @name, @age = name, age end def to_s @name + " " + @age.to_s end end # グループ class Group include Enumerable def initialize @persons = [] end def add(ps) @persons << ps; self end def to_s @persons.join(",") end def each @persons.each{|ps| yield ps} end end g = Group.new. add(Person.new("Hanako", 15)). add(Person.new("Jiro", 15)). add(Person.new("Tarou", 21)) g.each{|ps| p ps} p g.map{|ps| ps.age * 2} #=> [30, 30, 42] p g.select{|ps| ps.age < 20} #=> [Hanako 15, Jiro 15] p g.inject(0){|result, ps| result + ps.age} #=> 51 p g.inject(""){|result, ps| result + ps.name} #=> "HanakoJiroTarou"
ここで使われている yield とは、
自分で定義したブロック付きメソッドでブロックを呼び出すときに使います。 yield に渡された値はブロック記法において
|
と|
の間にはさまれた変数(ブロックの引数)に代入されます。
この yield の動作はイメージしにくい。理由は、ブロックの呼び出しをするために特別な構文が用意されていることと、当の呼び出し先のブロックの名前がメソッドの定義に現れていないことにより、一体 yield がどこに何を渡しているのかわかりずらいため。
Javascript で enumerable 関数を定義
ところで、前回は 「JavaScript のクロージャ と オブジェクト指向」 について見た。今回はこの延長線上で、上記 Ruby の Enumerable のような動作をする関数を定義してみる。
まずは、「人」 と 「グループ」 オブジェクトを生成する関数を定義。はじめは 「グループ」 に 「人」 を追加する関数を追加。
/** * 人 */ var person = function(spec){ var that = {}; var toString = function(){ return spec.name + " " + spec.age.toString(); }; that.toString = toString; var getName = function(){ return spec.name; }; that.getName = getName; var getAge = function(){ return spec.age; }; that.getAge = getAge; return that; }; /** * グループ */ var group = function(spec){ var that = {}; var persons = []; var add = function(ps){ persons[persons.length] = ps; return that; }; that.add = add; var toString = function(){ return persons.join(","); }; that.toString = toString; return that; }; var g = group({}).add(person({ name: "Hanako", age: 15 })).add(person({ name: "Jiro", age: 15 })).add(person({ name: "Tarou", age: 21 })); console.log(g.toString()); // Hanako 15,Jiro 15,Tarou 21
group 関数が返すオブジェクトに enumerable 関数を定義
次に Ruby でコンテナクラスに要素を順にブロックへと渡すメソッド each を定義したのと同様に、 「グループ」 オブジェクトに関数 each を定義してみる。
group 関数内…
var each = function(f){ var i; for (i = 0; i < persons.length; i++) { f(persons[i]); } }; that.each = each;
Ruby での定義は以下の通りだった。
def each @persons.each{|ps| yield ps} end
違いは Javascript では関数 f が明示されており、この f が Ruby のブロックに相当。 yield が関数 f の呼び出しに当たる。このように比較すると、Ruby の yield の意図が汲み取りやすく、呼び出しの関係もイメージしやすい。
先ほど定義した変数 g で each 関数を使ってみる。
g.each(function(ps){ console.log(ps.toString()); });
結果は、
Hanako 15 Jiro 15 Tarou 21
map, filter, fold の定義
上記 each 関数を使い、map, filter, fold を goup 関数内に定義する。
var map = function(f){ var result = []; each(function(ps){ result[result.length] = f(ps); }); return result; }; that.map = map; var filter = function(p){ var result = []; each(function(ps){ if (p(ps)) { result[result.length] = ps } }); return result; }; that.filter = filter; var fold = function(z, f){ var acc = z; each(function(ps){ acc = f(acc, ps); }); return acc; }; that.fold = fold;
これを使うと、
var g2 = g.map(function(ps){ return ps.getAge() * 2; }); console.log(g2.toString()); // 30,30,42 var g3 = g.filter(function(ps){ return ps.getAge() < 20; }); console.log(g3.toString()); // Hanako 15,Jiro 15 var g4 = g.fold(0, function(acc, ps){ return acc + ps.getAge(); }); console.log(g4.toString()); // 51 var g4 = g.fold("", function(acc, ps){ return acc + ps.getName(); }); console.log(g4.toString()); // HanakoJiroTarou
ここまでのコード
enumerable 関数に map, filter, fold を移す
map, filter, fold は group 関数に特有のものではない。Ruby のモジュールのように enumerable 関数を新たに作りその中 に 3 つの関数を移す。これにより、他のコンテナオブジェクトを生成する関数で共通に利用できるようになる。
以下の enumerable 関数の引数 that は、3 つの関数を追加するオブジェクトを表わす。
/** * Enumerable - コンテナが要素に対して行う処理。 * コンテナで each メソッドが定義されていることを要求する。 */ var enumerable = function(that){ var map = function(f){ var result = []; that.each(function(e){ result[result.length] = f(e); }); return result; }; that.map = map; var filter = function(p){ var result = []; that.each(function(e){ if (p(e)) { result[result.length] = e } }); return result; }; that.filter = filter; var fold = function(z, f){ var acc = z; that.each(function(e){ acc = f(acc, e); }); return acc; }; that.fold = fold; return that; };
group 関数内では、最終的に返すオブジェクト that を enumerable 関数に渡し、that のプロパティに map, filter, fold メソッドを追加してもらうように変更する。 Ruby でモジュールをインクルードすることに相当。
var group = function(spec){ var that = {}; that = enumerable(that);
ここまでのコード。
0コメント:
コメントを投稿