ES6のconstを使い倒すレシピ4 - コレクション一発代入/一発返却編 〜 JSおくのほそ道 #037

こんにちは、ほそ道です。
ここまでシリーズで`const`で宣言した変数(変数より値の名前といったほうが個人的にはしっくり来ますが)の状態管理方法についてやってきました。

- [1.前提共有編](http://qiita.com/hosomichi/items/093aadcd8891c86e9dda)
- [2.Object.freeze編](http://qiita.com/hosomichi/items/84b05c1b0c09d26cd11e)
- [3.immutable.js編](http://qiita.com/hosomichi/items/ea74d44efd47877b16a5)
- [5.条件分岐一発代入/一発返却編](http://qiita.com/hosomichi/items/119dc53980c8f61a0485)

今回は反復操作を行うにあたってやむを得ず`let`を使うことになってしまうようなケースに対して、あくまで`const`宣言した値に一発代入するようなレシピを作っていきたいと思います。

※オブジェクトや配列、`Map`や`Set`に関してはconst宣言したものであっても子要素の追加/変更が行えますが今回の趣旨として一発変換・一発代入でありますので不変性の担保については過去編参照でおねがいします。

[全体の目次はこちら](http://qiita.com/hosomichi/items/ad9a9c99a4fda041a563)

# 基本編・Array
## Array.filter

まずはこんなケースからやってみます。
配列の中から偶数だけを取り出して新しい配列を生成する処理です.
下記のbeforeケースでは反復と条件分岐の結果を配列に詰めて返すために
反復インデックスiと返却配列evensを`let`で宣言しております。

```js:before
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let i = 0, evens = [];

for (; i < nums.length; i++) {
  if (nums[i] % 2 === 0) {
    evens.push(nums[i]);
  }
}

console.log(evens);   // [ 2, 4, 6, 8, 10 ]
```

これをArrayの`filter`を使って下記のように`const`を使って一発で偶数配列を返します。
`filter`は引数に、「配列要素を一つ引数に取る関数」を受け取りtrueを返した場合の配列要素のみで新しい配列を作って返します。

```js:after
const evens = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter(num => num % 2 === 0);

console.log(evens);   // [ 2, 4, 6, 8, 10 ]
```

こんな感じで幾つかのケースの一発代入レシピを作っていきたいと思います。

## Array.map

よく使うものとしてmapメソッドも挙げておきます。
今回は`filter`と、この`map`を中心に据えていこうかなと思います。

```js
const array = [1, 2, 3, 4, 5].map(elm => elm * 100);

console.log(array);    // [ 100, 200, 300, 400, 500 ]
```

`filter`は配列要素の絞込でしたが、こちらは各要素に対する変換となります。

## 応用的な考え方
### 組み合わせ

`map`も`filter`も新しい配列を作って返すので、メソッドチェーンでつないで関数を生成することも可能です。

```js
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter(num => num % 2 === 0).map(num => num * 100);

console.log(array);    // [ 200, 400, 600, 800, 1000 ]
```

下記のようにして、値の検索を行うこともできます。

```js
const has5 = [1, 3, 5, 6, 9].filter(num => num === 5).length > 0

console.log(has5);  // true
```


### 関数化

ちょっとだけ目線を変えて、`filter`や`map`を使って配列を返す関数を作ります。
よりシンプルな関数にすることができると思います。

```js
const getEvens = array => array.filter(num => num % 2 === 0);

console.log(getEvens([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));    // [ 2, 4, 6, 8, 10 ]
```

値の検索処理も関数化することで汎化できます。

```js
const has = (array, value) => array.filter(num => num === value).length > 0

console.log(has([1, 3, 5, 6, 9], 5));  // true
console.log(has([1, 3, 5, 6, 9], 2));  // false
```

# Map

ES6で追加された`Map`もコレクションとして見ておきます。
`Map`は`filter`や`map`メソッドを持っていないということが要注意です。

```js
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);

console.log(map.filter);  // undefined
console.log(map.map);     // undefined
```

ただし、`Map`は`forEach`メソッドなら持っていますので下記のように書くことはできます。

```js
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);

const map2 = new Map();

map.forEach((v, k) => map2.set(k, v * 100));

console.log(map2);  // Map { 'a' => 100, 'b' => 200, 'c' => 300 }
```

上記のmap2に関しては`const`で宣言はしているものの、あとから要素を追加しているので不変な扱い方はできていないところが微妙に感じるところです。

なので、その微妙さは下記のように解決してみます。

```js
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);

const map2 = new Map(Array.from(map).map(kv => [kv[0], kv[1] * 100]));

console.log(map2);  // Map { 'a' => 100, 'b' => 200, 'c' => 300 }
```

`Array.from`をMapにカマすと`[[key, value], [key, value]]`な形式の配列にしてくれるので`map`やら`filter`やらを好きに使える、と言った感じです。
この形式の配列はそのまま`Map`のコンストラクタ引数になるので、変換して`new Map`にくわせてやれば良いという感じです。

# オブジェクトリテラル生成オブジェクト

オブジェクトリテラルで作ったオブジェクトはMapと同じように変換していきたいと思います。
こちらはlodashが提供してくれる`mapValues`関数一発で対応します。

```js
const _ = require('lodash');

const obj = {a: 1, b: 2, c: 3};

const obj2 = _.mapValues(obj, v => v * 100);

console.log(obj2);  // { a: 100, b: 200, c: 300 }
```

プロパティ名の方を変換したい場合は`mapKeys`を使って変換します。

```js
const _ = require('lodash');

const obj = {a: 1, b: 2, c: 3};

const obj2 = _.mapKeys(obj, (v, k) => k + k + k);

console.log(obj2);  // { aaa: 1, bbb: 2, ccc: 3 }
```

# immutable.js

[前回](http://qiita.com/hosomichi/items/ea74d44efd47877b16a5)も触れましたが、immutable.jsを使うことでconst定義したオブジェクトを不変なものとして扱えます。
基本的に配列は`immutable.List`、`Map`は`immutable.Map`、オブジェクトは`immutable.Record`という読み替えで扱っていけば良いと思います。
で、今回の一発変換・一発代入レシピとしてはimmutable.jsが「ならではのもの」として持っている便利なメソッドにフォーカスを当てたいと思います。

## immutable.Map.map

まず、先ほど`Map`のところでおこなった変換を、そのまんま`immutable.Map`に当てはめてみます。

```js
const I = require('immutable');

const map = I.Map([['a', 1], ['b', 2], ['c', 3]]);

const map2 = I.Map(Array.from(map).map(kv => [kv[0], kv[1] * 100]));

console.log(map2);  // Map { "a": 100, "b": 200, "c": 300 }
```

これがものの見事に動きます。すごいなー。
で、もうちょっとシンプルにならないか、ということで`immutable.Map`は`map`メソッドなどの処理を持っていてくれてますので、そちらを使ってみます。

```js
const I = require('immutable');

const map = I.Map([['a', 1], ['b', 2], ['c', 3]]);

const map2 = map.map(v => v * 100);

console.log(map2);  // Map { "a": 100, "b": 200, "c": 300 }
```

シンプルな感じにすることができました！
引数の関数は第二引数でkeyを受け取ることもできますが最終的にreturnする値は`immutable.Map`のvalueになるという感じです。

## immutable.List.flatMap

例えば学校の時間割を曜日とn時間目で表現します。
`[["月", 1], ["月", 2]...["金", 5]]`のような感じにしたいなーと思った時単純に`map`メソッドを重ねると下記のようになってしまいます。

```js
const I = require('immutable');

const daysOfWeek = I.List(['月', '火', '水', '木', '金']);

const lessons = I.List([1, 2, 3, 4, 5]);

const lessonsOfWeek = daysOfWeek.map(dayOfWeek => lessons.map(lesson => I.List([dayOfWeek, lesson])));

console.log(lessonsOfWeek);  // List [ List [ List [ "月", 1 ], List [ "月", 2 ] ... , List [ List [ "火", 1 ] ... , List [ "金", 5 ] ] ]
```

Listが三段重ねになって、曜日と曜日の間に間に余計なListができてしまいます。
こんな時に`Array`は持っていないメソッドである`immutable.List.flatMap`を使ってみます。

```js
const I = require('immutable');

const daysOfWeek = I.List(['月', '火', '水', '木', '金']);

const lessons = I.List([1, 2, 3, 4, 5]);

const lessonsOfWeek = daysOfWeek.flatMap(dayOfWeek => lessons.map(lesson => I.List([dayOfWeek, lesson])));

console.log(lessonsOfWeek);  // List [ List [ "月", 1 ], List [ "月", 2 ] ... , List [ "火", 1 ] ... , List [ "金", 5 ] ]
```

一発で望む形のListを得ることができました！
こんな感じでimmutable.jsにはビルトインメソッドにはない強力な道具が色々搭載されています。
大概の望む形は一発で取得できると思いますので大変気に入っています。

今回は以上です。