Ruby self and method definitions
Asked Answered
W

3

7
class MyClass
  def one
    def two
    end
  end
end

obj = MyClass.new
obj.one
puts obj.method(:two).owner  #==> MyClass

Here i define method two inside another method one. Method one is called by the instance of MyClass (obj) . So the self is obj when the method two is defined. when i check the owner of method two it's MyClass

obj.instance_eval do
  def three
  end
end

puts obj.method(:three).owner  #==> #<Class:#<MyClass:0x007f85db109010>>

In this snippet i do instance_eval on obj , so the self is again obj when the method three is defined . But when i check the owner of three , it's the singleton class of obj

why is this? is there anything else besides self which determines where the method definition goes ??

Weiweibel answered 1/12, 2013 at 11:43 Comment(3)
+1 for very good investigation... :)Belk
thnks , whenver i feel i've got handle on things, quirks like these come and bite me :) having said that , if you define methods in toplevel , those methods become private instance methods of Object and not singleton methods of main (and i read somewhere that this is intentional and only special case)Weiweibel
ha, def self.two; end will behave like instance_evalSnack
O
2

This is explained in a nice article by ruby-core contributor yugui: Three implicit contexts in Ruby. Basically, there is a default definition context, which is not the same as self. Methods that are not explicitly defined as singleton methods end up as instance methods of the default definition context. module and class definition bodies change the default definition context whereas def doesn't. instance_eval OTOH does change it.

Other answered 1/12, 2013 at 19:25 Comment(1)
ic , so when im defining method two , the default definee is the class of self and when i do instance_eval the default definee is the singleton class of the self.....same way when i define methods in toplevel they become instance methods of Object , as the default definee then is Object and not the singleton class of mainWeiweibel
B
8

I would use Kernel#set_trace_func method,to explain you what is going on under the hood. Look first the below code and the output:

trace = lambda do |event,file,line,id,binding,klass|
    p [event,File.basename(file),line,id,binding,klass]
end


set_trace_func trace

class MyClass
  def self.bar;end
  def one
    def two
    end
  end
end

obj = MyClass.new
obj.one
obj.instance_eval do
  def three
  end
end

output:

-----------------
----------------
-----------------
-----------------
-----------------
----------------- # part A
["c-call", "test.rb", 9, :singleton_method_added, #<Binding:0x83ab2b0>, BasicObject]
["c-return", "test.rb", 9, :singleton_method_added, #<Binding:0x83aaeb4>, BasicObject]
["line", "test.rb", 10, nil, #<Binding:0x83aab80>, nil]
["c-call", "test.rb", 10, :method_added, #<Binding:0x83aa900>, Module]
["c-return", "test.rb", 10, :method_added, #<Binding:0x83aa07c>, Module]
----------------------------- # part B
["line", "test.rb", 16, nil, #<Binding:0x83a976c>, nil]
["c-call", "test.rb", 16, :new, #<Binding:0x83a9488>, Class]
["c-call", "test.rb", 16, :initialize, #<Binding:0x83a90a0>, BasicObject]
["c-return", "test.rb", 16, :initialize, #<Binding:0x83a8e20>, BasicObject]
["c-return", "test.rb", 16, :new, #<Binding:0x83a8b28>, Class]
---------------------------
---------------------------
--------------------------- # part C
["c-call", "test.rb", 11, :method_added, #<Binding:0x83a7de0>, Module]
["c-return", "test.rb", 11, :method_added, #<Binding:0x83a79f8>, Module]
--------------------------- # part D
["line", "test.rb", 18, nil, #<Binding:0x83a7034>, nil]
["c-call", "test.rb", 18, :instance_eval, #<Binding:0x83a6c10>, BasicObject]
["line", "test.rb", 19, nil, #<Binding:0x83a65f8>, nil]
["c-call", "test.rb", 19, :singleton_method_added, #<Binding:0x83a61d4>, BasicObject]
["c-return", "test.rb", 19, :singleton_method_added, #<Binding:0x83a5ef0>, BasicObject]
["c-return", "test.rb", 18, :instance_eval, #<Binding:0x83a5d4c>, BasicObject]

Explanation:

Look at the 5 lines below part A. It simply tells us the when Ruby will find def key word inside a class, it will add that method as an instance method to that class. This is being done by calling the hook method Module#method_added. The same explanation goes to two lines below part C.

Now what goes on inside obj.instance_eval {..} ?

Ok,this will be cleared if you look at the lines below part D. Look from last, first and second line.Inside the instance_eval block, def third, causes third to be added as a singleton_method of the object ob, by calling the hook method BasicObject#singleton_method_added.

This is how MRI has been written.

Belk answered 1/12, 2013 at 11:44 Comment(1)
that's nice tracing method Kernel#set_trace_func , i wasn't aware of it ....it does make clear what happened under the hood , but i am still not clear as to why the ownership of method was different when the value of self was same?Weiweibel
S
2

def is not a method, so it doesn't need to behave like a method does when it comes to self. It's confusing because these two are obviously equivalent:

class Foo
  def one
    "one"
  end

  define_method(:two) { "two" }
end

While these two are obviously not (instances of Bar don't have define_method)

class Bar
  def one
    def two
      "two"
    end
    "one"
  end

  def three
    define_method(:four) { "four" }
    "three"
  end
end

You could view this as an argument for why the nested def is owned by the class. It makes sense because this kind of nested def is only possible when you've opened the class scope, so it affects every instance.

On the flipside, it definitely makes sense that def in instance_eval gets added to the singleton class because you've explicitly opened the instance only. It would break encapsulation for it to work the same as the other case.

So... basically its exceptional behavior. But it's not entirely nonsensical.

Slew answered 1/12, 2013 at 16:48 Comment(0)
O
2

This is explained in a nice article by ruby-core contributor yugui: Three implicit contexts in Ruby. Basically, there is a default definition context, which is not the same as self. Methods that are not explicitly defined as singleton methods end up as instance methods of the default definition context. module and class definition bodies change the default definition context whereas def doesn't. instance_eval OTOH does change it.

Other answered 1/12, 2013 at 19:25 Comment(1)
ic , so when im defining method two , the default definee is the class of self and when i do instance_eval the default definee is the singleton class of the self.....same way when i define methods in toplevel they become instance methods of Object , as the default definee then is Object and not the singleton class of mainWeiweibel

© 2022 - 2024 — McMap. All rights reserved.