Ruby 2.1 で導入された refinement は,特定のスコープ内でのみクラスやモジュール(や main)を拡張することができる。
とてもいい仕組みだと思うが,refinement で追加したメソッドが Object#send
では呼べないことが分かった。
以下のコードでは,Integer に,〈3 の倍数か(10進で)3 が付く場合に「あほ」を返す〉ようなメソッド nabeatsu
を追加してみたんだが,send(:nabeatsu)
で呼ぶことはできなかった。
module Nabeatsu
refine Integer do
def nabeatsu
"あほ" if modulo(3).zero? || to_s.match(/3/)
end
end
end
using Nabeatsu
puts 31.nabeatsu
#=> "あほ"
puts 31.send(:nabeatsu)
#=> undefined method `nabeatsu' for 30:Fixnum (NoMethodError)
残念。
どこに書いてある?
この件は 公式リファレンスマニュアルには見当たらなくて,@joker1007 さんの「Refinement関係の小技とできない事をまとめてみた」にも載ってない。
が,下記の「Indirect Method Calls」にダメとハッキリ書いてあった。(「are not honored for」とか英語がイマイチ分からないけど)
ただ,「この仕様は将来変わる可能性がある」とのことなので,send
でもいけるようになることを期待したい。
2016年5月12日追記 〈ブロックの代わりに &+シンボル〉がダメ
本件の弊害は当初考えたより大きいことが分かった。
以下のコードは,数の配列から非負の数だけを抜き出そうとしたものだ。
Numeric
クラスに,非負であることを確認する nonnegative?
メソッドを追加している。
module NumericExt
refine Numeric do
def nonnegative?
self >= 0
end
end
end
using NumericExt
ary = [-3, 0, 2]
# これは OK
p ary.select{|x| x.nonnegative?}
#=> [0, 2]
# これは NG
p ary.select(&:nonnegative?)
#=> NoMethodError
見てのとおり〈ブロックの代わりに &+シンボル を与える〉方法だと動かない。
なぜなら,&:nonnegative?
のほうは :nonnegative?.to_proc
が呼ばれて Proc オブジェクトが作られるのだが,その Proc
オブジェクトでは nonnegative?
メソッドが send
によって呼ばれるからだ。
あらわに send
を書いていないのに NoMethodError
が出るので,Refinement の制限を理解していたとしても,どこが悪いのか分かりにくい。
2017年4月28日追記 Ruby 2.4 で OK になってた!
今頃になって気づいたが,Ruby 2.4 では send
でも呼べるようになってた。やたー!