JavaScript-like Objects in Ruby (or Lisp)

As part of my exploration of Ruby, I attended Francis Hwang’s presentation to the New York Linux Users’ Group. One feature that caught my interest in his talk was the OpenStruct class, which lets you assign values to arbitrary “slots” within an object.

require 'ostruct'

me = OpenStruct.new
me.name = "Stuart Sierra"
me.net_worth = -500

Now me.name returns “Stuart Sierra” and me.net_worth returns -500. The clever thing about this is that nowhere did I define name or net_worth as member variables or methods of a class. They just spring into being in the OpenStruct instance as soon as I use them. This is great for developing new data structures before you’re entirely sure what slots you’ll need.

Objects in JavaScript always work this way. JavaScript is prototype-based, so there are no classes, just objects. Each object effectively has its own unique namespace in which you can create new slots simply by using them.

Common Lisp can do this too. Every CL symbol has a property list that can be accessed with get.

CL-USER> (defvar me)
ME
CL-USER> (setf (get 'me 'name) "Stuart Sierra")
"Stuart Sierra"
CL-USER> (get 'me 'name)
"Stuart Sierra"

What I find curious is that this feature of Common Lisp is totally distinct from the object system. Object slots are not symbol properties, just as symbols are not objects (they are bound to objects). But one could make a passable prototype-based object system using just symbol properties and function closures. I think it’s one of those places that shows how Common Lisp grew from many different sources, resulting in similar but distinct ways of doing the same thing. Peter Seibel demonstrates that it can be useful to have both, by using symbol properties to store bookkeeping information needed by macros that generate new classes.

6 thoughts on “JavaScript-like Objects in Ruby (or Lisp)”

  1. Perhaps a better idea would be to use your own plist instead of the symbol’s (`getf’ instead of `get’). Note that in your example you don’t need the `defvar’. If you use your own plist, you would have to give an initial value (e.g., `nil’).

  2. Actually, the symbol plist is the absolutely *wrong* place to put this information. Slots belong to instances of objects, not to the names of objects.

    Objects passed as functional arguments have a name (the parameter name), but that name is NOT global, and therefore, the global symbol’s plist does not belong to that parameter. Furthermore, that functional argument refers to that object only during the particular call; the next time the function is called, the object with that name is likely to be a completely different object.

    An object might, however, have a *slot* which is a plist, to which arbitrary “pseudo-slots” would be added.

    Symbol plists get used when *literal names* have significance, e.g., you are writing an assembler, and you record the definition of assembly labels under a my-assembler::definition property of the symbol named by the label, and the assumed width under a my-assembler::byte-width property, etc. You avoid collisions because no other application cares about those properties (your my-assembler package belongs to you), even if your assembly code input uses a label that matches a Lisp-defined symbol.

  3. Actually you could argue that standard CLOS has the same ability as Ruby, well at least the result is almost the same.

    In CLOS, you can redefine a class and that will take effect even in the running program with objects already instantiated. This means that if one decides to add a new slot to a class, one adds the slot definition, re-evaluate the defclass and voila, you may access the new slots in any object, and you do not have to use any special classes or MOP or anything to achieve the effect.

Comments are closed.