I have just gotten to a release point on my Whatever project. There are lots of projects around that want to treat objects more like dictionaries where you don't have to pre-define what instance variables you're going to use. These sorts of objects are like Self objects and are usually implemented with a dictionary instance variable and a DNU to act like getters and setters.
Several weeks ago it dawned on me that I wanted to make yet another one of these - but do it the most efficient way. The 'efficient' way is usually done by using at:'s and at:put:'s to access in to array slots and resizing the array when you run out of room. This is a lot faster than using a dictionary. Smarter implementations install methods on to a copy of the class to do the at: and at:put:
However, at: and at:put: are actually slower than instance variable access bytecodes. So in this implementation, we install methods on to our class copy that treat the shape of the class as if those slots were instance variables and not object slots in an array. This results in the fastest way we could have shapeless objects.
I'm now using Whatever.Object in Seaside-Mootools and Seaside-Javascript to represent a client side javascript object, since javascript objects are like Self objects and can have any slots you want on them. This greatly reduces the amount of code on the server side when trying to represent javascript objects and their subdry combinations of options.
The package is published in to public store as Whatever v29. Enjoy!
And now the package comment:
Whatever provides an efficient shapeless Object, much like a self object. You can set any slots you want on each instance, give it custom behavior and even inherit that behavior with new sub-instances. Consider this 'classless' objects.
Whatever.Object new name: 'My name'; age: 76; gender: #happy; lifeStory: (Whatever.Object new title: 'My epic tale of classlessness'; content: 'It was a dark and stormy night...'; yourself); methodAt: 'printString' put: '^self name, '', aged: '', self age printString'; yourself
This package implements these shapeless objects in one of the most efficient ways possible. It makes a copy of the class you're instantiating and when it receives a DoesNotUnderstand, if the selector looks like a getter or setter, the copy of the class will install getter and setter methods that directly access array slots so that the code runs as fast as a defined class would.
Each Whatever is technically an Array, but that is hidden to treat it like an object. Because you can define your own behavior on the instance, we can pretend there is no class at all.
In fact, there are two classes per instance - this may seem heavy weight, but the VM doesn't mind and it actually doesn't cost us that much. The class you instantiate from is first copied as a behaviorClass and then copied again for a shapeClass. When you add methods to your Whatever.Object, they are added to behaviorClass and when you add instance variables, they are added to the shapeClass.
When you subclass a Whatever.Object instance with #new or #clone, you do not inherit the shape methods from the superclass, such that you don't get shape change paradoxes between parent and child whatevers.
"Make a sub instance" (Whatever.Object new methodAt: 'test' put: '^true'; new) test "Make a clone instance" (Whatever.Object new methodAt: 'test' put: '^true'; clone) test