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

4

7

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.

Gangboard answered 17/9, 2012 at 22:23 Comment(0)
R
5

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.

Rodriguez answered 17/9, 2012 at 23:50 Comment(0)
E
10

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>)
Epicardium answered 18/9, 2012 at 6:21 Comment(3)
I like your "more functional" approach.Gangboard
@WhiteCat Was your question about how to call make-instance with those lists? I understood your question differently because the question's title and body say "How can I convert a keyword to a symbol suitable to access a slot?" and "How can I convert from a keyword like :id to a slot name that SLOT-VALUE can use?". If your real goal is just to call make-instance, not slot-value, Rainer Joswig's solution is a way to go.Rodriguez
@dkim: my question was exactly as written, which you answered correctly. I had tried with (find-symbol...) before but used KEYWORD as package, so it didn't work. Rainer's answer gave me just another perspective, one which I didn't consider and which is the correct solution. Thanks both.Gangboard
R
5

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.

Rodriguez answered 17/9, 2012 at 23:50 Comment(0)
W
1

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.

Winterize answered 18/7, 2017 at 19:50 Comment(0)
K
-1

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))
Kowtow answered 18/7, 2017 at 14:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.