空セマンティクスのオーバーロード

Rubyには nil がある。Smalltalkから継承した知恵の1つである。

今更Rubyistには言うまでもないが、これはマクロに過ぎないCの NULL やオブジェクトではないJavanull とは一線を画すものだ。C#の中の人が null を反省して「 nil いいなぁ」と言っていたらしいけれども、とにかく nil は良いものだ。

これはデフォルトのNull objectである。RubyにおいてもNull objectパターンは健在だが、「何らかの出力できるオブジェクト」でよい程度の軽量なケースにおいては、わざわざNull objectを定義しなくともデフォルトのNull objectとして nil を使うことができる。だから、その意味ではRubyではNull objectパターンは「当たり前」であると言ってもよいかもしれない。

空セマンティクス

Null objectは何らかの意味でそこにあるべきオブジェクトの不在を表す。セマンティクスの意味で不在ではあるのだけれども、言語レベルではそこにオブジェクトが存在している。だから、なんらかの機構を通じて生成されてきたオブジェクトを扱うクライアント側コードは、多態性を通じて空か空でないかを問わずに透過的にその参照対象を操作できる。「無の存在」をオブジェクトとして表現する。これがNull objectパターンである。

ところで、空であることのセマンティクスはケースによって異なる。ある場合には nil こそが唯一の空状態だろう。ある場合には独自のNull objectが必要かもしれない。ある場合には、空文字列は空オブジェクトであるべきかもしれない。こういうケースで、任意のオブジェクトに対して特定のセマンティクスにおける"空"性を問い合わせたい。そのとき、Rubyオープンクラスは有効に機能して、つまり、その"空"性を表す述語メソッドを作ればよい。ActiveSupportObject#blank? はwebアプリケーションによくある空のセマンティクスの実装である。適用分野に応じて、言語本体からは切り離してこういう実装を使いやすく行うことができるのは素晴らしい。これが私が blank? を賞賛する理由であり、Ruby本体に取り込むことに反対し続けている理由である。

空補完構文

Ruby論理和演算子は、C言語の系譜に属す多くの言語と同じく短絡する。更に、Ruby論理和演算子オペランドのいずれかを返し、更に対応する自己代入演算子を持つ。これが、つぎのイディオムを産む。

obj ||= default_value

これは、 objRubyの真偽評価の意味で偽である場合にはデフォルト値で置き換え、さもなくば何もしないということを簡潔に記述する頻出のコードである。ここで、Rubyの真偽評価においては nilfalse のみが偽で他の全てのオブジェクトは真だ。だから、 false の存在を無視すればこのイディオムは、 nil? の意味で空であれば補完し、さもなくば何もしないということを意味する。

空補完構文のオーバーロード

空補完構文のほうは、残念ながら"空"性に他のセマンティクスを与える方法はない。だから、 blank? の意味で空である値を補完したければ次のように書くほかない。

obj = default_value if obj.blank?

まー、これでもそれなりに簡潔といえなくはないけれども、 ||= に慣れた脳にとって、行っているのは何らかの「非空性保証」という契約(Design by contractの意味で)であって、条件分岐ではない。このギャップが思考を一瞬妨げる。

"空"性のセマンティクスを指定して ||= を使いたい。

obj.blank ?= default_value
obj ?blank|= default_value
obj |blank? = default_value

いまいち良い構文糖を思いつかない。思いついたらruby-devに提案したいのだけれども。