もんだいの解答。
いくつかの解答が挙げられていますが、問題文には仕様も前提もろくに書かれていないので、インチキもまとももなくみんな正解です。僕が想定していた解答はこんな感じ。
def div(x, y) puts [111111111, true, 12345678987654321] exit end
嘘です。
基本的な方針は i のメソッドの上書きです。つまり例えば、i.inspect の結果が i の実際の値と関係なく 111111111 を返せばいいわけです。Bignum#inspect ごと上書きしてしまうのも手ですが、他の Bignum にも影響が及ぶのであまり気分がよくないです。こういうときはやっぱり i の特異メソッドとして上書きしたいですよね。まずは p(i) #=> 111111111 だけ目指すとして、考えられるのは次のようなコードです。
def div(x, y) class << x def inspect 111111111 end end end i = 12345678987654321 div(i, 111111111) p(i)
特異メソッド inspect に、実際の値に関係なく 111111111 を返させます。ですが、このコードは動きません。
$ ruby div.rb div.rb:3:in `div': can't define singleton method "inspect" for Bignum (TypeError) from div.rb:10
ここで諦めてはいけません。Fixnum は unboxed な値として実装されているので、実装上の都合から特異メソッドを持たせられないのですが、Bignum は boxed なのでできるはずです。そこでこのエラーメッセージで ruby のソースを検索したところ、こうなっていました。
static VALUE num_sadded(VALUE x, VALUE name) { /* ruby_frame = ruby_frame->prev; */ /* pop frame for "singleton_method_added" */ /* Numerics should be values; singleton_methods should not be added to them */ rb_raise(rb_eTypeError, "can't define singleton method \"%s\" for %s", rb_id2name(rb_to_id(name)), rb_obj_classname(x)); return Qnil; /* not reached */ } ...(略)... rb_define_method(rb_cNumeric, "singleton_method_added", num_sadded, 1);
singleton_method_added とは Object クラスのメソッドで、インスタンスに特異メソッドが定義されたことの通知を受け取るメソッドです。つまりこの例外は singleton_method_added から投げられているということになります *1 。従って、まっとう (?) な解答としては、先に singleton_method_added を上書きしてしまえばいいです。
def div(x, y) class << x # 今後特異メソッドが定義されたことの通知はこれが受け取る def singleton_method_added(x); end # 特異メソッドを定義する (例外は投げられない) def inspect 111111111 end # 元に戻しておく remove_method :singleton_method_added end end i = 12345678987654321 div(i, 111111111) p(i)
これで p(i) が動きます。
$ ruby div.rb 111111111
一般化して、想定していた解答は次のようになります。一般化のやり方は jijixi さんの MutableInteger と同じ発想です。
def div(x, y) # 計算結果を保持する x.instance_eval { @val = x / y } class << x def singleton_method_added(x); end # (ほぼ) すべてのインスタンスメソッドを再定義して @val にたらい回す instance_methods.each do |m| # singleton_method_added は再定義しない next if m == "singleton_method_added" # 再定義で warning が出るのは鬱陶しいので再定義しない next if m == "__send__" || m == "__id__" # for 1.8 next if m == :object_id || m == :__send || m == :__send! # for 1.9 # 再定義する eval <<-END def #{ m }(*args) @val.__send__(#{ m.to_sym.inspect }, *args) end END end remove_method :singleton_method_added end end i = 12345678987654321 div(i, 111111111) p(i) #=> 111111111 p(i == 111111111) #=> true p(i * 111111111) #=> 12345678987654321
だがちょっと待って欲しい。問題のメソッド名は singleton_method_added です。過去形です (いや、過去分詞形) 。このメソッドは、特異メソッドが定義された後に呼ばれます。つまり、can't define singleton method の例外は、定義がされた後に投げられています。従って、次のようなコードでも、
def div(x, y) class << x # 特異メソッドを定義する (定義後に TypeError が投げられる) def inspect 111111111 end rescue TypeError # 拾う end end i = 12345678987654321 div(i, 111111111) p(i)
実は動いてしまいます。これって想定された動作なの?
$ ruby div.rb 111111111
この例外に悩んだ人は、実はその時点でほぼ正解していたわけですね。Ruby で嫌いな人と共同開発をするときは、こういう機能をふんだんに使って陥れましょう (嘘)
僕の想定範囲外の解答として、id:mr_konn さんの TOPLEVEL_BINDING を使った解答 (とそれの改良版) は面白いです。また、yowa さんの p を再定義する解答は愚かにも気がついてませんでした。基本なのに。例えばこんな感じ?
def div(x, y) self.instance_eval do def p(x) super(x.is_a?(Integer) ? x / 111111111 : !x) end end end
*1:あとで気がつきましたが、このことは ruby 1.8 feature に (こっそり) 書いてありました。