One of the things I’ve found slightly frustrating about Ruby is the
Nearly twenty years ago I was writing code in Objective-C on NeXT, which had explicit allocation and initialization:
Using this invocation,
foo could be any sort of Object, and
init could return any sort of Object.
This two-part object creation could do something out-of-the-box ruby couldn’t.
It’s easy to create a new object in Ruby.
An instance of a class
Foo is created by calling its
new is called it routes to the
Foo’s new instance method.
The code for the new method is written in C: the
rb_class_new_instance function is the new instance method in
Foo, and is mapped up into Ruby by the
In the context of the
Foo instantiation above, this code allocates a
Foo instance, initializes it with whatever arguments were passed to
new, and finally returns the new object.
Sometimes though, a new object is not what is really wanted. Sometimes an object may already exist that should be returned instead, or perhaps an object of another type should be created and returned. Whatever the reason and for whatever purpose, this is not possible using the standard Ruby mechanism - only the allocated instance is returned. To return an object different from the one allocated, an enhanced mechanism is needed.
In a class that uses Class’ new method to construct instances, there are only two calls that may be intercepted to enhance its functionality - memory allocation and initialization.
During memory allocation the space needed to hold an instance is allocated by Class’
allocate method, which returns an uninitialized instance of the class.
During initialization the instance’s
initialize method is called using any arguments that were passed to
new to set up the initial state of the instance.
initialize method’s responsibility to call its superclass’
initialize method - which is responsible for calling its superclass’
initialize and so on up the chain to Object, from which all instances derive.
The only other constraint on any mechanism affecting object creation is that it should be above the surface of the Ruby interpreter - object creation should be extended, not replaced in Ruby.
Returning an Alternate Object
Initialization is the more reasonable place to control the enhanced functionality since allocation is generally logic-free.
This also allows information in a partially instantiated instance to be used to make decisions about its possible replacement.
initialize determines that another object should be returned by
new, some sort of replacement mechanism must be used.
Looking back at the C code, it is allocation, not initialization, that determines the object that is returned by
Since the value returned from
initialize is ignored and the Ruby Interpreter cannot be changed, different semantics are required to replace an object.
remap_new_object method that can be called by
initialize to declare an object replacement works well.
After moving the original
new method aside with an
alias, a new
new method is defined that first calls the original
new method and then either returns the alternate object that was reassigned, or the object itself.
If an alternate object is present, it’s deleted upon retrieval to keep the remapping Hash small.
This would work fine if some classes didn’t already redefine the
The enhanced code above can’t deal with this situation since these classes may not even call Class’
Rather than intercepting the
new and possibly returning an alternate object, these classes would bypass the remapping code altogether.
The mechanism must still be adjusted somewhat to handle this.
Elective Alternate Remapping
Part of the code is still good: the chunk that creates the Hash and adds alternate objects to it.
What’s still left is the retrieval of an alternate object.
Thinking more about the whole idea of returning alternate objects from new, this relatively small change is philosophically a radical break from standard Ruby. It probably makes sense to require classes that need object replacement during creation to explicitly include this capability. Only when a class elects to have the capability should the mechanism to alias the new method and lookup remappings be put into place - for just that class. Besides positively asserting the class’ intention to break from standard Ruby, this explicitness will help safeguard against cavalier use of such non-standard behavior.
The code to do this is the virtually same as was in the Class version:
To use this, a call to
allows_object_remapping_in_new would be made by each class that needed remapping capability.
By making the call, the metacode would add the retrieval mechanism to the class.
Note the metacode is surrounded by the
class<<self ... end construct.
This is required because it must be evaluated at the class level rather than the instance level.
Ruby is intertwingled at this point: the instance methods of Class are the class methods of Class’ instances (the classes themselves.)
This is what allows Ruby to be entirely object-oriented - it folds back on itself in a strange loop.
Consider a simple class
Foo that either creates an instance of itself or an instance of another class
new is called.
(The criteria for deciding which to create is purposefully trivial here, but should demonstrate the idea - much more complicated logic can be used as the situation dictates.)
When this code runs, it produces:
Simple and straightforward.
Bar conditionally created and returned an instance of some other class, say
initialize method would remap the allocated
Bar instance to the
Foo instance would remap to the
Mumble instance and it would ultimately be what was returned from
Cascaded remappings like this require no additional effort or special knowledge outside the
initialize method; multiple levels of alternative remapping happen naturally.
So now there’s a generic Ruby way for object creation to return a different object than the one allocated.
However, care must be taken in the use of this mechanism, lest havoc ensue.
Generally, if the object returned from
new is remapped, the returned object should either be of the same type, or have duck-like aspects of the original where needed.
If what is returned is not compatible, any manner of problems may take place.
This is likely the reason that the semantics of standard Ruby’s new method are what they are.
Careful (and perhaps exhaustive) testing of any code that uses this mechanism should definitely take place!
The mechanism has been added to the eymiha rubygem, available at rubygems.org.