238
222

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.

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

Last updated at Posted at 2015-05-18

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

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

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

インストール

$ npm install immutable

coffeeのREPLで試します。

$ coffee

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

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

簡単な使い方

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エンジニアの方、テンションがあがりますね。はい。

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

Seq.of(1,2,3)

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

JSからImmutableへ

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

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でないとダメ。

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の扱い便利ですね。

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に実装してあり、各コレクションはIterableを継承しているので、共通で使えるようになっているみたいです。

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

Map

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

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などが使えます。

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

重複のない集合です。

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。

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

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

Range

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

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として使うと良さそう。

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したらバグ踏みました...。

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。

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

flatten。

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返すと止まります。

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に..。

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

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

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

take。

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)

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

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

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

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

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

max/count。

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。

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

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

238
222
2

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
238
222

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?