5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2013-06-08

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

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#mapArray#forEachArray#reduceArray#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]]));
5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?