LoginSignup
6
2

More than 5 years have passed since last update.

Refinement で追加したメソッドが send で呼べない

Last updated at Posted at 2016-03-14

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 でも呼べるようになってた。やたー!

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2