I'm trying to implement a programming language in Common Lisp by following this implementation in Java (https://craftinginterpreters.com/control-flow.html).
One of the things that was being really bothersome is plenty of slot-values everywhere.
Granted I can add a line in each function with with-slots, but that's also repetitive, and given that my symbols are not :used but I refer to them by the package, as there is plenty of symbols from many modules and I don't want to lose track from where they come from, even with-slots requires the full qualified name. All this seems to me like very bad code smell.
I googled and found rutils. With its @object.slot accessor I managed to clean up my code immensely. Just look at the last commit https://github.com/AlbertoEAF/cl-lox/commit/84c0d62bf29bff9a2e0e77042a9108c168542b96?diff=split excerpt:
(I could paste the code but all of it is available on that repo, I wanted to show the diff highlights)
Not only it removes some characters, but more importantly, there's one less level of depth (slot-value call) and parenthesis to think about - which helps a lot but for the trivial functions.
It gets worse when I have lots and lots of symbol names and can no longer export them all because I start having conflicts in symbols.
Can you guys give me input on the code? This looks much better now but I'm curious as if to are better ways to go about it?
Thanks!
If you define a CLOS class like this:
(defclass person ()
((name
:initarg :name)))
(defparameter *p* (make-instance 'person
:name "John"))
The only way to access then the slot value of name
slot in *p*
are:
(slot-value *p* 'name)
;; "John"
(with-slots (name) *p*
name)
;; "John"
(with-slots ((nm name)) *p*
nm)
;; "John"
But if you define for each slot an :accessor
, you can use the function name
given as argument of :accessor
and don't have to use slot-value
or with-slots
, to
access and to mutate it (setf
-able!).
(defclass person ()
((name
:initarg :name
:accessor nm)))
(nm *p*)
;; "John"
(setf (nm *p*) "Doe")
;; "Doe"
(nm *p*)
;; "Doe"
The convention however is, to use the slot name also as the :accessor
method's
name:
(defclass person ()
((name
:initarg :name
:accessor name))) ;; but better use the slot name as accessor
(name *p*)
;; "John"
(setf (name *p*) "Doe")
(name *p*)
;; "Doe"
The :accessor
method (generic function) is specific for the objects of
this class. Therefore you don't need to have fear of a name-space clash.
(defclass house ()
((address
:initarg :address
:accessor addr)))
;; you cannot use `address` because it is already occupied
;; by a system's function/symbol -> see under `(describe 'address)`
(defparameter *h* (make-instance 'house :address "Bakerstreet 1"))
(name *h*)
;; EVAL: undefined function name OR:
;; NO-APPLICABLE-METHOD error (in the case
;; that other classes exist with a `name` accessor method.
(addr *h*)
;; "Bakerstreet 1"
(addr *p*)
;; *** - NO-APPLICABLE-METHOD: When calling #<STANDARD-GENERIC-FUNCTION ADDR>
;; with arguments (#<PERSON #x1B06B79E>), no method is applicable.
;;