stephen cheng
stephen cheng

Reputation: 99

ruby Metaprogramming and keyword self confusing

being reading some codes of self used in ruby, still can not figure out some examples:

class A
  def hi
    "hi"
  end
  def self.hello
    "hello"
  end
end
a=A.new
a.hi
A.hello

understand I can use below to change instance method def:

class << a
    def hi
      "hi from a <<"
    end
end

a.hi => "hi from a <<"

but what is this?

class << a
  def self.hello
    "H E L L O"
  end
end


[21] pry(main)> A.hello
=> "hello"
[22] pry(main)> a.hello
<<ERROR1>> NoMethodError: undefined method `hello' for #<A:0x007fe41338ecb8>

and I am not really sure what I am doing here actually.

and what is difference between the definition

class << a
  def hi
    "hi from a <<"
  end
end

and

class << A
  def hi
    "hi from a <<"
  end
end

?

----------------------question added a little further----------------------

class A
  p "in class A: #{self}, type: #{self.class}"

  def f
    p "in f: #{self}, type: #{self.class}"
  end

  def self.m
    p "in selfm: #{self}, type: #{self.class}"
  end

end

metaclass = class << a;self;end

metaclass.instance_eval do

  "hi : #{self}, type: #{self.class}"
  def f7
    "in f7 .. : #{self}, type: #{self.class}"
  end

  def self.f9
    "in f7 .. : #{self}, type: #{self.class}"
  end

end

A.f7
a.f7
A.f9
a.f9


<<ERROR2>> 

[20] pry(main)> A.f9 NoMethodError: undefined method f9' for A:Class from (pry):40:inpry' [21] pry(main)> a.f9 NoMethodError: undefined method f9' for #<A:0x007fb70717c0d0> from (pry):41:inpry'

<<ERROR3>> 

[22] pry(main)> [23] pry(main)> A.f7 NoMethodError: undefined method f7' for A:Class from (pry):42:inpry' [24] pry(main)> a.f7 NoMethodError: undefined method f7' for #<A:0x007fb70717c0d0> from (pry):43:inpry' [25] pry(main)> A.f9 NoMethodError: undefined method f9' for A:Class from (pry):44:inpry' [26] pry(main)> a.f9 NoMethodError: undefined method f9' for #<A:0x007fb70717c0d0> from (pry):45:inpry'

Can you help pin point what exactly these errors are: refer to <> mark

  1. see that the <>, my question:

Does it make any sense to define self.method in an object, if not, why there is no warring/error? if it make sense, what does it mean for a self.method for an object?

  1. >, why f7 is not callable using both A's class and object?

  2. >, why f9 is not callable using both A's class and object?

A little more for discussion:

<>

class A
    def self.f1
      "f1"
    end

    def self.f2(&block)
      (class << self; self; end).instance_eval do
        define_method("f1", &block)
      end
    end



    def self.f3(&block)
      m=(class << self; self; end)
        m.instance_eval do
        define_method("f1", &block)
      end
    end


    def self.f4(&block)
      m=(class << self; self; end)
        m.instance_eval do
          def f1
            "f4 is called"
          end
      end
    end


    def self.f5(&block)
      m=(class << self; self; end)
        m.instance_eval do
          def f1
            "f5 is called"
          end
      end
    end

end

Seems I am little closer to the truth now, here's final one need to get the magic revealed:

If I do

A.f2 do
  "f2 is called"
end

A.f1

A.f3 do
  "f3 is called"
end

A.f1

I am able to overwrite the f1 method with either A.f2 or A.f3 call, however if I directly def method in instance_eval block, it won't achieve the same goal, what's the difference here?

A.f4

A.f1

A.f5

A.f1

the A.f1 still returns "f1". What I found is that if you use the def, then the method is defined and tied with eigen class instance, if you use define_method, the method then is tied with class A as static method.

What's the difference here between the usage of define_method and def ?

Upvotes: 1

Views: 223

Answers (1)

bigtunacan
bigtunacan

Reputation: 4986

The questions you are asking here is really about the basics of how classes, methods, and objects work within Ruby.

What you have going on is really just the difference between class methods and instance methods.

An instance method means in the most basic sense that the method can only be called from the level of an instantiated object.

This is what you see with your syntax here.

class A
  def hi
    "hi"
  end
end

In this case the method hi is an instance method of class A and so we must instantiate an object to call it. To avoid confusion between casing a vs A I will use foo instead of a

foo = A.new
foo.hi

Now we have just created an instance foo of A and as such we can call the method hi on foo.

Next we have class methods, or methods that can be called at the level of the class. In Ruby there are multiple syntaxes to achieve this, with all of the following being semantically equivalent.

class A
  def self.hello
    "hello"
  end
end

class A
  def A.hello
    "hello"
  end
end

class A
  class << self
    def hello
      "hello"
    end
  end
end

Now one of the nice things about the last version that uses class << self is that we can define multiple methods in that section. I go back and forth between the last two methods above depending on what I'm doing and almost never use the first version, but this is just personal preference.

Now, up to this point, everything I have said is pretty much Ruby's implementation of standard OO class/method definition and similar concepts are going to be found in any other OO language you have used C++, Java, C#, etc...

The last piece where you were confused is where Ruby steps into the idea of metaclasses or as they are typically referred to in Ruby as "eigenclasses".

What this basically means is that there is a class that you do not define that exists between the class and the instance. This is a bit confusing; read here for more clarity (http://en.wikipedia.org/wiki/Metaclass).

But what this allows us to do is define methods directly on an instance so the methods exist only on that instance and not on other instances of the class.

This is where the syntax below comes in.

class << foo
  def hi
    "hi from foo"
  end
end 

Now the method hi has been overridden only for the instance foo. This can also be used to define completely new methods that only exist on the instance. For example

class << foo
  def bar
    "bar"
  end
end

Now if we instantiate a new instance of A

b = A.new

The method bar does not exist on b; it only exists on the instance foo.

Upvotes: 3

Related Questions