連日連続Erlang記事、その三。
最初の記事で:
洗練されてパーフェクトなものより、少し奇妙で不格好なほうが魅力的だってことがあるでしょ。そういう魅力があるね、Erlang。
と、そう書きました。実際「ちょっとコレは…」と思うところも多いですねぇ、Erlang。でも僕の場合、「こいつ変だ」とか「ここは気にくわん」とかの感情がないと興味も湧かないので、不満を持つのは好きになっているってことかと。
それで、Erlangに対し「こいつ変だ/ここは気にくわん」と思うところを(とりあえず一つだけ)述べておきます。([追記]後でさらに不満を書き足した→「階層的パッケージ 」[/追記])
状態を持つデータを模倣する方法と構文
Erlangの標準ライブラリ(STDLIB)に、dictというモジュールがあります。ディクショナリ(ハッシュマップと呼ばれることが多い)データ構造を提供します。dict:new でディクショナリを生成し、dict:store で項目(キー/値のペア)を格納し、dict:find で検索します。もし次のように書けたらオブジェクト指向っぽいですよね。
sample() ->
%
% やってることに意味はないよ
%
Dic = dict:new(), % ディクショナリ生成
Dic.store("板東トン吉", [27, male]), % 項目を格納
Dic.store("大垣ペケ子", [21, female]), % さらに項目を格納
Val = Dic.find("板東トン吉"), % キーに対する値を検索し
Val. % それを返す
もちろん、こんな書き方はできません。オブジェクト指向のメソッドでは暗黙に渡される引数(this)も、明示的に書かなくてはなりません。dictの場合は、最後の引数がthisに相当します。
sample() ->
Dic = dict:new(),
dict:store("板東トン吉", [27, male], Dic),
dict:store("大垣ペケ子", [21, female], Dic),
Val = dict:find("板東トン吉", Dic),
Val.
はい、残念でした。これじゃ、ダメです。多くの関数型言語がそうであるように、Erlangのデータはイミュータブル(immutable)です。つまり、破壊的変更はできません*1。変更後のデータが新しく作られ、それが戻り値で返されます。
sample() ->
Dic = dict:new(),
Dic = dict:store("板東トン吉", [27, male], Dic),
Dic = dict:store("大垣ペケ子", [21, female], Dic),
Val = dict:find("板東トン吉", Dic),
Val.
またまた残念でした。これでもダメです。Erlangは単一代入、つまり変数束縛は1回しかできません。よって、代入のたびに新しい変数を使う必要があるので:
sample() ->
D0 = dict:new(),
D1 = dict:store("板東トン吉", [27, male], D0),
D2 = dict:store("大垣ペケ子", [21, female], D1),
Val = dict:find("板東トン吉", D2),
Val.
悪い冗談のようなこのコードが標準的な書き方です。dict:store呼び出しが数十個並んで、しばしば書き換えが起きたりすると悪夢ですな。
関数呼び出しの入れ子を使えば変数を消去できます。
が、これも読み書きが辛い。
sample() ->
dict:find("板東トン吉",
dict:store("大垣ペケ子", [21, female],
dict:store("板東トン吉", [27, male], dict:new()))).
なんとかならないの?
関数の結合(合成)を左から右の順(diagrammatic order)で行う演算子を仮に「|>」とでもしておきましょう。すると、h(g(f(x))) の代わりに、(f |> g |> h)(x) と書けます。定数や変数も関数扱いしてよいなら、f(x) |> g |> h とか x |> f |> g |> h と順次実行に似た感じで書けます。Unixのパイプライン(記号は「|」)はこの流儀ですね。
しかしこの記法は、1引数関数じゃないと、値の入る場所が不明になります。例えば、g(f(x), y) を f(x) |> g と書くわけにはいきません。そこで、直前の評価結果が入るべき場所を例えば「?」で目印することにすれば、f(x) |> g(?, y) と書けます。この記法のいい点は:
- 値を仲介(一時保存)する変数が不要。
- 左から右への順なので、直感的に理解しやすい。
- 入れ子にならないので簡潔。
問題のErlang関数をこの記法を使って書けば:
sample() ->
dict:new() |>
dict:store("板東トン吉", [27, male], ?) |>
dict:store("大垣ペケ子", [21, female], ?) |>
dict:find("板東トン吉", ?). % 最後に評価された結果が値となる
記号「|>」「?」に対する趣味的な好悪はともかくとして、構文糖衣として悪くない気がするんですが… ちょっと調べた範囲では、これを実現するにはErlangパーザー自体に手を入れるしかないようです(それは面倒だし抵抗があるなー)。
*1:最適化により、実際は破壊的変更がされるかもしれませんが。