coalesce

というわけで、下の :? 演算子Rubyで出来ないか試してみた。

まずはcoalesce。
これは引数を順に評価し、最初にnullでなかったものを返す。
つまり、こうなる。

Owner@fam ~ $ ruby -e '
> class Object
>   def coalesce(*v)
>     self
>   end
> end
> class NilClass
>   def coalesce(*v)
>     v.select{|x|break x if x}
>   end
> end
> p nil.coalesce(nil, 2)
> '
2

いあ、単純に関数として定義しても問題は無い気がするけど…

しかし、これだと :? 演算子とは違う動きっぽい。
フツウの演算子として使うものだろうから、どちらかというとNULLIF関数(Postgres)が近そう。
実装してみる。

Owner@fam ~ $ ruby -e '
> class Object
>   def nullif(v)
>     self
>   end
> end
> class NilClass
>   def nullif(v)
>     v
>   end
> end
> p nil.nullif(2)
> '
2

そう。こういう動きだ。
これを :? 演算子として定義してみる。

Owner@fam ~ $ ruby -e '
> class NilClass
>   def :?(v)
>     v
>   end
> end
> '
-e:3: syntax error, unexpected tSYMBEG
  def :?(v)
       ^
-e:6: syntax error, unexpected kEND, expecting $end

…そういえばコロンははシンボルの開始ですね。

ここでは:?のシンボルを定義する必要がある。
単なる文字列として :? を定義するのは、比較的簡単なので、

Owner@fam ~ $ ruby -e 'p ":?".intern'
:":?"

これをうまく使えないだろうか。
通常のdefだとsymbolは使えない。
となると、キホンdefine_methodでメタ的に操作するのがいいのだろう。

Owner@fam ~ $ ruby -e '
> class Object
>   define_method(":?".intern){|v|
>     self
>   }
> end
> class NilClass
>   define_method(":?".intern){|v|
>     v
>   }
> end
> '

うまくいった。

さて、うまくdefineされたところで、こんどは使う方法。
そのまま演算子で使おうとすると

Owner@fam ~ $ ruby -e '
> class NilClass
>   define_method(":?".intern){|v|
>     v
>   }
> end
> p (nil :? 1)
> '
-e:7: syntax error, unexpected ':', expecting ')'
p (nil :? 1)
        ^

と言われ、当然、文法違反。

.でメソッドだと強制的にパーサに教えようとしても、

Owner@fam ~ $ ruby -e '
> class NilClass
>   define_method(":?".intern){|v|
>     v
>   }
> end
> p (nil.:? 1)
> '
-e:7: syntax error, unexpected tSYMBEG
p (nil.:? 1)
        ^
-e:7: warning: invalid character syntax; use ?\s

理解してくれない。

結局パーサによる自然な解釈はあきらめ、他の方法を使うしかないのだろう。
というか、リフレクションで強制的に呼び出すしかない。
rubyでmethodを直接指定するには、methodメソッドがある。
methodメソッドを利用すると、Methodオブジェクトが生成されるので、ここに引数を渡せば呼び出しと同じとなる。

Owner@fam ~ $ ruby -e '
> class NilClass
>   define_method(":?".intern){|v|
>     v
>   }
> end
> p nil.method(":?".intern)[1]
> '
1

呼び出せた。
…って、やろうとした事と大分かけ離れてしまった…。
うう。