Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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みたいなの導入してほしい。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした