Island Life

< 「Xで使うべき」だからといって「X以外では使うべきじゃない」ってわけじゃない。けれど… | gitのワークフロー >

2010/02/19

マップリテラル

Lispのハッシュテーブルには伝統的にリテラル表記(外部表現)が無い。 他の主要なデータ構造ではread/write invariance (WiLiKi:Scheme:ReadWriteInvariance) が 保証されているので、残念なことである。 ハッシュテーブルは手頃な大きさのマップ(キーから値への写像を表現するデータ構造) に 便利だが、組み込みのマップ型にリテラル表記を備えている言語も増えていて、 Lispに移った時に不便に感じることもある。 (但し、マップを必ずしもハッシュテーブルで実装する必要はない。 Lispの場合、とりあえず読み書きしたかったらalistでいいじゃん、 という要因はあったかもしれない。)

なので、新しいLisp拡張を考える時にハッシュテーブルのリテラル表記を、 と思うのは自然なのだけれど、Common LispやSchemeでハッシュテーブルリテラルが 無いのには理由がある。最大の理由は、 「ハッシュテーブルを再現するには、各要素だけでなくハッシュ関数および比較関数の情報が必要」 という点だろう。

一般的なクロージャのポータブルなリテラル表記は無いので、 これらの関数が自由に指定できる場合、ハッシュテーブルの リテラル表記がいつでも可能とは限らない。 それに関数を書き出せたとしても、 それが人間にとって読み書きしやすいものにはならないかも。 (特に、プログラム中にリテラルとして書きたい場合とか。)

アプリケーションが決まればハッシュ関数と比較関数は固定される ことが多いだろうから、そしたらリードマクロを使って リテラル表現を自由に決めてください、ってのがたぶんCommon Lispの立場。 例えば比較関数をeql決め打ちで

  { :foo => 1
    :bar => 2 }

なんてのをハッシュテーブルとして読み込む、とかいうコードはすぐ書ける。

つまり、ハッシュ関数と比較関数が言語で決め打ちになっていれば (たとえカスタマイズできるとしても、大部分のケースをカバーできる関数がデフォルトに なっていれば)、言語としてリテラルを決めてしまってもいい。 マップリテラルがある他の言語は、そういうことだ。 ハッシュ/比較関数の自由度と、リテラルの便利さがトレードオフになっているわけだ。

Lispには比較関数がいくつもあって、これはプログラマの 選択肢をできるだけ用意しとくって精神なんだろうけど、敢えてデフォルトに 絞り込もうとすると「eqかeql」とequalp (Schemeなら「eq?かeqv?」とequal?) が考えられる。 この二つは甲乙つけがたい。基本的に、前者はオブジェクトとしての同一性、 後者は値としての同一性を見ていると考えられるけど、 どちらも同じくらい必要なことが多いからだ。

ところでClojureはマップリテラルを持っている。 その理由は、Clojureではオブジェクトの同一性よりも値の同一性の方が はるかに重要なので、比較関数を = に決め打ちしてしまってもほぼ問題にならないから。 で、なぜ値の同一性の方がそんなに重要になるかというと、 Clojureのデータ構造が原則immutableだから。 immutableなデータ構造では、値の同一性のみが問題になる (メモリ上の同一番地にあろうが別の場所にあろうが、それらのオブジェクトは 操作に対してまったく同様に振る舞うので、区別できない)。

だもんでClojureに関して言えば、データ構造のmutabilityを捨てたら マップリテラルが自然についてきた、という感じだ。

マップリテラルがあるかどうかには、 「等しい」とは何か、という問いが隠されている、という話。 俺言語を設計するときにでも参考にされたし。

Tags: Programming, Lisp

Past comment(s)

higepon (2010/02/20 16:13:37):

マップリテラルがないのは困る事が多いですね。 自分は alist->eq-hashtable なんていう手続きを書いたりしてます。

速度を気にして alist ではなく eq-hashtable にしたのは良いけど read/write できないというのがありがちです。

shiro (2010/02/21 09:56:23):

とりあえずカスタム比較関数は無視して、Gaucheではeq?, eqv?, equal? だけでもリテラルにしちゃおうかなあとは思ってるんですが (というかコードは既に入ってて#if 0で切ってある)、ほんとにそれが欲しいものなのか、というところでまだ迷いがあります。

  • read/write invarianceを気にするなら、決め打ちの比較関数以外のハッシュテーブルが書き出せないというのは中途半端。
  • プログラム中にリテラルとして書き込みたい場合、比較関数を指定させるのがどうも冗長に見える。

higepon (2010/02/24 02:08:38):

前者はバランスの問題で個人的には eq? eqv? のリテラルがあれば十分かなと思っています。equal? の key を使った事はないです。 だいたい key は symbol か string なので。

後者は→の違いとかで区別するのはどうでしょう? eq? は {key -> hoge} eqv? は {key => hoge}

とか。うーん。いまいちかも。

higepon (2010/02/24 02:10:14):

eqv? は eq? を含むので eqv? のリテラルがあるだけで十分な気がしてきました。

shiro (2010/02/24 08:27:47):

eqv?かeq?はどっちかでいいんですが、Schemeではstringは同じ内容でもeqv?にならないのがねぇ。(個人的にはstringはimmutableにして値扱い=eqv?で比較、の方がいいと思ってるんですが)。

2種類に限って新たな構文を入れるなら、#{ key -> val } と { key -> val } とか色々やりようはあると思います。

higepon (2010/02/24 12:06:58):

ああ。そうでした>string。

確かに string は immutable で全然困らないですね。string-set! したことないです。

Post a comment

Name: