先日,「Refinement で追加したメソッドが send で呼べない」件を書いたんだが,その後,別のデキナイコトを見つけた。
ただ,refinement に課された制約というわけではないのだと思う。
まず動く例を
まず,ふつうに動く例をお見せする。
以下のコードは,Integer に,〈3 の倍数か(10進で)3 が付く場合に「あほ」を返す〉ようなメソッド nabeatsu
を追加する。
module Nabeatsu
refine Integer do
def nabeatsu
"あほ" if modulo(3).zero? || to_s.match(/3/)
end
end
end
using Nabeatsu
puts 31.nabeatsu
#=> "あほ"
この sample.rb は期待通りに動く。
irb ではダメ
上記のコードをコピーして,irb 上でペーストしてみる。
すると
using Nabeatsu
までは OK だが,最後の
puts 31.nabeatsu
で
undefined method `nabeatsu' for 31:Fixnum
が出た。
なんでやねんっ?
irb は 1 行ずつ標準入力を読み込んでいって,式が完結するごとに逐次実行される。おそらく,using
の行を実行するときと,次の puts
の行を実行する時とでは,refinement のスコープとしてつながっていないんだろうな。
だとすると,両者を 1 行で表現すればイケるのでは?
試しに,
using Nabeatsu; puts 31.nabeatsu
と入力してみたら,期待通り「あほ」が表示された。
そうか,じゃあ
begin
using Nabeatsu
puts 31.nabeatsu
end
もイケるか? うん,イケた。
この例では,puts
の行を読み込んでも begin
に対応する end
がまだ現れていないので,Ruby の式として完結せず,実行が行われない。
次の end
を読み込んで初めて一つの式として解釈でき,ようやく begin
から end
までが実行されるわけだ。
その後(2018-12-27 追記)
この記事の内容が少し古くなったので追記。
この記事の時点では,
using Nabeatsu
はエラーを出さなかったが,少なくとも Ruby 2.3.3 以降は
RuntimeError: main.using is permitted only at toplevel
が出る。
記事を書いたときの Ruby のバージョンが分からない。ちゃんと書いておくべきだった。反省。
irb 上で self
ってやると main
と表示されるのに,トップレベルじゃない,ってどーゆーこと?
で,(ここ重要)この記事で書いた問題は,irb の起動時のオプションで解消することが分かった。
つまり,
irb --context-mode=1
で起動すれば,irb 上でも最初に掲げたようなコードが期待どおりに動作する。
この issue にそのへんのことが書かれていた。
Bug #9580: Refinements regression in IRB - Ruby trunk - Ruby Issue Tracking System
この話はたしか Qiita の記事で知ったはずだが,いくら探しても記事が見つからず。
この話は以下の記事で知った(2018-12-28 追記)。
数値クラスに逆数を取得するメソッドを追加(+refinementsの勉強) - Qiita
ちなみに pry にはこういうオプションは無いっぽい。