Separating initialization arguments and class slots in Common Lisp Object System for making objects - oop

This asks about initializing slots from other slots. What I want to achieve instead is to take some arguments as input - perhaps but not necessarily to make-instance - and convert these arguments into the class slots for storage. In effect, I want to separate the implementation of the class from its (initialization) interface.
Is there a recommended way to achieve this?
The simplest way I can imagine is simply create a (defun make-my-object ...) as the interface. This may then call make-instance with appropriate arguments.
For example, imagine
(defclass my-object () (slot-1 slot-2))
(defun make-my-object (arg-1 arg-2)
(make-instance 'my-object
:slot-1 (+ arg-1 arg-2)
:slot-2 (- arg-1 arg-2)))
Other ways I can imagine include implementing an initialize-instance :after that takes arg-1 and arg-2 as keyword arguments and initializes slot-1 and slot-2 appropriately. However, since after methods are called in the least-specific-first-order, that means that superclass slots will be initialized before current-class slot. On the other hand, what looks more common is that one will take arguments for constructing current-class, and on the basis of these arguments, one will initialize the super-class slots.
The alternative is initialize-instance :before - or also :around - but if multiple classes in the hierarchy have such "interface-implementation" differences, I don't see this working, unless I can pass arguments to call-next-method.
Are there other ways?
EDIT: Thanks to #ignis volens for bringing to my notice that (one of) my main concern(s) is about superclass slots being initialized from subclass slots. Is there a recommended way to do this?

I am not sure I understand your question. The answer is almost certainly after methods on initialize-instance I think. You say that this will cause slots defined in superclasses to be initialized first: yes, it will, and that almost certainly what you want to happen. Slots defined in superclasses don't generally depend for their values on subclass slots (its always possible to think of exceptions to everything) and so initialising in least-specific first order is almost always what you want.
The two common ways of initializing slots that I use are either to simply declare what their initargs are in the definition:
(defclass minibeast ()
((legs :initform 'uncountable
:initarg :legs
:initarg :leg-count
:accessor legs)
(tentacles :initform 'many
:initarg :tentacles
:initarg :number-of-tentacles
:accessor tentacles)))
And now (make-instance 'minibeast :legs 87) does what you expect. And this works (because, obviously it has to if the two slots were defined in different classes):
(defclass awful-monster ()
((legs :initform 'uncountable
:initarg :legs
:initarg :leg-count
:accessor legs)
(appendages :initform 'many
:initarg :legs
:initarg :appendages)))
Now (make-instance 'awful-monster :legs 93) will result in an awful monster with 93 legs and 93 appendages.
However that method perhaps doesn't qualify as separating interface from implementation. You may also want to perform some computation when initialising slots. In both these cases after methods on initialize-instance are generally the right approach:
(defclass horrible-monster ()
((legs :initform 983
:accessor legs)
(eyes :initform 63
:accessor eyes)
(appendages
:reader appendages)))
(defmethod initialize-instance :after
((m horrible-monster) &key eyes legs (stalky-eyes t))
(with-slots ((e eyes) (l legs) appendages) m
(when eyes (setf e eyes))
(when legs (setf l legs))
(setf appendages (if stalky-eyes (+ e l) l))))
And now horrible monsters will get the appropriate number of appendages (I am not sure why horrible monsters don't know whether their eyes are on stalks or not: perhaps they don't have mirrors).
There are any number of other combinations of course. You might well not want to have user code call make-instance explicitly but wrap things up in some function:
(defun make-awful-thing (&rest args &key (sort-of-horrible-thing 'horrible-monster)
&allow-other-keys)
(let ((the-remaining-args (copy-list args)))
;; No doubt alexandria or something has a way of doing this
(remf the-remaining-args ':sort-of-horrible0thing)
(apply #'make-instance sort-of-horrible-thing the-remaining-args)))
And now, of course you can have some bespoke initialzation protocol easily:
(defgeneric enliven-horrible-thing (horrible-thing &key)
(:method :around ((horrible-thing t) &key)
(call-next-method)
t))
(defun make-awful-thing (&rest args &key (sort-of-horrible-thing 'horrible-monster)
&allow-other-keys)
(let ((the-remaining-args (copy-list args)))
;; No doubt alexandria or something has a way of doing this
(remf the-remaining-args ':sort-of-horrible0thing)
(apply #'enliven-horrible-thing
(apply #'make-instance sort-of-horrible-thing
the-remaining-args)
the-remaining-args)))
(defmethod enliven-horrible-thing ((horrible-thing horrible-monster)
&key (ichor t) (smell 'unspeakable))
...)

Related

Common Lisp structures with dynamically scoped slots

Common Lisp is lexically scoped, but there is a possibility to create dynamic bindings with (declare (special *var*)). What I need, is a way to create a dynamically scoped structure slot, whose value is visible to all other slots. Here is an example:
(defun start-thread ()
*delay*) ;; We defer the binding of *delay*
This works for a usual lexical environment:
(let ((*delay* 1))
(declare (special *delay*))
(start-thread)) ;; returns 1
This does not work:
(defstruct table
(*delay* 0)
(thread (start-thread)))
(make-table) ;; => Error: *delay* is unbound.
My questions are
How to refer to the slot delay from other slots?
How to make the slot delay dynamically scoped, so that its value becomes visible
for the function (start-thread) ?
The first thing to realise that there's no good way to have a dynamically-scoped slot in an object (unless the implementation has some deep magic to support this): the only approach that will work is to use, essentially, explicit shallow-binding. Something like this macro, for instance (this has no error checking at all: I just typed it in):
(defmacro with-horrible-shallow-bound-slots ((&rest slotds) object &body forms)
(let ((ovar (make-symbol "OBJECT"))
(slot-vars (mapcar (lambda (slotd)
(make-symbol (symbol-name (first slotd))))
slotds)))
`(let ((,ovar ,object))
(let ,(mapcar (lambda (v slotd)
`(,v (,(first slotd) ,ovar)))
slot-vars slotds)
(unwind-protect
(progn
(setf ,#(mapcan (lambda (slotd)
`((,(first slotd) ,ovar) ,(second slotd)))
slotds))
,#forms)
(setf ,#(mapcan (lambda (slotd slot-var)
`((,(first slotd) ,ovar) ,slot-var))
slotds slot-vars)))))))
And now if we have some structure:
(defstruct foo
(x 0))
Then
(with-horrible-shallow-bound-slots ((foo-x 1)) foo
(print (foo-x foo)))
expands to
(let ((#:object foo))
(let ((#:foo-x (foo-x #:object)))
(unwind-protect
(progn (setf (foo-x #:object) 1) (print (foo-x foo)))
(setf (foo-x #:object) #:foo-x))))
where all the gensyms with the same name are in fact the same. And so:
> (let ((foo (make-foo)))
(with-horrible-shallow-bound-slots ((foo-x 1)) foo
(print (foo-x foo)))
(print (foo-x foo))
(values))
1
0
But this is a terrible approach because shallow binding is terrible in the presence of multiple threads: any other thread that wants to look at foo's slots will also see the temporary value. So this is just horrid.
A good approach is then to realise that while you can't safely dynamically-bind a slot in an object, you can dynamically bind a value which that slot indexes by using a secret special variable to hold a stack of bindings. In this approach the values of slots do not change, but the values they index do, and can do so safely in the presence of multiple threads.
A way of doing this this is Tim Bradshaw's fluids toy. The way this works is that you define the value of a slot to be a fluid, and then you can bind that fluid's value, which binding has dynamic scope.
(defstruct foo
(slot (make-fluid)))
(defun outer (v)
(let ((it (make-foo)))
(setf (fluid-value (foo-slot it) t) v) ;set global value
(values (fluid-let (((foo-slot it) (1+ (fluid-value (foo-slot it)))))
(inner it))
(fluid-value (foo-slot it)))))
(defun inner (thing)
(fluid-value (foo-slot thing)))
This often works better with CLOS objects because of the additional flexibility in things like naming and what you expose (you almost never want to be able to assign to a slot whose value is a fluid, for instance: you want to assign the value of the fluid).
The system uses a special variable behind the scenes to implement deep binding for fluids, so will work properly with threads (ie distinct threads can have different bindings for a fluid) assuming the implementation treats special variables sensibly (which I'm sure all multithreaded implementations do).
I don't think that this makes sense. Variables have scope and extent, but values just are, and slots are just parts of values. Additionally, threads do not inherit dynamic bindings.
If you want to have some kind of object that is dynamically changed (so to speak), you need to put it into a dynamic variable as a whole value, and do re-bindings with modified versions (preferably on the basis of some immutability, i. e. persistent datastructures, e. g. with FSet).
I'm doing a bit of guessing about what you need here, but I think using a class and initialize-instance will give you what you want. In the code below, I rewrote your struct as a class, and the object itself is passed to initialize-instance in a call to (make-instance 'table).
(defclass table ()
((delay :initform 5)
(thread)))
(defun start-my-thread (obj)
(print (slot-value obj 'delay)))
(defmethod initialize-instance :after ((obj table) &key)
(start-my-thread obj))
(make-instance 'table)
; above call will print 5

Why does `class-name` does not work in the REPL for this case?

I am reading the book Object Oriented Programming in Common Lisp from Sonja Keene.
In chapter 7, the author presents:
(class-name class-object)
This would make possible to query a class object for its name.
Using SBCL and the SLIME's REPL, I tried:
; SLIME 2.26.1
CL-USER> (defclass stack-overflow ()
((slot-1 :initform 1 )
(slot-2 :initform 2)))
#<STANDARD-CLASS COMMON-LISP-USER::STACK-OVERFLOW>
CL-USER> (make-instance 'stack-overflow)
#<STACK-OVERFLOW {1002D188E3}>
CL-USER> (defvar test-one (make-instance 'stack-overflow))
TEST-ONE
CL-USER> (slot-value test-one 'slot-1)
1
CL-USER> (class-name test-one)
; Evaluation aborted on #<SB-PCL::NO-APPLICABLE-METHOD-ERROR {10032322E3}>.
The code above returns the error message below:
There is no applicable method for the generic function
#<STANDARD-GENERIC-FUNCTION COMMON-LISP:CLASS-NAME (1)>
when called with arguments
(#<STACK-OVERFLOW {1003037173}>).
[Condition of type SB-PCL::NO-APPLICABLE-METHOD-ERROR]
How would be the proper use of class-name?
Thanks.
The argument to class-name must be a class object, not an instance of the class.
Use class-of to get the class of the instance, then you can call class-name
(class-name (class-of test-one))
Using #Barmar's hint on a comment, this would the correct approach with class-name:
CL-USER> (class-name (defclass stack-overflow ()
((slot-1 :initform 1 )
(slot-2 :initform 2))))
STACK-OVERFLOW
class-name receives as an argument a class. In order to work with instances, the correct approach is using class-of:
CL-USER> (class-of 'test-one)
#<BUILT-IN-CLASS COMMON-LISP:SYMBOL>
I am not sure why class-name would be helpful, though.

CLOS: Method combination with arbitrary function

While reading about CLOS (in ANSI Common Lisp by Paul Graham), I noticed that there are nine functions that can be given to defmethod as its second argument:
+, and, append, list, max, min, nconc, or and progn. According to this answer, they are called simple method combinations.
Question
Why only these nine? What is the reason I cannot pass an arbitrary function as the second argument?
Example of what I would like
Suppose I defined xor as
(defun xor (&rest args)
(loop for a in args counting (not (null a)) into truths
finally (return (= truths 1))))
(this could certainly be improved). I would like to define several classes describing clothes and their combinations using xor:
(defgeneric looks-cool (x)
(:method-combination xor))
(defclass black-trousers () ())
(defclass quilt () ())
(defclass white-shirt () ())
(defclass hawaii-shirt () ())
(defmethod looks-cool xor ((tr black-trousers)) nil)
(defmethod looks-cool xor ((qu quilt)) t)
(defmethod looks-cool xor ((ws white-shirt)) nil)
(defmethod looks-cool xor ((hs hawaii-shirt)) t)
(defclass too-stiff (black-trousers white-shirt) ())
(defclass scottish (quilt white-shirt) ())
(defclass also-good (black-trousers hawaii-shirt) ())
(defclass too-crazy (quilt hawaii-shirt) ())
Now if this compiled (which it doesn't), I would be able to use Lisp to guide me as to what to wear:
> (looks-cool (make-instance 'too-stiff))
NIL
> (looks-cool (make-instance 'scottish))
T
> (looks-cool (make-instance 'also-good))
T
> (looks-cool (make-instance 'too-crazy))
NIL
I am well aware that this is a rather artificial example of no practical importance. Still, I would like to know whether there is some deeper reason behind or whether the restriction to the nine functions is just to make implementation easier.
Use the standard Common Lisp macro DEFINE-METHOD-COMBINATION to define your own simple method combinations:
Example:
(define-method-combination xor :identity-with-one-argument t)
Then:
CL-USER 5 > (mapcar #'looks-cool
(list (make-instance 'too-stiff)
(make-instance 'scottish)
(make-instance 'also-good)
(make-instance 'too-crazy)))
(NIL T T NIL)
If we look at (define-method-combination xor :identity-with-one-argument t), it has several meanings for the name xor:
it uses an operator xor - a function, macro or special form - not only functions are allowed. If the operator name should be different from the method combination name -> use the :operator keyword to specify that.
it defines a method combination named xor. This name can be used in defgeneric.
it defines a method qualifier xor. This can be used in defmethod.
Note that one can also define more complex method combinations with that DEFINE-METHOD-COMBINATION.

Defining custom setf for class in lisp

Suppose I have a class called board:
(defclass board ()
((blocker :accessor blocker :initarg :blocker :initform 0))
According to this book I can define a custom setf for blocker by:
(defmethod (setf blocker) (new-blocker (b board))
(setf (slot-value b 'blocker) new-blocker))
However, steel bank common lisp will say function not defined, even though I have evaluated it. Does anyone know what's wrong here?
That looks correct. Note that you are redefining the already existing setf method that you created by specifying :accessor blocker. SBCL will give you a style-warning about that.
Your mistake is somewhere else. Are you in a different package, perhaps? Try to show the steps you have taken in your IDE to compile and load those forms, and to attempt to run that method invocation.
You have to declare a generic function before defining any methods.
(defgeneric (setf blocker) (new-blocker board))
See this chapter in Practical Common Lisp for an example.

Slots in CLOS

Could any CL'er please explain 'slots' in CLOS? I am finding it difficult to understand the part after the slot name. That is in :
(defclass foo ()
(data1 :initarg foo))
What do the 'initarg' and other such similar things mean? I am re-reading manuals. So, I would really appreciate if any of you here could explain it to a layman like me.
Thanks!
Your example is slightly wrong. It has to be:
(defclass foo ()
((data1 :initarg foo)))
Notice the added parentheses to indicate a list of slot descriptions.
DEFCLASS takes a list of slots. So with two slots we have:
(defclass foo ()
((data1 :initarg :data1arg
:initform (random 1.0)
:type number
:documentation "doc here"
:accessor foo-data1-acc)
(data2 :initarg :data2arg)))
DATA1 is the name of the slot. Behind that you find pairs of :keyword value.
:INITARG tells you what the parameter for MAKE-INSTANCE is.
(make-instance 'foo :data1arg 10) ; creates the object and sets the slot data1 to 10.
Usually you should use a keyword symbol (like :data1arg here).
:INITFORM sets the slot by default, when the object is created. Like in:
(make-instance 'foo) ; creates the object. The slot is set to the value of the initform.
:TYPE specifies the type of the slot's object.
:DOCUMENTATION is just a string for, well, documentation.
:ACCESSOR specifies a function to read and write the slot.
(foo-data1-acc some-foo-object-here) ; read
(setf (foo-data1-acc some-foo-object-here) 7) ; write
Note that you can write the pairs in any order and that you can also specify multiple accessor functions. There are also :READER and :WRITER functions.
With CLOS you can specify all that within the DEFCLASS macro. These things are not automatically generated like in defstruct, which has a shorter notation.
The description of DEFCLASS is here: DEFCLASS.
Short CLOS Intro.
In a slot specification, the general syntax is (slot-name [slot-option option-value]...). The essentially-authoritatiev reference is the HyperSpec page on defclass, but in short:
:reader A function to read the value of the slot
:writer A function to write the value of the slot
:accessor A function to both read and (via SETF) set the value of the slot
:initarg A symbol to set the slot's value from MAKE-INSTANCE
There are more, but that list is the four I mostly use (actually, I mostly use :initarg and one of :accessor or :reader).