I have a class like this one
(defclass shape ()
((color :initform :black)
(thickness :initform 1)
(filledp :initform nil)
(window :initform nil)))
Is there a function in common-lisp how to get a list of those slots if i only know instance of this class?
Many Common Lisp implementations support the CLOS Meta-object Protocol. This provides introspective operations for classes, slots and other meta objects.
In LispWorks the corresponding functions are directly accessible in the package CL-USER.
CL-USER 139 > (defclass shape ()
((color :initform :black)
(thickness :initform 1)
(filledp :initform nil)
(window :initform nil)))
#<STANDARD-CLASS SHAPE 40202910E3>
CL-USER 140 > (mapcar #'slot-definition-name
(class-direct-slots (class-of (make-instance 'shape))))
(COLOR THICKNESS FILLEDP WINDOW)
The functions slot-definition-name and class-direct-slots are defined by the Meta Object Protocol for CLOS and are supported in many Common Lisp implementations - just the package they are in may differ. In SBCL for example one might find them in the package SB-MOP.
From a class we can get the list of direct slots. Direct slots are the slots which are directly defined for that class and which are not inherited. If you want to get a list of all slots, then use the function class-slots.
Slot here means that we get a slot definition object, which describes the slot. To get the name of the slot, you have to retrieve the name from the slot definition object using the function slot-definition-name.
Related
In the CLHS I read for :read-only x: "When x is true, this specifies that this slot cannot be altered; it will always contain the value supplied at construction time."
Bit I can do this (CCL, SBCL):
CL-USER> (defstruct foo
(one 0 :read-only t))
FOO
CL-USER> (defparameter *foo* (make-foo))
*FOO*
CL-USER> *foo*
#S(FOO :ONE 0)
CL-USER> (setf (slot-value *foo* 'one) 1)
1 (1 bit, #x1, #o1, #b1)
CL-USER> *foo*
#S(FOO :ONE 1)
Shouldn't changing this slot be prohibited by Lisp?
slot-value is not how you access the fields of an object whose class was defined with defstruct. Such objects do not portably have named slots at all: they have named accessors.
Some implementations give the fields of such objects names, and may also allow access to them with slot-value: such behaviour is entirely nonportable however.
If you work within the language defined by the standard then you should not be able to modify the value of a structure field defined with a :read-only option.
The specification says:
setf will not accept the reader function for this slot.
slot-value is not the reader function created by defstruct. The reader function is foo-one (unless you override the naming scheme with the :conc-name keyword). So you should get an error if you try to do
(setf (foo-one *foo) 1)
I would like to know, what is the common approach to common-lisp interactive development in emacs (i use sly, but i think the slime instructions should be the same)
say i have this file:
(eval-when (:compile-toplevel :load-toplevel :execute)
(ql:quickload :closer-mop))
(in-package :cl-user)
(defpackage :shapes
(:use :closer-common-lisp-user)
(:export #:rectangle))
(in-package :shapes)
(defclass rectangle ()
((height :initform 0.0 :initarg :height)
(width :initform 0.0 :initarg :width)))
which is quite simple.
Evaluating it experssion by expression seems to be ok, while loading the whole file (C-c C-l) gives me the following error:
The variable SHAPES:RECTANGLE is unbound.
[Condition of type UNBOUND-VARIABLE]
stripping it down to
(in-package :cl-user)
(defpackage #:shapes
(:use #:cl-user)
(:export #:rectangle))
(in-package #:shapes)
(defclass rectangle ()
((height :initform 0.0 :initarg :height)
(width :initform 0.0 :initarg :width)))
doesn't make any change.
compile-and-load (C-c C-k) doesn't work either, leaving me with:
; in: DEFCLASS RECTANGLE
; (SHAPES::DEFCLASS SHAPES:RECTANGLE NIL
; ((SHAPES::HEIGHT :INITFORM 0.0 :INITARG :HEIGHT)
; (SHAPES::WIDTH :INITFORM 0.0 :INITARG :WIDTH)))
;
; caught COMMON-LISP:STYLE-WARNING:
; undefined function: SHAPES::DEFCLASS
i see that defclass can't be properly resolved to from cl-user:defclass, but can't see the way to fix it.
I wonder what am i missing?
And what is the common flow for developing interactively in emacs?
The underlying problem here is that you are confusing two ways that packages can be used in CL. A package generally serves one, or both, of two purposes:
it can export a number of symbols, providing some kind of interface to functionality;
it can be a package in which other packages are used or from which symbols are imported, but which does not export any symbols (unless it is also a type 1 package).
There is no formal distinction between these types of packages, but there very often is an informal distinction. Packages which are of the second type above are often called *-USER with the canonical example being the CL-USER package. They often (but not always) serve as places for scratch work.
So what you are doing is defining a package whose use list is such a user package. You can see that this is not going to work by simply looking at the external symbols of this package. From your second example:
> (do-external-symbols (s (find-package "CL-USER"))
(print s))
nil
In other words, CL-USER exports no symbols at all. This means that your SHAPES package will initially not have access to any symbols at all, and in particular none of the CL symbols will be present.
Well, the language defines a canonical 'type 1' package, which is CL: the whole purpose of this package is to export the symbols which define the Common Lisp language, and only those symbols. So the definition of the SHAPES packages in your second example should be
(defpackage #:shapes
(:use #:cl)
(:export #:rectangle))
(Note that SHAPES is a type 1 package: it is providing some functionality in the form of SHAPES:RECTANGLE, and presumably is therefore intended to be used by other packages.)
Closer to MOP provides two packages which mirror the standard CL and CL-USER packages:
CLOSER-COMMON-LISP is like CL except that various symbols are replaced by ones defined by Closer to MOP, and there may be additional MOP symbols;
CLOSER-COMMON-LISP-USER is like CL-USER: it's a package intended general use, which users CLOSER-COMMON-LISP but which does not export any symbol at all.
It is said, only special variables in Common Lisp can be unbound. For all lexical variables the default value is nil. I thought that class slots exist in something like closure, but obviously they don't.
If I define a CLOS slots without :initform parameter (in hope that they will be bound to nil anyway) and do not supply values when creating instance, I'll get an instance with unbound slots. Why it is so? It's not handy.
It is very handy for things like computing slot values on demand, where the value may sometimes be nil. When accessing an unbound slot, CLOS normally calls the SLOT-UNBOUND generic function, which signals an error. Instead of an error, though, you can specialize SLOT-UNBOUND to compute and store the value on demand. Subsequent accesses will use the slot value directly, and you can flush the slot "cache" by using SLOT-MAKUNBOUND.
You could do this with a sentinel "not-bound" value of some sort, but it's very handy to have the functionality built-in instead.
Example of slot-unbound use:
(defclass foo ()
((bar :accessor bar)
(baz :accessor baz)))
(defmethod slot-unbound (class (instance foo) slot-name)
(declare (ignorable class))
(setf (slot-value instance slot-name) nil))
In action:
CL-USER> (defparameter *foo* (make-instance 'foo))
*FOO*
CL-USER> (bar *foo*)
NIL
CL-USER> (setf (baz *foo*) (not (baz *foo*)))
T
CLOS instances are not closures
CLOS instances are usually not implemented as closures. That would be difficult. They are some data structure with something like a vector for the slots. Similar to Common Lisp's structures. A difference between CLOS instances and structures complicates it: it is possible for CLOS instances to change the number of slots at runtime and one can change the class of a CLOS instance at runtime.
Making sure slots have a NIL value
With some advanced CLOS you can make sure that slots have a NIL value. Note that the functions in the package CLOS in my example may be in another package in your CL.
This functions looks over all slots of an instance. If a slot is unbound, it gets set to NIL.
(defun set-all-unbound-slots (instance &optional (value nil))
(let ((class (class-of instance)))
(clos:finalize-inheritance class)
(loop for slot in (clos:class-slots class)
for name = (clos:slot-definition-name slot)
unless (slot-boundp instance name)
do (setf (slot-value instance name) value))
instance))
We make a mixin class:
(defclass set-unbound-slots-mixin () ())
The fix will be run after initializing the object.
(defmethod initialize-instance :after ((i set-unbound-slots-mixin) &rest initargs)
(set-all-unbound-slots i nil))
Example:
(defclass c1 (set-unbound-slots-mixin)
((a :initform 'something)
b
c))
CL-USER 1 > (describe (make-instance 'c1))
#<C1 4020092AEB> is a C1
A SOMETHING
B NIL
C NIL
I am building a mechanism to take an arbitrary CLOS object and return a hash from it (useful in my debugging experience).
However, I am not sure how to force a variable expansion. I sense that the solution lies with a correct use of gensym, but I'm not sure how.
;;helper macro
(defun class-slots-symbols (class-name)
"Returns a list of the symbols used in the class slots"
(mapcar 'closer-mop:slot-definition-name
(closer-mop:class-slots
(find-class class-name))))
;;macro that I am having difficulty with
(defmacro obj-to-hash (obj-inst)
"Reads an object, reflects over its slots, and returns a hash table of them"
`(let ((new-hash (make-hash-table))
(slot-list (class-slots-symbols (type-of ,obj-inst))))
;;The slot-list needs to expand out correctly in the with-slots form
(with-slots (slot-list) obj-inst
(loop for slot in slot-list do ;and also here
(format t "~a~&" slot)
(hashset new-hash (string slot) slot)))))
After a macroexpand-1, I find that that this expands into the following code (*bar* is a class object):
(macroexpand-1 '(obj-to-hash *bar*))
LET ((NEW-HASH (MAKE-HASH-TABLE))
(SLOT-LIST (CLASS-SLOTS-SYMBOLS (TYPE-OF *BAR*))))
(WITH-SLOTS (SLOT-LIST) ;; <-- this needs to be expanded to *bar*'s slots
*BAR*
(LOOP FOR SLOT IN SLOT-LIST ;;<-- not so important
DO (FORMAT T "~a~&" SLOT) (HASHSET NEW-HASH (STRING SLOT) SLOT))))
Obviously, the problem is that slot-list is not being expanded. Less obvious (to me) is the solution.
Followup: After Rainer pointed me in the right direction:
(defun class-slots-symbols (class-instance)
"Returns a list of the symbols used in the class slots"
(mapcar 'closer-mop:slot-definition-name
(closer-mop:class-slots
(class-of class-instance))))
(defun object-to-hash (obj)
"Reflects over the slots of `obj`, and returns a hash table mapping
slots to their values"
(let ((new-hash (make-hash-table))
(slot-list (class-slots-symbols obj)))
(loop for slot in slot-list do
(hashset new-hash (string slot)
(slot-value obj slot)))
new-hash))
Just looking at it I can see no reason why this should be a macro. Rewriting it as a function will save you a lot of trouble.
The use of WITH-SLOTS is not possible they way you try it. The object is not known in general until runtime. The compiler needs to know the slots of the object at compile time already. You need to use SLOT-VALUE and look up the slot value at runtime.
You are thinking in many ways too complicated and your code is slightly confused. You can get rid of some confusion by following simple rules and avoiding some wording.
Let's look at your code:
First, it is not a helper macro, since what follows is a function.
;;helper macro
(defun class-slots-symbols (class-name)
Why take a class name? Why not use the class itself? Classes are first class objects. Write function with obvious interfaces. Elementary functions should work on the basic data types.
"Returns a list of the symbols used in the class slots"
In the class slots no symbols are used. slots have names, one can get this symbol.
(mapcar 'closer-mop:slot-definition-name
(closer-mop:class-slots
(find-class class-name))))
It is no wonder you have a problem with this macro. It is simply because it should be a function, not a macro. Macros are for source transformation. All you need is a simple computation, so no macro is needed
;;macro that I am having difficulty with
(defmacro obj-to-hash (obj-inst)
Poor wording: obj-inst. Either name it object or instance. Not both.
"Reads an object, reflects over its slots, and returns a hash table of them"
Poor documentation: you don't READ anything. Read is an I/O operation and in your code is none. You are talking about an 'object', but above you have something like 'obj-inst'. Why talk about the same thing in two different ways? You may want to document what the hash table actual maps. From which keys to which values?
`(let ((new-hash (make-hash-table))
new-hash is also a poor name. Basically the thing is a hash-table.
(slot-list (class-slots-symbols (type-of ,obj-inst))))
Why TYPE-OF and then later in the helper function call FIND-CLASS? Common Lisp has CLASS-OF, which returns the class directly.
;;The slot-list needs to expand out correctly in the with-slots form
(with-slots (slot-list) obj-inst
Above won't work since WITH-SLOTS expects slot names at compile time, not a slot-list.
(loop for slot in slot-list do ;and also here
(format t "~a~&" slot)
(hashset new-hash (string slot) slot)
HASHSET is not needed, unless it does something special. The usual way to set values is via SETF. SETF takes the form to read a place and the form to compute a value. That's all. It works for all kinds of data structures. One never needs to remember again how the writer function looks like (name, parameter list, ...).
))))
Here is my version:
Note that I use the package CLOS, you may want to use your package CLOSER-MOP
(defun class-slots-symbols (class)
"Returns a list of the symbol names of the class slots"
(mapcar 'clos:slot-definition-name
(clos:class-slots class)))
Above is a simple function taking a class and returning the list of slot names.
Next, we have a simple function, which in this form has been written a million times in Common Lisp:
(defun object-to-hash (object)
"returns a hashtable with the object's slots as keys and slot-values as values"
(let ((hash-table (make-hash-table)))
(loop for slot-name in (class-slots-symbols (class-of object))
do (setf (gethash slot-name hash-table)
(string (slot-value object slot-name))))
hash-table))
We can also rewrite it to slightly older style Lisp:
(defun object-to-hash (object &aux (hash-table (make-hash-table)))
"returns a hashtable with the object's slots as keys
and string versions of the slot-values as values"
(dolist (slot-name (class-slots-symbols (class-of object)) hash-table)
(setf (gethash slot-name hash-table)
(string (slot-value object slot-name)))))
Above is much simpler and has the whole confusion about macros, generating code, compile time information vs. runtime, ... removed. It is much easier to understand, maintain and debug.
I'm forming a class for some work on molecular dynamics as follows:
(defclass %atom (particle)
((name :initarg :name :initform (error "Every atom in the system must have a name!"))
(mass :accessor mass :initarg :mass :initform (getmass name))
(charge :accessor charge :initarg :charge :initform (getcharge name))))
Initially I thought that I could somehow refer to other slots within the class definition with an initform i.e. (getmass name) - but that turns out to be untrue (or does it?!?). Instead, I'm told that initialize-instance would be the place to put all that initialization stuff... fair enough.
The question I have, then, is when is :initform used? What's the idiomatic preference? I've seen it used as above for generating (error "...") code, and also to initialize default arguments when an :initarg is not provided. But both of those could easily fit into initialize-instance and may make more sense there. Is there a particular way :initform is generally used?
Usually one would use :initform to give a slot some default value. With defclass one can't compute initforms simply based on other slots. There are also other complications, so :initform should be used for simple forms.
Examples
set the z slot of a 3d-vector object to 0.0
set the list of windows in a screen object to NIL
set the 'key function' slot in an object to #'identity
set the 'report string' slot in an error to "an error occured"
Any other more complicated set up of slot values should be done in a method. If the object needs to be set up automatically after MAKE-INSTANCE use an :AFTER method to INITIALIZE-INSTANCE.
To complicate things a little bit further, there is also the class option :default-initargs.
Personally I use :default-initargs and :initform as shortcuts when the full power of an after-method for initialize-instance is not needed, and :initform when the slot doesn't have an :initarg.