Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
223
Help us understand the problem. What is going on with this article?
@ryoppy

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

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

223
Help us understand the problem. What is going on with this article?
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
ryoppy

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
223
Help us understand the problem. What is going on with this article?