このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

Erlangのとても困ったところ:単一代入の思わぬ弊害

連日連続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) と書けます。この記法のいい点は:

  1. 値を仲介(一時保存)する変数が不要。
  2. 左から右への順なので、直感的に理解しやすい。
  3. 入れ子にならないので簡潔。

問題のErlang関数をこの記法を使って書けば:


sample() ->
dict:new() |>
dict:store("板東トン吉", [27, male], ?) |>
dict:store("大垣ペケ子", [21, female], ?) |>
dict:find("板東トン吉", ?). % 最後に評価された結果が値となる

記号「|>」「?」に対する趣味的な好悪はともかくとして、構文糖衣として悪くない気がするんですが… ちょっと調べた範囲では、これを実現するにはErlangパーザー自体に手を入れるしかないようです(それは面倒だし抵抗があるなー)。

*1:最適化により、実際は破壊的変更がされるかもしれませんが。