Skip to content

Instantly share code, notes, and snippets.

@voluntas
Last active December 5, 2016 01:54
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save voluntas/7218024 to your computer and use it in GitHub Desktop.
Save voluntas/7218024 to your computer and use it in GitHub Desktop.
Erlang/OTP Map コトハジメ

Erlang/OTP Map コトハジメ

更新

2014-03-05

バージョン

0.1.1

作者

@voluntas

URL

http://voluntas.github.io/

概要

この資料は 17 系がリリースされる前の資料のため色々古いです

18.0 がリリースされたタイミングで更新予定です

Erlang/OTP 17.0 で導入される Map を触ってみます。

定義はこちら http://www.erlang.org/eeps/eep-0043.html

17 RC2 未実装

  • Map comprehension syntax
  • Accessing a single value

コンパイル

Map が実装されている Erlang/OTP 17RC2 を使います

url

http://www.erlang.org/download/otp_src_17.0-rc2.tar.gz

頑張ってコンパイルして下さい

文法

文法は、大きく二つに分かれます。Put と Update です。

Put:

#{ key => val }

Update:

#{ key := val }

Put と Update の違いですが、Put は存在しないキーでも追記可能です。Update の := は存在しないキーに対して実行するとちゃんとクラッシュしてくれます。

以下の例は Person という map に対して値を入れて行っています。 => の場合は特に問題なく gender という key / value を入れられますか := の場合は 存在しない ものを 更新 しようとしているためクラッシュします。

Erlang/OTP 17 [RELEASE CANDIDATE 2] [erts-6.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [kernel-poll:false] [dtrace]

Eshell V6.0  (abort with ^G)
1> Person = #{name => spam, age => 10}.
#{age => 10,name => spam}
2> Person#{age => 20}.
#{age => 20,name => spam}
3> Person#{age := 15}.
#{age => 15,name => spam}
4> Person#{gender => male}.
#{age => 10,gender => male,name => spam}
5> Person#{gender := male}.
** exception error: bad argument
     in function  maps:update/3
        called as maps:update(gender,male,#{age => 10,name => spam})
     in call from erl_eval:'-expr/5-fun-0-'/2 (erl_eval.erl, line 249)
     in call from lists:foldl/3 (lists.erl, line 1261)

実はこの Put と Update ですが、maps モジュールでも同様のことが出来ます。

1> Person = maps:new().
#{}
2> Person2 = maps:put(name, spam, Person).
#{name => spam}
3> Person3 = maps:put(age, 10, Person2).
#{age => 10,name => spam}
4> maps:put(age, 20, Person3).
#{age => 20,name => spam}
5> maps:update(age, 15, Person3).
#{age => 15,name => spam}
6> maps:put(gender, male, Person3).
#{age => 10,gender => male,name => spam}
7> maps:update(gender, male, Person3).
** exception error: bad argument
     in function  maps:update/3
        called as maps:update(gender,male,#{age => 10,name => spam})

パターンマッチ

Map でももちろんパターンマッチが出来ます。これが魅力の一つでしょう。

1> Person = #{name => spam}.
#{name => spam}
2> #{name := spam} = Person.
#{name => spam}
3> #{name := egg} = Person.
** exception error: no match of right hand side value #{name => spam}

このコードは name キーに spam というバリューをもった Person という Map を生成しています。 そしてその Person の中から name キーが spam であればパターンマッチ成功という実装です。

Maps モジュール

前述した new と put と update 以外にもいくつか関数があります。

maps:get/2

get は key が見つからないとクラッシュします

1> Person = #{name => spam, age => 10}.
#{age => 10,name => spam}
2> maps:get(name, Person).
spam
3> maps:get(age, Person).
10
4> maps:get(gender, Person).
** exception error: bad_key
     in function  maps:get/2
        called as maps:get(gender,#{age => 10,name => spam})

maps:find/2

find は戻りが ok/error の形になり、見つからない場合は error を返します。

1> Person = #{name => spam, age => 10}.
#{age => 10,name => spam}
2> maps:find(name, Person).
{ok,spam}
3> maps:find(gender, Person).
error

そのほか

keys は全てのキーがリストで取得できるようになりなります。

maps:keys/1:

1> Person = #{name => spam, age => 10}.
#{age => 10,name => spam}
2> maps:keys(Person).
[age,name]

Key が存在しているかを確認出来ます

maps:is_key/2:

3> maps:is_key(name, Person).
true

Map のサイズが取得できます

maps:size/1:

4> maps:size(Person).
2

値を順番に取得できます

maps:values/1:

5> maps:values(Person).
[10,spam]

指定した Key を削除します

maps:remove/2:

11> maps:remove(name, Person).
#{age => 10}

proplists から map に変換します

maps:from_list/1:

12> maps:from_list([{name, spam}, {age, 10}]).
#{age => 10,name => spam}

map から proplists に変換します

maps:to_list/1:

13> maps:to_list(#{age => 10,name => spam}).
[{age,10},{name,spam}]

tips

皆さんがよく使っていた proplists:get_value/3 のデフォルトが指定出来るのはないのか、とお思いだと思いますが、merge を使う事で実現可能です。

maps:merge/2:

1> Person = #{name => spam, age => 10}.
#{age => 10,name => spam}

%% gender が空だった場合は male を指定するというコードです
2> maps:merge(#{gender => male}, Person).
#{age => 10,gender => male,name => spam}

%% gender が female で存在しているため gender の male は反映されていません
3> maps:merge(#{gender => male}, Person#{gender => female}).
#{age => 10,gender => female,name => spam} 

erlang モジュール

erlang:is_map/1 と erlang:map_size/1 が追加されます。

前者は map 型かどうか、後者は maps:size/1 と同等です。

ポイント

基本的には => と := の違いだけを学べば困ることはなさそうです。

また record で解決できていたところはできる限り record を使いましょう。

proplists で重複が許されなかったところ、設定ファイルなどが Map の出番だと思います。

参考

Big changes to Erlang

http://joearms.github.io/2014/02/01/big-changes-to-erlang.html

Where are we on the Maps?

http://www.erlang-factory.com/upload/presentations/779/WhereareweontheMap.pdf

Erlang R17 gets maps (August Lilleaas' blog)

http://augustl.com/blog/2014/erlang_r17_maps/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment