Erlang/OTP 17では新しいデータ型としてmap型が導入される予定だ。list型やbinary型と同様に、専用の文法を持ち、パターンマッチやmap comprehensionなどに対応している。かなり期待できる機能だ。
現時点では、まだ仕様が確定していないようだが、現在の仕様では以下のように使える。( 2014年2月1日追記 現状ではmapの文法サポートに制限があり、mapのキー部分に変数を使用できない。またmap comprehensionにも未対応とのこと。当分は標準ライブラリの maps
モジュール経由のアクセスが主流になりそう)
> M = #{ key1 => 100, {key2, 1.0} => "yo!"}.
> M1 = M#{ key1 := 101 }.
> M2 = M1#{ fun1 => fun(X) -> X end }.
> F1 = M2#{ fun1 }. %% 単一の値の取り出し。まだ動作しない。
> #{ key1 := V1, {key2, 1.0} := V2 } = M2. %% パターンマッチ
> V1.
101
> V2.
"yo!"
バージョン17はまだリリースされていないので、map型を試すためには、GitHubから開発版のソースコードをダウンロードして、自分でビルドする必要がある。そこで、kerlの実験的な機能を使用して、未リリース版のErlang/OTPを導入してみることにした。
kerlは複数バージョンのErlang/OTPを簡単に導入・管理するためのスクリプトだ。本来はリリース版のみを対象にしたツールだが、実験的な機能として、gitリポジトリからソースコードをダウンロードして、ビルド、インストールすることもできる。(以下、Erlang/OTPを、Erlangと記述)
準備
まず、こちらの記事を参考に、kerlスクリプトをインストールする。
さらに、gitもインストールしておく。手順は、ネットで検索すればすぐにわかるので、省略する。
未リリース版のErlang/OTPのビルド
未リリース版のErlangのソースコードをgitリポジトリからダウンロードして、ビルドする。当たり前のことだが、未リリース版は仕様が変更されたり、一部の機能が動作しなかったり、致命的なバグが含まれていたりするので、本番環境で使用してはいけない。あくまでもお試し版として触ってみる程度に留めておく。
もちろん、バグを見つけて報告したり、パッチを送ったりして、開発コミュニティに貢献することもできる。
ソースコードからビルド
kerlでgitにあるソースコードからErlangをビルドするには、以下のようにする。
$ kerl build git <gitリポジトリのURL> <ブランチ または タグ> <ビルドにつける任意の名前>
バージョン17のmap型はGitHub erlang/optのmasterブランチにある。以下のコマンドでビルドした。
$ KERL_CONFIGURE_OPTIONS="--disable-hipe --enable-smp-support --enable-threads --enable-kernel-poll" kerl build git https://github.com/erlang/otp.git master r17rc1_20140201
ちなみに環境は Arch Linux。
$ uname -a
Linux arch-mini 3.12.9-1-ARCH #1 SMP PREEMPT Sun Jan 26 09:01:37 CET 2014 x86_64 GNU/Linux
( 2014年2月1日追記 Erlang/OTP 17については、erlang.orgからもリリース候補版 RC1 のソースコードがダウンロードができるようになったようだ。バージョンナンバーの振り方に変更があり、kerlでビルドするときは最新版のkerlが必要となる(参考)。最新版を取得後に kerl update releases
を実行すると、リストの先頭に 17.0-rc1
と表示される。)
インストール
無事ビルドできたらインストールする。
$ kerl install r17rc1_20140201 ~/erlang/r17rc1_20140201
Erlang/OTP 17のMap型を試してみる
$ . ~/erlang/r17rc1_20140201/activate
$ erl
Erlang/OTP 17 [RELEASE CANDIDATE 1] [erts-6.0] [source-eec1d22] [64-bit] [smp:8:8] [async-threads:10] [kernel-poll:false]
Eshell V6.0 (abort with ^G)
1>
Map型の仕様はこちらにある:EEP 43。また、書籍 Programming Erlang, Second Edition (2013年、Joe Armstrong著) でも紹介されている。まずは書籍の例を拝借。
> Facts = #{ {wife, fred} => "Sue", {age, fred} => 45, {daughter, fred} => "Mary" }.
#{{age,fred} => 45,{daughter,fred} => "Mary",{wife,fred} => "Sue"}
パターンマッチ
> #{ {age, fred} := FredsAge, {wife,fred} := FredsWife } = Facts.
#{{age,fred} => 45,{daughter,fred} => "Mary",{wife,fred} => "Sue"}
> {FredsAge, FredsWife}.
{45,"Sue"}
EEP 43より、パターンマッチを使用した、リスト内の単語の出現頻度のカウントプログラム。(コンパイルできなかった → バージョン17ではmapの構文サポートに制限がつく見込み。後述の2014年2月1日追記を参照)
freq_m(Is) -> freq_m0(Is, #{}).
freq_m0([I|Is], #{I := C} = M) -> freq_m0(Is, M#{ I := C + 1 });
freq_m0([I|Is], M) -> freq_m0(Is, M#{ I => 1 });
freq_m0([], M) -> maps:to_list(M).
従来の方法。gb_treesを使用する場合。
freq_t(Is) -> freq_t0(Is, gb_trees:empty()).
freq_t0([I|Is], T) ->
case gb_trees:lookup(I, T) of
none -> freq_t0(Is, gb_trees:enter(I, 1), T);
{value, V} -> freq_t0(Is, gb_trees:enter(I, V + 1, T))
end;
freq_t0([], T) -> gb_trees:to_list(T).
後でもう少し試して、ここに追記する予定。とりあえず、Erlangランタイム環境のテストケースが、今は一番参考になるかも。erts/emulator/test/map_SUITE.erl
2014年2月1日追記 現状はmapの文法サポートに制限があり、mapのキー部分に変数を書いたり、単一のキー指定での値の取り出しはできないとのこと。また map comprehension もサポートしない(erlang-questions: Maps branch and disclaimers) そのため、代わりに標準ライブラリの maps
モジュールを使うことになる:
例:
-
M#{ K }
の代わりにmaps:get(K :: term(), M :: map()) -> V :: term()
を使用。 - map comprehension
#{ 式1 => 式2 || K := V <- Map }
の代わりに、lists:foldl/3
を使用。
lists:foldl(fun({K, V}, M) -> maps:put(式1, 式2, M) end, #{}, maps:to_list(Map))