I mentioned that we could write a Lisp macro to allow us to define class instance-variables like Ruby does. I think it’s worth seeing this, not only to see some of the usefulness of Lisp macros but to see some of the considerations you have to make when using them to extend Lisp.

First we’ll need some functions to support creating slot definition lists.

(defun defslot-accessible (accessor-type slot-name)
  "Make a slot definition from a slot-name and accessor-type keyword."
  (if (keywordp slot-name)
      (let ((slot (intern (symbol-name slot-name))))
        (list slot accessor-type slot :initarg slot-name))
      (list slot-name accessor-type slot-name)))

(defun defslots-accessible (accessor-type &rest slot-names)
  "Takes a list of keywords and outputs slot definition lines"
  (loop
     for slot-name in slot-names
     collect (defslot-accessible accessor-type slot-name)))

(defun get-accessor-keyword (attr)
  (ecase attr
    (attr-accessor :accessor)
    (attr-reader   :reader)
    (attr-writer   :writer)))

A couple of notes.

Initargs are optional for object slots in CLOS, so I thought it would was reasonable to reflect that in the kind of symbol that DEFSLOT-ACCESSIBLE receives for a slot-name. If it’s a keyword, it makes a corresponding initarg, if not, it just makes a plain accessor for the slot.

GET-ACCESSOR-KEYWORD maps a Ruby-like “attr_” to CLOS’s accessor keywords. Note that it will error if the attr symbol given is not one of the three types. We’ll catch this error in our macro and assume it’s a regular CLOS slot definition.

Now we can build a macro that builds a DEFCLASS form from Ruby-like attr-accessor expressions.

(defmacro defclass-rubyish (name superclasses
                            &optional slots &rest options)
  `(defclass ,name ,superclasses
     ,(loop
         for indirect-slot in slots
         append (handler-case 
                    (apply 'defslots-accessible
                           (get-accessor-keyword (first indirect-slot))
                           (rest indirect-slot))
                  (type-error () (list indirect-slot))))
     ,@options))

We have just extended Common Lisp. There’s probably a more elegant way, but for now, let’s just see it in action:

CL-USER> (pprint
          (macroexpand-1
           '(defclass-rubyish rbcl ()
             ((attr-reader :foo bar)
              (attr-accessor baz :qux)
              (blerg :writer :blerg :type 'string)
              (pwomp :writer :pwomp :documentation "pwomp!"))
             (:default-initargs
              :foo 0
              :qux 1))))
(DEFCLASS RBCL ()
  ((FOO :READER FOO :INITARG :FOO) 
   (BAR :READER BAR) 
   (BAZ :ACCESSOR BAZ)
   (QUX :ACCESSOR QUX :INITARG :QUX) 
   (BLERG :WRITER :BLERG :TYPE 'STRING)
   (PWOMP :WRITER :PWOMP :DOCUMENTATION "pwomp!"))
  ((:DEFAULT-INITARGS :FOO 0 :QUX 1)))

CL-USER> (defclass-rubyish rbcl ()
           ((attr-reader :foo bar)
            (attr-accessor baz :qux)
            (blerg :writer :blerg :type 'string)
            (pwomp :writer :pwomp :documentation "pwomp!"))
           (:default-initargs
            :foo 0
            :qux 1))
#<STANDARD-CLASS RBCL>

The macro takes our Rubyish syntax and expands into a CLOS class definition. I’m not going to explain how the macro code-building works here. You can learn the basics from chapter 8 of Peter Seibel’s Practical Common Lisp

More advanced macro techniques can be learned from Paul Graham’s On Lisp and still more from Doug Hoyte’s Let Over Lambda.

I do need to explain, why this may not be a good extension of CLOS syntax, and why I don’t expect to use it in my further code examples.

Consider that twist I added to DEFSLOT-ACCESSIBLE: if the argument to slot-name is a keyword symbol it makes a slot with an initarg, but if it’s a regular symbol it does not. In fact, neither the accessor, nor the initarg are necessary for defining a slot. Moreover, they may also be defined more than once for a slot.

In the DEFCLASS-RUBYISH macro, I allowed one to define slots traditionally, in addition to the Rubyish way. In Common Lisp we can define lots of things about class slots. We should look at the Common Lisp Hyperspec.

http://www.lispworks.com/documentation/HyperSpec/Body/m_defcla.htm

So, while this macro adds some convenience for common use cases, it’s functionally redundant, and adds some inconvenience if we have to refactor, say to add type or documentation, or additional accessors or initargs to any slots first defined this way. In Ruby these aren’t concerns, so in Ruby this is good and useful syntax.

Even if we don’t do exactly this, we will probably write macros around DEFCLASS in just this way, and as an example, it’s not too bad.

next

prev