"Seeing Metaclasses Clearly"


#1

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/seeingMetaclassesClearly.html


#2
end

end
end

I forgot to give the error message when I inherit from this Truck
class and call company: ArgumentError: empty symbol string


#3

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 :slight_smile: 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


#4

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.


#5

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


#6

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!
:slight_smile:

David


#7

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


#8

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.