Reputation: 535
I have a class with a number of slots. I also have a builder function to make objects of that class such that passing the following list '(:id "john" :name "John Doe" :age 42)
to that function will construct a new object with those slots values. I will use that function to generate more than one object, using a list of lists.
How can I convert from a keyword like :id
to a slot name that SLOT-VALUE
can use?
Thanks.
Upvotes: 7
Views: 1378
Reputation: 542
My solution to this stupidity of CL was:
(defun locate-symbol
(inst kw)
(let* ((slot-name (symbol-name kw))
(slot-def (find slot-name
(clos:compute-slots (class-of inst))
:test #'(lambda (name sd)
(string= name
(symbol-name (clos:slot-definition-name sd)))))))
(if slot-def
(clos:slot-definition-name slot-def)
(error "Can't find a slot definition named ~s." slot-name))))
(defun gets
(self slot-name)
"Get a value of a slot by its name (keyword)"
(slot-value self (locate-symbol self slot-name)))
(defun sets!
(self slot-name value)
"Set a value of a slot by its name (keyword)"
(setf (slot-value self (locate-symbol self slot-name))
value))
So now you can do:
(defvar obj (make-instance '<some-class>))
(sets! obj :some-slot "some value")
(format t "-> ~a~%" (gets obj :some-slot))
Upvotes: -1
Reputation: 51501
I realize that this is quite old, but I think that the most important point to be made here is:
Don't use slot-value
like that!
In order to get an accessor, use the :accessor
or :reader
slot options, and for passing values to the constructor, use :initarg
:
(defclass foo ()
((bar :accessor foo-bar :initarg :bar)))
This means: create a getter method and a setf expander named foo-bar
, and use a keyword argument named :bar
to make-instance
to initialize this slot's value.
Now you can instantiate such an object like this:
(make-instance 'foo :bar "quux")
or, if you get a property list of initargs (as Rainer had already shown):
(let ((initargs (list :bar "quux"))) ; getting this from somewhere
(apply #'make-instance 'foo initargs))
You can then get the value like this:
(foo-bar some-foo)
And set it with setf
as usual:
(setf (foo-bar some-foo) "wobble")
If you use :reader
instead of :accessor
, setting is not allowed. This is often useful to communicate intent of immutability.
Slot-value
is really for special situations in the lifetime of an object, such as when playing around with methods for initialize-instance
. That is an advanced topic.
Upvotes: 1
Reputation: 139251
If the keywords are the initargs for the class, then you just can call MAKE-INSTANCE
via APPLY
:
(defclass person ()
((id :initarg :id )
(name :initarg :name)
(age :initarg :age )))
CL-USER > (mapcar
(lambda (initargs)
(apply #'make-instance 'person initargs))
'((:id "john" :name "John Doe" :age 42)
(:id "mary" :name "Mary Doe" :age 42)))
(#<PERSON 402027AB7B> #<PERSON 402027AC33>)
Upvotes: 10
Reputation: 3970
The find-symbol
and symbol-name
functions will be helpful to you. If defclass
and slot-value
happen in the same package, you can use those functions as follows:
(defclass person ()
((id :initarg :id)
(name :initarg :name)
(age :initarg :age)))
(slot-value (make-instance 'person :id "john" :name "John Doe" :age 42)
(find-symbol (symbol-name :id)))
If defclass
and slot-value
happen in two different packages, you need to give find-symbol
the name of the package where defclass
happens:
(in-package #:common-lisp-user)
(defpackage #:foo
(:use #:common-lisp)
(:export #:person))
(defpackage #:bar
(:use #:common-lisp #:foo))
(in-package #:foo)
(defclass person ()
((id :initarg :id)
(name :initarg :name)
(age :initarg :age)))
(in-package #:bar)
(slot-value (make-instance 'person :id "john" :name "John Doe" :age 42)
(find-symbol (symbol-name :id) 'foo))
(find-symbol name &optional (package (sane-package)))
Function: Return the symbol named STRING in PACKAGE. If such a symbol is found then the second value is :INTERNAL, :EXTERNAL or :INHERITED to indicate how the symbol is accessible. If no symbol is found then both values are NIL.
(symbol-name symbol)
Function: Return SYMBOL's name as a string.
Upvotes: 5