タイトルはアレですが、内容は普通です。あらっきぃです。
RubyなのかJavaScriptなのかはっきりしないタイトルですが…
さて、array.map(&:method)
というものですが、これは当然Rubyのことです。
Rubyでオブジェクトを整数値に変換したいときは、オブジェクトのto_i
メソッドを呼び出します。では、配列の各要素をオブジェクトに変換したい場合はどうすればいいのでしょう?
答えは、こうなります。
array = ['123', '456', '789']
p array.map(&:to_i) #=> [123, 456, 789]
ここはRubyの記事ではありませんからあまり詳しく解説しませんが、シンボルに&
を付けてブロックとして渡すと、
p array.map{|x| x.to_i}
のようなコードと等価になります。モンキーパッチされなければ。(詳しくはここら辺を参照してください)
で、これをJavaScriptでできたらいいなー、とか思いました。
いやまあ、上記のような場合は
var
array = ['123', '456', '789'];
console.log(array.map(parseFloat)); //=> [123, 456, 789]
で十分なのですが(なぜparseInt
でなくparseFloat
なのかは後述)、どうしてもイテレーター(配列を操作するメソッドに渡す関数のことです)の最初の引数のオブジェクトのメソッドを呼びたくなることがあります。
例えば、配列を平滑する(何重かになった配列を一つにまとめる)関数flatten
は、
function flatten(arr) {
return Array.isArray(arr) ? arr.map(flatten).reduce(function(arr, a) {
return arr.concat(a);
}, []) : arr;
}
のように定義出来ますが、これのreduce
の引数の関数の中身は単にオブジェクトのconcat
を呼び出して適用しているだけです。これを、もっと簡単に書くことができたら素敵だと思いませんか?
というわけで、最初に考えるのはこんなものです。
function method(name, arglen) {
arglen = arglen || 0;
return function(self/*, ...args*/) {
var
args = [].slice.call(arguments, 1, arglen + 1);
return self[name].apply(self, args);
}
}
これはこう使います。
function flatten(arr) {
return Array.isArray(arr) ? arr.map(flatten).reduce(method('concat', 1), []) : arr;
}
無名関数が無くなってかなりすっきりしたように思えます。
また、method
関数(ややこしい…)にはarglen
という一見無用に見える引数が用意されていますが、これはJavaScriptのArray#map
やArray#forEach
、Array#reduce
やArray#filter
はイテレーターにその配列の要素だけでなく、現在のインデックスやその配列そのものを渡しているため、そのまま二番目以降の引数を全て適用してしまうとややこしいことになるからです。
具体的には、こんな風になります。
var
array = ['1', '2', '3', '4', '5'];
array.forEach(console.log);
//=> 1 0 [ '1', '2', '3', '4', '5' ]
//=> 2 1 [ '1', '2', '3', '4', '5' ]
//=> 3 2 [ '1', '2', '3', '4', '5' ]
//=> 4 3 [ '1', '2', '3', '4', '5' ]
//=> 5 4 [ '1', '2', '3', '4', '5' ]
この仕様は(第二引数に基数を取るparseInt
などを渡したいとき)不便に思われますが、非常に柔軟で慣れるとわっと驚くようなものも出来てしまうので、僕はかなり気に入っています。(配列を高速に探索するTipsでもArray#reduce
のイテレーターに渡されたインデックスを使っています)
さて、寄り道はこれぐらいにしましょう。
To the ES.next
で、このmethod
関数(ややこ(ry)なのですが、わざわざ文字列リテラルを使っているのが微妙な気がします。二文字も対応数が増加しますし…。別にそんなに微妙じゃねーよ、って思う人が大半でしょうけど。
ということで、僕の大好きなES.nextのProxy
を使ってさらに使いやすく実装したいと思います。
(ちなみにこれから出てくるコードは direct_proxies を使っているため、Firefox 18以上か harmony-reflect の動く環境でないと動きません。)
function method(arglen) {
return Proxy({}, {
get: function(_, name) {
return function(self/*, ...args*/) {
var
args = [].slice.call(arguments, 1, arglen + 1);
return self[name].apply(self, args);
};
},
});
}
これの使い方はこうなります。
var
_ = method(1);
function flatten(arr) {
return Array.isArray(arr) ? arr.map(flatten).reduce(_.concat, []) : arr;
}
console.log(flatten([[1, 2], [3, 4]]));
よし、method('concat', 1)
が_.concat
になった!
と、ここで気づいたのですが、これはまるでScalaの無名関数みたいですね…。Rubyばかり意識してたせいで気づかなかった…。
最後に
自分はProxy
が好きです。思えば、前の記事もProxy
だったような気がします。(あれはタイトルを「ES.next時代のAPI設計」とかに変えればもっと人気が出たんじゃないかって未だに思ってます)
まだ対応しているブラウザがFirefoxとChromeしか無くて永遠に広まりそうにない上、公開されたES6のドラフトからも仕様が不安定だからかばっさり項目が抜けているProxy
ですが、まだまだ面白い使い道がありそうです。可愛がってやってください(オマエ何様だ。
あとこの記事、ES.nextの部分抜かしても普通に成り立つ気がしてなりません!
では、御静読ありがとうございました!
var Proxy = require('harmony-reflect').Proxy;
/*
function method(name, arglen) {
arglen = arglen || 0;
return function(self/*, ...args* /) {
var
args = [].slice.call(arguments, 1, arglen + 1);
return self[name].apply(self, args);
}
}
function flatten(arr) {
return Array.isArray(arr) ? arr.map(flatten).reduce(method('concat', 1), []) : arr;
}
*/
function method(arglen) {
return Proxy({}, {
get: function(_, name) {
return function(self/*, ...args*/) {
var
args = [].slice.call(arguments, 1, arglen + 1);
return self[name].apply(self, args);
};
},
});
}
var
_ = method(1);
function flatten(arr) {
return Array.isArray(arr) ? arr.map(flatten).reduce(_.concat, []) : arr;
}
console.log(flatten([[1, 2], [3, 4]]));