Immutable.jsを色々触ってみました

[immutable.js](https://github.com/facebook/immutable-js)は、facebook社が作っているJavaScriptのライブラリでimmutableなコレクションを提供してくれます。`List, Stack, Map, OrderedMap, Set, OrderedSet and Record`があるとのこと。

関数型言語にインスパイアされて作っているらしいので、関数型に馴染みのある関数が出てきて嬉しいです。

他にも、正格でなく遅延評価してくれるという点も嬉しいですね。

## インストール

```
$ npm install immutable
```

coffeeのREPLで試します。

```
$ coffee
```

まずrequireします。めんどうなのでSeqの変数を置きます。

```coffeescript
coffee> Immutable = require 'immutable'
coffee> Seq = Immutable.Seq
```

## 簡単な使い方

```coffeescript
coffee> Seq([1,2,3]).map((i) -> i + 1)
# Seq [ 2, 3, 4 ]
coffee> Seq([1,2,3]).filter((i) -> i % 2 == 0)
# Seq [ 2 ]
```

Scalaエンジニアの方、テンションがあがりますね。はい。

可変引数でも書けるようです。

```coffeescript
Seq.of(1,2,3)
```

下の方でもっといっぱい例書きます。

## JSからImmutableへ

構造がネストしている場合は、`Immutable.fromJS`を使う。

```coffeescript
coffee> data = {a: 1, b: [1,2,3]}
#{ a: 1, b: [ 1, 2, 3 ] }
coffee> Immutable.Seq(data)
#Seq { "a": 1, "b": 1,2,3 }
coffee> Immutable.fromJS(data)
#Map { "a": 1, "b": List [ 1, 2, 3 ] }
```

## JSのオブジェクトに変換

toArray/toObject/toJSがあります。

ネストしてなければtoArrayかtoObjectを明示的に使うのが良さそう。Listの中にMapがあったり、ネストしてる場合はtoJSでないとダメ。

```coffeescript
coffee> Seq([1,2,3]).map((i) -> i + 1).toArray()
# [ 2, 3, 4 ]
coffee> Seq([1,2,3]).map((i) -> i + 1).toObject()
# { '0': 2,
#   '1': 3,
#   '2': 4 }
coffee> Seq([1,2,3]).map((i) -> i + 1).toJS()
# [ 2, 3, 4 ]
```

objectの扱い便利ですね。

```coffeescript
coffee> Seq({a: 1, b: 2, c: 3}).map((i) -> i + 1)
# Seq { "a": 2, "b": 3, "c": 4 }
coffee> Seq({a: 1, b: 2, c: 3}).map((i) -> i + 1).toArray()
# [ 2, 3, 4 ]
coffee> Seq({a: 1, b: 2, c: 3}).map((i) -> i + 1).toObject()
# { a: 2, b: 3, c: 4 }
```

## 他のコレクション

各コレクションは内部的にはTrie木を使っているらしいので、Listだから添字アクセスはO(n)になるみたいなことは気にしなくていいはず。

mapやfilterみたいなコレクションメソッドは、[Iterable](https://github.com/facebook/immutable-js/blob/master/src/IterableImpl.js)に実装してあり、各コレクションはIterableを継承しているので、共通で使えるようになっているみたいです。

Seqを基本的には使い、必要あればListやMapを使うという感じでいいと思います。

### Map

updateやmergeなどできるようです。

```coffeescript
coffee> Map({"a": 1, "b": 2})
# Map { "a": 1, "b": 2 }
coffee> Map([["a", 1], ["b", 2]])
# Map { "a": 1, "b": 2 }

coffee> Map({"a": 1, "b": 2}).set("c", 3)
# Map { "a": 1, "b": 2, "c": 3 }
coffee> Map({"a": 1, "b": 2}).delete("a")
# Map { "b": 2 }
coffee> Map({"a": 1, "b": 2}).update("a", -> 3)
# Map { "a": 3, "b": 2 }

coffee> Map({"a": 1, "b": 2}).merge Map({"b": 3, "c": 4})
# Map { "a": 1, "b": 3, "c": 4 }
```

### List

Arrayでおなじみのpopやらpushなどが使えます。

```coffeescript
coffee> List.of(1,2,3).push(4)
# List [ 1, 2, 3, 4 ]
coffee> List.of(1,2,3).pop()
# List [ 1, 2 ]
coffee> List.of(1,2,3).unshift(0)
# List [ 0, 1, 2, 3 ]
coffee> List.of(1,2,3).shift()
# List [ 2, 3 ]
coffee> List.of(1,2,3).set(0, 9)
# List [ 9, 2, 3 ]
coffee> List.of(1,2,3).delete(0)
# List [ 2, 3 ]
coffee> List.of(1,2,3).clear()
# List []
```

### Set

重複のない集合です。

```coffeescript
coffee> Immutable.Set.of(1,2,2,3)
# Set { 1, 2, 3 }
coffee> Immutable.Set.of(1,2,2,3).map((i) -> i + 1)
# Set { 2, 3, 4 }
```

### OrderedSet

追加した順番を保証してくれるSet。

```coffeescript
coffee> Immutable.OrderedSet.of(1,3,2,1).toArray()
[ 1, 3, 2 ]
```

どういうときに使うんだろうか?

### Range

範囲(無限)のリストを扱えます。

```coffeescript
coffee> Immutable.Range(1, 100)
# Range [ 1...100 ]
coffee> Immutable.Range(1)
# Range [ 1...Infinity ]
coffee> Immutable.Range(1).map((i) -> i + 1).take(3)
# Seq [ 2, 3, 4 ]
```

### Record

クラスのようなものを作れます。immutableなのでEntityとして使うと良さそう。

```coffeescript
coffee> Foo = new Immutable.Record({n: 1})
# [Function: Record]
coffee> foo1  = Foo {n: 2}
# Record { "n": 2 }
coffee> foo1.get 'n'
# 2
coffee> foo1.set 'n', 3
# Record { "n": 3 }
```

mapしたら[バグ](https://github.com/facebook/immutable-js/issues/220)踏みました...。

```coffeescript
coffee> Foo = new Immutable.Record({n: 1})
# [Function: Record]
coffee> foo1  = Foo {n: 2}
# Record { "n": 2 }
coffee> foo1.get 'n'
# 2
coffee> foo1.map((i) -> i + 1)
# undefined
coffee> foo1.get 'n'
# 3
```

## コレクションメソッドの例

flatMap。

```coffeescript
coffee> Seq([1,2,3]).flatMap((i) -> if i == 1 then Seq(["one"]) else Seq([]))
# Seq [ "one" ]
```

flatten。

```coffeescript
coffee> Seq.of(1,2,3,Seq.of(4,5),6)
# Seq [ 1, 2, 3, Seq [ 4, 5 ], 6 ]
coffee> Seq.of(1,2,3,Seq.of(4,5),6).flatten()
# Seq [ 1, 2, 3, 4, 5, 6 ]
```

forEach。末尾の値が返る..。false返すと止まります。

```coffeescript
coffee> Seq([1,2,3]).forEach((i) -> console.log(i))
# 1
# 2
# 3
# 3
coffee> Seq([1,2,3]).forEach((i) -> if i == 2 then false else console.log(i))
# 1
# 2
```

find。マッチした値が返ります。Optionとかないのでundefinedに..。

```coffeescript
coffee> Seq([1,2,3]).find((i) -> i == 2)
# 2
coffee> Seq([1,2,3]).find((i) -> i == 99)
# undefined
```

reduce。JSのreduceと違い、空でも例外飛ばないのですごくいいですね。

```coffeescript
coffee> Seq([1,2,3]).reduce((acc, i) -> acc + i)
# 6
coffee> Seq([]).reduce((acc, i) -> acc + i)
# undefined
```

take。

```coffeescript
coffee> Seq([1,2,3]).take(2)
# Seq [ 1, 2 ]
coffee> Seq([1,2,3]).takeWhile((i) -> i <= 2)
# Seq [ 1, 2 ]
```

first/rest。(head/tail)

```coffeescript
coffee> Seq([1,2,3]).first()
# 1
coffee> Seq([1,2,3]).rest()
# Seq [ 2, 3 ]
```

every。全部trueか、空か。(forall)

```coffeescript
coffee> Seq([1,2,3]).every((i) -> i <= 3)
# true
coffee> Seq([]).every((i) -> i <= 3)
# true
```

some。trueなものがあるか。(exists)

```coffeescript
coffee> Seq([1,2,3]).some((i) -> i == 2)
# true
coffee> Seq([1,2,3]).some((i) -> i == 4)
# false
```

max/count。

```coffeescript
coffee> Seq([1,2,3]).max()
# 3
coffee> Seq([1,2,3]).count()
# 3
coffee> Seq([1,2,3]).maxBy((i) -> i % 2 == 0)
# 2
coffee> Seq([1,2,3]).countBy((i) -> i % 2 == 0)
# Map { false: 2, true: 1 }
```

join。

```coffeescript
coffee> Seq([1,2,3]).join(',')
# '1,2,3'
```

Optionみたいなの導入してほしい。