2
votes

When accessing class slots, instead of writing

(defmethod get-name ((somebody person) (slot-value somebody 'name))

is it possible to use the dot notation aka C++, namely

(defmethod get-name ((somebody person) somebody.name) ?

Otherwise, when there are many slot operations in a method, (slot-value... creates a lot of boilerplate code.

I have figured out the answer today and I am just posting it as a Q&A, but if there are better solutions or there are problems I should expect with my solution, feel free to add new answers or comments.

3

3 Answers

6
votes

The library access provides a dot notation reader macro for accessing slots (and hash-tables and other things). After enabling the reader macro by calling (access:enable-dot-syntax) you'll able to use #D. to access a slot name with the dot syntax popular in other languages.

(defclass person ()
  ((name :initarg :name :reader name)))

CL-USER> (access:enable-dot-syntax)
; No values
CL-USER> (defvar *foo* (make-instance 'person :name "John Smith"))
*FOO*
CL-USER> #D*foo*
#<PERSON #x302001F1E5CD>
CL-USER> #D*foo*.name
"John Smith"

There is also a with-dot macro if you don't want to use a reader macro

CL-USER> (access:with-dot () *foo*.name)
"John Smith"
3
votes

You should not write accessors by hand, nor use slot-value (outside of object lifecycle functions, where the accessors may not have been created yet). Use the class slot options instead:

(defclass foo ()
  ((name :reader foo-name
         :initarg :name)
   (bar :accessor foo-bar
        :initarg :bar)))

Now you can use the named accessors:

(defun example (some-foo new-bar)
  (let ((n (foo-name some-foo))
        (old-bar (foo-bar some-foo)))
    (setf (foo-bar some-foo) new-bar)
    (values n old-bar)))

Often, you want your classes to be "immutable", you'd use :reader instead of :accessor then, which only creates the reader, not the setf expansion.

2
votes

The easiest solutions seems to be a reader macro that overloads . so that (slot-value somebody 'name) can be written as .somebody.name My strategy is to read somebody.name as a string (we need to define a non-terminating macro character so that the reader does not stop mid-string), and then process the string to construct the appropriate (slot-value...

I will need two helper functions:

(defun get-symbol (str)
  "Make an uppercase symbol"
  (intern (string-upcase str)))

(defun split-string (str sep &optional (start 0))
  "Split a string into lists given a character separator"
  (let ((end (position sep str :start start)))
    (cons (subseq str start end) (if end (split-string str sep (1+ end))))))

And then I can define my reader macro:

(defun dot-reader (stream char)
  (declare (ignore char))
  (labels ((make-query (list)
             (let ((car (car list))
                   (cdr (cdr list)))
               (if cdr `(slot-value ,(make-query cdr) (quote ,(get-symbol car)))
                   (get-symbol car)))))
    (make-query (nreverse (split-string (symbol-name (read stream)) #\.)))))

Finally, I need to register this reader macro:

(set-macro-character #\. #'dot-reader t)

Now it is possible to write:

(defmethod get-name ((somebody person) .somebody.name)

or, if name is itself a class,

(defmethod get-name ((somebody person) .somebody.name.first-name)

One restriction is that s-expressions will not work between the dots, say

.(get-my-class).name

won't work.