Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I convert a keyword to a symbol suitable to access a slot?

Tags:

common-lisp

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.

like image 280
sbenitezb Avatar asked Sep 17 '12 22:09

sbenitezb


3 Answers

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>)
like image 133
Rainer Joswig Avatar answered Sep 23 '22 17:09

Rainer Joswig


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.

like image 38
dkim Avatar answered Sep 22 '22 17:09

dkim


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.

like image 38
Svante Avatar answered Sep 20 '22 17:09

Svante