Erlang のパターンガード
ところで Erlang の言語仕様で個人的に気になること。
とりあえずこのみっつかな。最初のもひどいんだが、 Haskell を使っているとそれなりに感覚がマヒしてくるので(とりあえずiconvとかあるしいーや派)ひとまず脇に置くとして、関数内にローカルな定義を書けないのも、それが Erlang の流儀ならそれでよしとしようじゃない。
で、最後のことについて。たとえば member という次のような関数があるとします。
member(X, []) = false; member(X, [X|_]) = true; member(X, [_|L]) = member(X, L).
でも、この member をガードに使うことができないわけです。たとえば、メッセージを受信したいが、あらかじめ事前に、リストで渡したデータだけを受け取り、ほかのものは無視したいとする。 ML とか Haskell とかに慣れていると、自然と次のように書くことでしょう。
receiver(L) -> receive Msg when member(Msg, L) -> ... end.
でもこうは書けないっつーことです。 when 以降には任意の式が置けるわけじゃないから。
……when は特別なんですよと覚えればいいじゃないって思いましたか? でもね、 Erlang では、実は if 式というのもパターンガードの集まりであるという原理がある。したがってif の条件部には任意の式は置けません。これはけっこうびっくりしませんか?
1> if member(1, [1,2,3,4,5]) -> ok; true -> fail end. ** 1: illegal guard expression **
どうするかというと、こういう場合には case を使えばいい。
1> case member(1, [1,2,3,4,5]) of true -> ok; _ -> fail end.
それじゃ、 receive でガードする場合はどうでしょう。ガードが成立してもしなくても処理をする場合はともかく、ガードが成立しなくて今この場では処理したくないメッセージはどうればいいんですかねー。自分で自分に send するのかしら?
receiver(L) -> receive Msg -> case member(Msg, L) of true -> ...; _ -> self() ! Msg end end.
もっと複雑なパターンの場合にメッセージの順番が維持されるかとかが気になるけれど、ひとまずこう書けばいいのかなー。
でもなんで、パターンガードにそんな制約を加えているんだろう。確かに、任意の関数を許すよりはパフォーマンスは上がるのかもしれないが、なんかちょっと微妙。
>> なんで、パターンガードにそんな制約を加えているんだろう
これは Erlang に限らず,flat GHC を初めとした,「フラットな」committed-choice 型言語と呼ばれる族の一般的な特徴だと思います.Erlang はけっこう古い言語なので,案外そこらへんの言語の仕様の影響を受けているのでは無いかと.
GHC のような並列論理型言語の場合,データフローに方向性が無いため,ガード部に任意のユーザ定義ルールを許すと,けっこう理論的な解析が面倒になり,全体的な並列度もかなり犠牲になると思います.
確かに微妙と言えば微妙過ぎる仕様ですが,当時は,並列計算というプログラミングパラダイム自体が黎明期だったので,何よりも単純性が重要視されたという経緯があったのではないか.
なるほど。古い言語だからだろうな、とは思っていたのですが、並列論理言語の影響というのは考えていませんでした。確かに。