JavaScriptでもarray.map(&:method)がしたい！

タイトルはアレですが、内容は普通です。あらっきぃです。

##RubyなのかJavaScriptなのかはっきりしないタイトルですが…

さて、`array.map(&:method)`というものですが、これは当然Rubyのことです。
Rubyでオブジェクトを整数値に変換したいときは、オブジェクトの`to_i`メソッドを呼び出します。では、配列の各要素をオブジェクトに変換したい場合はどうすればいいのでしょう？

答えは、こうなります。

```ruby
array = ['123', '456', '789']

p array.map(&:to_i) #=> [123, 456, 789]
```

ここはRubyの記事ではありませんからあまり詳しく解説しませんが、シンボルに`&`を付けてブロックとして渡すと、

```
p array.map{|x| x.to_i}
```

のようなコードと等価になります。モンキーパッチされなければ。（詳しくは[ここら辺]( http://d.hatena.ne.jp/kitokitoki/20120415/p1)を参照してください）

で、これをJavaScriptでできたらいいなー、とか思いました。

いやまあ、上記のような場合は

```javascript
var
array = ['123', '456', '789'];

console.log(array.map(parseFloat)); //=> [123, 456, 789]
```

で十分なのですが（なぜ`parseInt`でなく`parseFloat`なのかは後述）、どうしてもイテレーター（配列を操作するメソッドに渡す関数のことです）の最初の引数のオブジェクトのメソッドを呼びたくなることがあります。
例えば、配列を平滑する（何重かになった配列を一つにまとめる）関数`flatten`は、

```javascript
function flatten(arr) {
  return Array.isArray(arr) ? arr.map(flatten).reduce(function(arr, a) {
    return arr.concat(a);
  }, []) : arr;
}
```

のように定義出来ますが、これの`reduce`の引数の関数の中身は単にオブジェクトの`concat`を呼び出して適用しているだけです。これを、もっと簡単に書くことができたら素敵だと思いませんか？

というわけで、最初に考えるのはこんなものです。

```javascript
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);
  }
}
```

これはこう使います。

```javascript
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`はイテレーターにその配列の要素だけでなく、現在のインデックスやその配列そのものを渡しているため、そのまま二番目以降の引数を全て適用してしまうとややこしいことになるからです。
具体的には、こんな風になります。

```javascript
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](http://qiita.com/items/10052866719ba5c5f5d7)でも`Array#reduce`のイテレーターに渡されたインデックスを使っています）

さて、寄り道はこれぐらいにしましょう。

##To the ES.next

で、この`method`関数（ややこ(ry）なのですが、わざわざ文字列リテラルを使っているのが微妙な気がします。二文字も対応数が増加しますし…。別にそんなに微妙じゃねーよ、って思う人が大半でしょうけど。

ということで、僕の大好きなES.nextの`Proxy`を使ってさらに使いやすく実装したいと思います。
（ちなみにこれから出てくるコードは [direct_proxies](http://wiki.ecmascript.org/doku.php?id=harmony:direct_proxies) を使っているため、Firefox 18以上か [harmony-reflect](https://github.com/tvcutsem/harmony-reflect) の動く環境でないと動きません。）

```javascript
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);
      };
    },
  });
}
```

これの使い方はこうなります。

```javascript
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`だったような気がします。（[あれ](http://qiita.com/items/68d7f319cece7c2d4891)はタイトルを「ES.next時代のAPI設計」とかに変えればもっと人気が出たんじゃないかって未だに思ってます）

まだ対応しているブラウザがFirefoxとChromeしか無くて永遠に広まりそうにない上、公開されたES6のドラフトからも仕様が不安定だからかばっさり項目が抜けている`Proxy`ですが、まだまだ面白い使い道がありそうです。可愛がってやってください（オマエ何様だ。

あとこの記事、ES.nextの部分抜かしても普通に成り立つ気がしてなりません！

では、御静読ありがとうございました！

```javascript
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]]));
```