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.

3 Comments

http://gravatar.com/avatar/29f838796175982a838a639f984f4e8e.jpg?d=identicon
Dan Ballard says:

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.

Dec 21, 2007 04:00 AM PST
http://gravatar.com/avatar/c7be29f230d0fc8df73e146bca8366fd.jpg?d=identicon
Zach Beane says:

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.

Dec 21, 2007 06:12 AM PST
http://gravatar.com/avatar/c7be29f230d0fc8df73e146bca8366fd.jpg?d=identicon
Zach Beane says:

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.

Dec 21, 2007 06:13 AM PST

Speak Your Mind

Preview

Commenting Help

Email / Avatar

  • Supply your email address and your Gravatar will be used.
  • I will never email you and your address won't be published.

No HTML allowed!

All HTML is auto-escaped. Use Markdown. Examples:

  • *emphasis* = emphasis
  • **strong** = strong
  • [link](http://foo.bar) = <a href="http://foo.bar">link</a>
  • `code in backticks` = code in backticks
  •     code indented 4 spaces =
    code indented four spaces
  • > Angle-brace quoted text =
    Angle-brace quoted text