Ruby のパターンマッチを利用して任意のメソッドが定義されているかどうかを判定する
と、いうのが bugs.ruby に来ていたので。
class Runner def run end def stop end end runner = Runner.new case runner in .run & .stop :reachable in .start & .stop :unreachable end
ほしい気持ちはわかるんだけど流石に上記の提案だと情報が欠落しすぎているのとそもそもパターンマッチでやるべきこと?と思ってしまい個人的にはいまいち。
なんかもっといい感じの構文だといいとは思うんですが…。
と、言うことで既存のパターンマッチで出来ないかやってみました。
using Module.new { refine Object do # パターンマッチ内部では #deconstruct_keys を暗黙的に呼び出してそれで判定を行っている # そこで deconstruct_keys を経由してメソッド情報を取得することでパターンマッチで利用できるようにする # { メソッド名: 値... } となるような Hash を返す def deconstruct_keys(keys) keys.select { |name| respond_to?(name) }.to_h { |key| [key, send(key)] } end end } def check(obj) case obj in { run: _, stop: _ } :reachable in { start: _, stop: _ } :unreachable else :none end end class Runner def run end def stop end end runner = Runner.new p check(runner) # => :reachable class Runner2 def start end def stop end end runner2 = Runner2.new p check(runner2) # => :reachable
Ruby のパターンマッチでは暗黙的に #deconstruct_keys
(や #deconstruct
)が呼び出され、その戻り値を参照してパターンマッチを評価します。
上記の実装では { メソッド名: 値... }
というような Hash をパターンマッチで使用できるようにすることで
case obj in { run: _, stop: _ } :reachable in { start: _, stop: _ } :unreachable else :none end
というようなパターンマッチをかけるようにしています。
これならメソッドが定義されているかどうかを判定することも出来ますし『任意のメソッドの値』をキャプチャすることも出来ます。
# obj.run の値をキャプチャする case obj in { run: status, stop: _ } p "status is #{status}" :reachable in { start: status, stop: _ } p "status is #{status}" :unreachable else :none end
これ、かなり汎用性が高そうなので普通にほしい。ってか、すでに機能としてありそう。