And now, something entirely impractical.
I picked up Beginning Ruby
by Peter Cooper the other day to look
for some teaching material. Flipping through, I came across a basic
feature that had heretofore escaped my notice: the Struct
you want to use a class for nothing more than bundling together a few
values, you can create a Struct instead of writing out
yourself. The idiom is like
class Server < Struct.new(:host, :port, :password)
port == 6600 ? host : "# host :# port "
That particular one's from Njiiri
(which may clue you in that it took
me several months to get around to writing this post). For educational
purposes, it's nice: you can point out how we give a name to a class
(I've also tried to explain assignment as "naming" rather than
"storing"), that there's an unnamed class (classes are also objects!),
and overriding a method (which uses the dynamically defined stuff),
without any boilerplate to get in the way.
So it's tempting to use this in a lesson. Unfortunately, Ruby is a
little bit weird here, and you run into those distracting "practical"
issues about the particular language you're working in. Your classes
can't subclass Class, and thus you can't say Foo.new
like you can
. Struct is actually implemented in C (or Java, or
whatever's native). So you have to do some handwaving.
This is because the "metaclasses" we have in Ruby are implemented, so
to speak, as singleton classes of objects (including class objects). I
do not mean this as a criticism of Matz at all, but they seem like
more of a serendipitous thing than an intentional design -- "hey, if
we implement classes in this particular way, we get this sort of
metaclassing automatically ." (The clearest explanation of why
this is that I've found is in the first chapter of Advanced Rails
by Brad Ediger.)
I wanted to be able to just subclass Class, rather than have all that
fun power that we normally get to abuse in Ruby, only because I think
this is the clearest way to explain the abstraction. Even I still have
trouble describing singletons in plain English. Struct is intuitively
a class of classes, and factors out similar/boring stuff -- a good
practice. It could be an example of how to refactor some simple
classes, if only we could follow it.
I decided to give up on the idea for my tutorial, but it kept me up at
night. Ruby metaclasses clearly can do anything that the sort of
Struct-like metaclasses I have in my mind -- "parametric" classes, if
you will -- can do, and I can dynamically define just about anything;
why not make it happen? We can
instantiate Class itself, but the
tool we have to shape that class is singleton methods. This can
certainly be abstracted away. So, I whipped something up.
Before getting to it, though, let's visit a new construct in Ruby
1.9 and 1.8.7: Object#tap
. Apart from the obvious debugging use
described in its documentation, it makes it quite easy to factor out
thing = Thing.new
Into something closer to the style of functional or declarative
(Well, "do stuff" is obviously still procedural, but A for effort.)
Which one is "better" could probably be the subject of much debate,
but: I really prefer the second one; even though it has exactly the
same effect, it looks like "what" rather than "how" (something else I
try to beat into impressionable young heads. :-)), which is I think
easier to write tests for. I'm going to use it here, because it's
Now, Object#tap passes the object as an argument to the block; in our
case, we are going to define a metaclass, so we want to work with
, instead. So we define a new version, class_tap
analogy with class_def
, I suppose --- which class_eval
block rather than simply evaluating it:
tap _self _self.class_eval &blk
And to do the following trickery, we make use of MetAid
, written by
why the lucky stiff -- it's very small, so we could always just
incorporate the bits we want into the code here, but this short file
provides a common vocabulary for talking about metaclass stuff which
is quite valuable.
Now, we can write a method to create our new classes on the fly.
Here's what I came up with:
class << Class
def meta(_super=Object, &blk)
new.class_tap do # 1
meta_def :new do *args # 2
Class.new(_super).class_tap do # 3
class_exec *args, &blk # 4
(Note the "class << Class
", opening the singleton, rather than
", opening Class itself. Also, the distinction between
-- they are the same method, but from inside
we're no longer in the Class class.) The lines of
meta itself mean:
- The value of this thing is a generated class, which
we will describe thusly:
- Its singleton class has a new method, which gives you a
value that is:
- Another generated class, which is defined by:
- the original block, which now gets run with new's arguments.
Nary an assignment in sight!
The (embarrassing) caveat is that methods in the block that
defines the new class instance cannot use def
to create methods
as normal. A def
is evaluated in a totally fresh scope (I don't
think I get the opinion behind this decision, to be honest), so we
need to use class_def
instead. This is, I must admit, rather
hideous. Perhaps someday the language will change.
But, first things first: now we can implement Struct in pure Ruby.
class MyStruct < Class.meta \
class_def :initialize do *instance_args
args.zip(instance_args).each do attr, val
instance_variable_set :"@# attr ", val
The real Struct class does a few other nice things for you, but this
is the heart of it; I can go back to that Njiiri example and just swap
(Providing an instant performance gain
of -200%... :-)).
Here's a "shapes" example from my abandoned OO lesson:
class Polygon < Struct.new(:sides)
class RegularPolygonClass < Class.meta(Polygon) \
class_def :initialize do side_length
@sides = [side_length] * n_sides
class_def :area do
@sides.size * @sides.first**2 / Math.tan(Math::PI / @sides.size) / 4
class Square < RegularPolygonClass.new(4); end
class Pentagon < RegularPolygonClass.new(5); end
(Note that area
has no free variables and thus could actually be
defined with def
. It would just look funny.)
You only have to change one number here to make new polygon classes,
rather than accumulating parameter lint or explicitly subclassing and
redefining something implicit just for the derived class . In a way it
the exact analogue of the imperative vs. tap style described above.
There is quite a bit of aesthetics involved; one way is not "right".
Apart from the syntactical wart, I like being able to do things this
way. Ruby is, as they say, optimized for programmer happiness and the
principle of least surprise. Still, I'm sure it is quite slow, and
I don't particularly need it for any real-world application right now.
For an intro to OO it's way too much complexity to have lurking
unexplained beneath the surface and still requires getting bogged down
in the language you happen to be using. But hey! It's neat.