Forum: Ruby "Seeing Metaclasses Clearly"

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Pat M. (Guest)
on 2006-05-14 10:29
(Received via mailing list)
I saw a link for why's article "Seeing Metaclasses Clearly" [1]
earlier this week.  Been looking at it, and honestly I can't say it's
making things more clear for me.  The final example is:

 class MailTruck
   def self.company( name )
     meta_def :company do; name; end
   end
 end

 class HappyTruck < MailTruck
   company "Happy's -- We Bring the Mail, and That's It!"
 end

I simply don't see how that's different from

class MailTruck
  def self.company(name)
    instance_eval do
      define_method(name) { name }
    end
  end
end

except for, um, my code doesn't work - wrong number of arguments (0 for
1)

I also don't understand why this doesn't work, I thought it was the
same thing as the first example, just without the helper methods that
why wrote:

class Truck
  def self.company(name)
    class << self
      instance_eval do
        define_method(name) { name }
      end
    end
  end
end

Now after reading this I don't even have a clue how/why all the evals
are supposed to be used.  Thought I had it down, but I'm just way more
confused after this article.  I know there's already a post on the
various evals, but can someone explain what's going on in this
metclass article?  Clearly, please.

Pat


[1] http://whytheluckystiff.net/articles/seeingMetacla...
Pat M. (Guest)
on 2006-05-14 10:32
(Received via mailing list)
>     end
>   end
> end

I forgot to give the error message when I inherit from this Truck
class and call company: ArgumentError: empty symbol string
unknown (Guest)
on 2006-05-14 11:11
(Received via mailing list)
Hi --

On Sun, 14 May 2006, Pat M. wrote:

> class HappyTruck < MailTruck
> end
> end
>
> except for, um, my code doesn't work - wrong number of arguments (0 for 1)

The problem is that you're defining an *instance* method of MailTruck,
called "company".  Meanwhile, the class method company is still the
above method.  So when you call HappyTruck.company, it's running the
method that expects the (name) argument.

You'd need to do:

   def self.company(name)
     metaclass.instance_eval do     # or class_eval
       define_method(name) { name }
     end
   end

(Note: I use this "metaclass" method under protest :-)  I believe it
should be called singleton_class; see
http://www.rcrchive.net/rcr/show/231.)

I think part of the confusion may be that the same name ("company") is
being used for both the method that creates a new method, and for that
new method itself.  The idea is that the parent class has a method,
company, and every time a child class calls that method, the child
class gets a *new* method called company, which reports back the
argument to the original method.  I think the collapse of levels in
your code might stem from this collapsing of names.

Here's a rewrite of the example, using differentiated names.  See if
this helps:

class MailTruck
   def self.set_slogan( our_slogan )
     meta_def :slogan do; our_slogan; end
   end
end

class HappyTruck < MailTruck
    set_slogan "Happy's -- We Bring the Mail, and That's It!"
end

p HappyTruck.slogan

(It would be more "Rubyish" to use slogan= rather than set_slogan, but
anyway, I just wanted to change it by one increment.)

In this rewrite, set_slogan is the equivalent of the parent "company"
method, and slogan is equivalent of the child "company" method that
the parent method is able to create for each subclass.


David
Ross B. (Guest)
on 2006-05-14 11:39
(Received via mailing list)
On Sun, 2006-05-14 at 15:28 +0900, Pat M. wrote:
>  class HappyTruck < MailTruck
>   end
> end
>

I'm sure this is probably a typo, but the above code isn't doing exactly
what it's supposed to:

class HappyTruck < MailTruck
  company "Happy's -- We Bring the Mail, and That's It!"
end

puts HappyTruck.instance_methods
# dup
# hash
# private_methods
# [...]
# Happy's -- We Bring the Mail, and That's It!
# [...]

puts HappyTruck.new.send("Happy's -- We Bring the Mail, and That's It!")
# Happy's -- We Bring the Mail, and That's It!

I.e. you're making an instance method named for the argument passed into
the class-level 'company'. ruby is pretty relaxed about that, but
obviously it can't be called without using send.

Switching to 'define_method(:company) { name }' made it work as (I
think) _why intended.
unknown (Guest)
on 2006-05-14 11:48
(Received via mailing list)
Hi --

On Sun, 14 May 2006, removed_email_address@domain.invalid wrote:

> You'd need to do:
>
>  def self.company(name)
>    metaclass.instance_eval do     # or class_eval
>      define_method(name) { name }
>    end
>  end

Whoops, as Ross pointed out, it should be define_method(:company), not
define_method(name).  (And it was, in the version I tested.  Honest!
:-)


David
Pat M. (Guest)
on 2006-05-14 14:53
(Received via mailing list)
On 5/14/06, Ross B. <removed_email_address@domain.invalid> wrote:
> I.e. you're making an instance method named for the argument passed into
> the class-level 'company'. ruby is pretty relaxed about that, but
> obviously it can't be called without using send.

Well that was kind of a stupid mistake on my part.

> Switching to 'define_method(:company) { name }' made it work as (I
> think) _why intended.

Actually, it doesn't.  Your code lets me do
HappyTruck.new.company

but why's code doesn't.  His code does
HappyTruck.company

Which struck me as kind of useless, to be honest.  If you're going to
make it a class-level method, why do all the metaclass stuff?  What's
wrong with:

class HappyTruck < MailTruck
  def self.company; "Happy Truck Company"; end
end

That is a much more direct way to achieve the same result, at least as
far as I can tell.  Okay actually even if I did it your way it seems
like it would make a lot more sense to just do

class HappyTruck < MailTruck
  def company; "Happy Truck Company"; end
end

I'm REALLY not understanding this stuff now :\

Pat
Gregory S. (Guest)
on 2006-05-14 18:01
(Received via mailing list)
On Sun, May 14, 2006 at 07:52:08PM +0900, Pat M. wrote:
[...]
} Actually, it doesn't.  Your code lets me do
} HappyTruck.new.company
}
} but why's code doesn't.  His code does
} HappyTruck.company
}
} Which struck me as kind of useless, to be honest.  If you're going to
} make it a class-level method, why do all the metaclass stuff?  What's
} wrong with:
}
} class HappyTruck < MailTruck
}  def self.company; "Happy Truck Company"; end
} end
[...]
} I'm REALLY not understanding this stuff now :\

For purposes of exposition, _why was using a simple example. A bit
further
down the post he uses a more complex example involving Creatures. I'll
rewrite his example a little more simply, without his meta_def and such
(which I find somewhat confusing, honestly), and with some stylistic
changes I prefer:

class Creature
  class << self
    attr_reader :traits

    def traits(*args)
      @traits ||= {}
      return @traits if args.empty?
      attr_accessor *args
      args.each { |trait|
        #need to use send because define_method is private
        self.class.send(:define_method, trait) { |val|
          @traits[trait] = val
        }
      }
    end

    def set_traits_defaults(obj)
      @traits.each { |k,v|
        obj.send(:instance_variable_set, "@#{k}", v)
      }
    end

  end

  def initialize
    self.class.set_traits_defaults(self)
  end

end

class WhimperingWhuffle < Creature
  traits :fear_level, :whimpering_volume
  fear_level 2
  whimpering_volume 8
end

class SarcasticSnark < Creature
  traits :snarkiness_level, :toxicity
  snarkiness_level 7
  toxicity 200
end

First off, what is the goal of this code? We want to be able to create
Creature subclasses that have creature traits with default values. For
our
WhimperingWhuffle, new instances are created with a fear_level of 2 and
a
whimpering_volume of 8.

In the WhimperingWhuffle class definition, the traits line calls the
method
we defined on the Creature class. The Creature.traits method initializes
the @traits instance variable, but the instance in question is the
WhimperingWhuffle class object, i.e. an instance of Class. The @traits
instance variable can be thought of as metadata for the
WhimperingWhuffle
class. The Creature.traits method also call attr_accessor, but this is
again called on the WhimperingWhuffle class rather than the Creature
class.
When we instantiate (e.g. foo = WhimperingWhuffle.new), the initialize
method gets called. The initialize method sets the associated instance
variables to the defaults in the traits metadata.

Now consider the following version, which is a bit kinky but doesn't
require the repetition inherent in declaring the traits then setting
their
default values:

class Creature
  class << self
    attr_reader :traits

    def method_missing(method_name, *args)
      if /[=?]/ !~ method_name.to_s && args.length == 1
        @traits ||= {}
        attr_accessor method_name
        @traits[method_name] = args[0]
      else
        super method_name, *args
      end
    end

    def set_traits_defaults(obj)
      @traits.each { |k,v|
        obj.send(:instance_variable_set, "@#{k}", v)
      }
    end

  end

  def initialize
    self.class.set_traits_defaults(self)
  end

end

class WhimperingWhuffle < Creature
  fear_level 2
  whimpering_volume 8
end

class SarcasticSnark < Creature
  snarkiness_level 7
  toxicity 200
end

We have the same sort of metaprogramming going on, but it's a bit
simpler.
We simply assume that any statements (that do not have a ? or = in them
and
are called with a single argument) that are otherwise unrecognized are
trait declarations, with a default value. The code is much the same as
before, except we don't have to create class methods for the traits.

Does this help your understanding?

} Pat
--Greg
Mike N. (Guest)
on 2006-05-14 19:01
Gregory S. wrote:
> I'll
> rewrite his example a little more simply, without his meta_def and such
> (which I find somewhat confusing, honestly), and with some stylistic
> changes I prefer:
>

Not sure if this helped Pat, but it sure helped me. I too find _why's
meta_def stuff confusing.

Also, your method_missing approach helped me see the whole idea behind
the traits thing clearer (probably because there is less code that way).
That was always a bit elusive to me before.
This topic is locked and can not be replied to.