This is a read-only archive!

CLOS vs. Ruby

I thought I sort of understood CLOS, until...

CL-USER> (defclass foo ()
       ((bar :initform "BAR" :accessor bar)))

#<STANDARD-CLASS FOO>
CL-USER> (defparameter *x* (make-instance 'foo))
*X*
CL-USER> (bar *x*)
"BAR"
CL-USER> (defclass foo ()
       ((bar :initform "NEWBAR" :accessor bar)
        (baz :initform "BAZ" :accessor baz)))
#<STANDARD-CLASS FOO>
CL-USER> (defparameter *y* (make-instance 'foo))
*Y*
CL-USER> (bar *y*)
"NEWBAR"
CL-USER> (bar *x*)
"BAR"
CL-USER> (baz *y*)
"BAZ"
CL-USER> (baz *x*)
"BAZ"

The last line was unexpected.

Note in Ruby:

#!/usr/bin/ruby

class Foo
  attr_reader :bar
  def initialize
    @bar = 'BAR'
  end
end

p x.bar # => "BAR"

class Foo
  attr_reader :baz
  def initialize
    @bar = 'NEWBAR'
    @baz = 'BAZ'
  end
end

y = Foo.new

p y.bar # => "NEWBAR"
p x.bar # => "BAR"

p y.baz # => "BAZ"
p x.baz # => nil

So redefining a class alters objects that were already instantiated using an older version of the class. I'm not sure how that works.

December 20, 2007 @ 3:59 PM PST
Cateogory: Programming
Tags: Lisp, Ruby, OOP

3 Comments

Dan Ballard
Quoth Dan Ballard on December 20, 2007 @ 8:00 PM PST

I've been reading Practical Common Lisp too and have been having a fling with Ruby over the last few years.

I think what it is is this, in CLOS perhaps data isn't initialized until accessed for the first time, some kind of lazy evaluation. Either way, the first three lines of accessing at the end of your CLOS example make sense.

Now, CLOS doesn't use message passing as do many other OO languages, but instead relies on Generics, functions. So classes don't have methods, there are simply generic functions tuned to classes. So when you redefined the class with the baz piece of data, defclass is a macro which generates code, and in this case, it generates generic functions for the class that are accessors.

More specifically, the code in those generic function accessors say: get the variable and if it isn't defined, use the init form as its initial data.

So the class it self is just a collection of data. When you redefied the class you made a new generic accessor function of baz, and when you called it one the old function, i was deesigned to run on that class, looked, found no baz variable, and used init form to initialize it. This train of thought makes sense so the only potential voodoo is that they had to make sure when to redefine classes that the class itself that just stores data can be updated, but if they're just using a hash internally anyway, its trivial and automatically handled.

I kind of reasoned that out as I typed it up, hope it makes sense.

Zach Beane
Quoth Zach Beane on December 20, 2007 @ 10:12 PM PST

CLOS has class redefinition in the bag. You can customize what happens to old instances by adding methods to the standard UPDATE-INSTANCE-FOR-REDEFINED-CLASS generic function. The default method does what you observed here.

CLOS supports design evolution (and customization of the process) very, very strongly.

Zach Beane
Quoth Zach Beane on December 20, 2007 @ 10:13 PM PST

Dan Ballard: no need to pull a definition out of your intuition (it's wrong in this case). You can just look up what happens in the hyperspec.