What's the equivalent of constructors in CLOS? - lisp

How would you express the following Java code in Lisp?
class Foo {
private String s;
public Foo(String s) {
this.s = s;
}
}
class Bar extends Foo {
public Bar(int i) {
super(Integer.toString(i));
}
}
In Lisp, is make-instance or initialize-instance the equivalent of a constructor? If yes, how do you call the super class constructor(s)?

Use CALL-NEXT-METHOD from within a method to call the next one.
Typically just use the standard method combination facility and write an :BEFORE, :AFTER or :AROUND method for INITIALIZE-INSTANCE.
One way:
(defclass foo ()
((s :type string :initarg :s)))
(defclass bar (foo) ())
(defmethod initialize-instance :around ((b bar) &key i)
(call-next-method b :s (princ-to-string i)))
Example:
CL-USER 17 > (make-instance 'bar :i 10)
#<BAR 402000B95B>
CL-USER 18 > (describe *)
#<BAR 4130439703> is a BAR
S "10"
Note that I've called CALL-NEXT-METHOD without changing the argument it dispatches on. I just changed the &key initarg parameter.

Here is an amplification of Rainier's example that adds a small twist:
specialization (subclassing) by constraining slot values, at least at
construction time. Consider a class, m-box, for two-dimensional rectangles:
(defclass m-box ()
((left :accessor m-box-left :initform 0 :type integer :initarg :left )
(top :accessor m-box-top :initform 0 :type integer :initarg :top )
(width :accessor m-box-width :initform 0 :type integer :initarg :width )
(height :accessor m-box-height :initform 0 :type integer :initarg :height)))
We can try it like this:
(describe (make-instance 'm-box :left 42 :top -39 :width 5 :height 11))
: #<M-BOX {10047A8F83}>
: [standard-object]
:
: Slots with :INSTANCE allocation:
: LEFT = 42
: TOP = -39
: WIDTH = 5
: HEIGHT = 11
Now consider a subclass or specialization: let an m-block be an m-box with
unit width and height. We set the initialize-instance method to pass through
values for left and top, but not width and height:
(defclass m-block (m-box) ())
(defmethod initialize-instance
:around
((mb m-block)
&key (left 0) (top 0))
(call-next-method mb :left left :top top :width 1 :height 1))
We can make an instance of m-block as follows:
(describe (make-instance 'm-block :left 17 :top -34 :width 5 :height 11))
: #<M-BLOCK {10049C0CC3}>
: [standard-object]
:
: Slots with :INSTANCE allocation:
: LEFT = 17
: TOP = -34
: WIDTH = 1
: HEIGHT = 1
The constructor does not block the user's attempt to set width and height,
as it would do with some non-existent slot:
(describe (make-instance 'm-block :left 17 :top -34 :plugh 2345))
Invalid initialization argument:
:PLUGH
in call for class #<STANDARD-CLASS COMMON-LISP-USER::M-BLOCK>.
[Condition of type SB-PCL::INITARG-ERROR]
but the constructor does correct the user's invalid inputs with 1.
You might want to make the model more watertight by calling error if the
user attempts to input invalid width or height:
(defclass m-block (m-box) ())
(defmethod initialize-instance
:around
((mb m-block)
&key (left 0) (top 0) (width 1) (height 1))
(when (or (/= 1 width) (/= 1 height))
(error "an m-block must have unit width and height"))
(call-next-method mb :left left :top top :width 1 :height 1))
The following attempt by the user is rejected:
(describe (make-instance 'm-block :left 17 :top -34 :width 5 :height 11))
an m-block must have unit width and height
[Condition of type SIMPLE-ERROR]
But this one, which also demonstrates defaulting of height, goes through:
(describe (make-instance 'm-block :left 17 :top -34 :width 1))
: #<M-BLOCK {1005377983}>
: [standard-object]
:
: Slots with :INSTANCE allocation:
: LEFT = 17
: TOP = -34
: WIDTH = 1
: HEIGHT = 1
This example allows the user to setf the width or height afterwards. I do not
know how to make the width and height read-only in instances of the subclass
and not in instances of the superclass.

Related

Calling Class allocated slot on class-names in Common Lisp

Is there any way to call a :class allocated slot on the name of a class instead of an instance? Something like: (class-alloc-slot 'name-of-the-class)
LispWorks:
CL-USER 6 > (defclass foo () ((bar :allocation :class :initform :baz)))
#<STANDARD-CLASS FOO 402005B3CB>
CL-USER 7 > (make-instance 'foo)
#<FOO 4020240C33>
CL-USER 8 > (class-prototype (find-class 'foo))
#<FOO 402005EB73>
CL-USER 9 > (slot-value * 'bar)
:BAZ
use CLOSER-MOP for portable MOP features.

CLOS slot accessors: read but not write

I have a list of the names of slots of a CLOS object:
(DEFCLASS TRIAL-DATA (STANDARD-OBJECT)
((A-DATUM :ACCESSOR A-DATUM :INITARG :A-DATUM :INITFORM NIL)
(BOTH-DATA :ACCESSOR BOTH-DATA :INITARG :BOTH-DATA :INITFORM 0)
(CUMULATIVE-DATA :ACCESSOR CUMULATIVE-DATA :INITARG :CUMULATIVE-DATA :INITFORM NIL)
(NAME :ACCESSOR NAME :INITARG :NAME :INITFORM VALUE)))
(let* ((td (make-instance 'trial-data))
(slot-lst (mapcar #'slot-definition-name (class-slots (class-of td)))))
I can read the values of these slots:
(let* ((td (make-instance 'trial-data))
(slot-lst (mapcar #'slot-definition-name (class-slots (class-of td)))))
(funcall (symbol-function (nth 0 slot-lst)) td))
==> NIL
But why can I not write new values to these slots? Shouldn't my class definition of trial-data have created an accessor function for each slot?
;; Should set the first slot, a-datum's, value to 42
(let* ((td (make-instance 'trial-data))
(slot-lst (mapcar #'slot-definition-name (class-slots (class-of td)))))
(setf (funcall (symbol-function (nth 0 slot-lst)) td) 42))
==>
;Compiler warnings for "/Users/frank/Documents/NRL/Error/Patrolbot/Patrol Construction Notes & Testing.lisp" :
; In an anonymous lambda form at position 123: Undefined function (SETF FUNCALL)
> Error: Undefined function (SETF FUNCALL) called with arguments (42 #<STANDARD-GENERIC-FUNCTION A-DATUM #x302001D1C5DF> #<TRIAL-DATA #x30200200D95D>) .
> While executing: #<Anonymous Function #x30200200EB7F>, in process Listener-2(5).
The accessor is called a-datum.
The reader:
CL-USER 9 > #'a-datum
#<STANDARD-GENERIC-FUNCTION A-DATUM 406000091C>
The writer:
CL-USER 10 > #'(setf a-datum)
#<STANDARD-GENERIC-FUNCTION (SETF A-DATUM) 422000958C>
If you want to call via funcall the writer, you need to call above function.
If you have a plain form (setf (a-datum foo) 'bar)) then this needs to be resolved at macro expansion time.
The error message says that #'(setf funcall) is undefined. Thus (setf (funcall ...) ...) does not exist.
How do you get the writer function in your case?
CL-USER 11 > (fdefinition '(setf a-datum))
#<STANDARD-GENERIC-FUNCTION (SETF A-DATUM) 422000958C>
CL-USER 12 > (let ((name 'a-datum)) (fdefinition `(setf ,name)))
#<STANDARD-GENERIC-FUNCTION (SETF A-DATUM) 422000958C>
Task for you: what are the correct arguments for above function?
Rainer Joswigs's answer addresses the issue of why you can't set with the code that you have now. However, it's also important to note that there's no reason that reader, writer, or accessor name has to be the same as the slot name, so if what you've actually got is the slot name, then you should use (setf slot-value) with it. E.g.,
(defclass foo ()
((bar :accessor getbar :initform 42)))
(defparameter *foo* (make-instance 'foo))
;; neither of these work
(setf (bar *foo*) 34)
(funcall #'(setf bar) 34 *foo*)
(slot-value *foo* 'bar)
;=> 42
(setf (slot-value *foo* 'bar) 36)
;=> 26
(slot-value *foo* 'bar)
;=> 36

How do i access the :Documentation string of a slot of a Defclass in Common lisp

Ok here is How i instantiate my Defclass and related Defmethod and Defparameter
(defvar *account-numbers* 0)
(defclass bank-account ()
((customer-name
:initarg :customer-name
:initform (error "Must supply a customer name.")
:accessor customer-name
:documentation "Customer's name")
(balance
:initarg :balance
:initform 0
:reader balance
:documentation "Current account balance")
(account-number
:initform (incf *account-numbers*)
:reader account-number
:documentation "Account number, unique within a bank.")
(account-type
:reader account-type
:documentation "Type of account, one of :gold, :silver, or :bronze.")))
(defmethod initialize-instance :after ((account bank-account)
&key opening-bonus-percentage)
(when opening-bonus-percentage
(incf (slot-value account 'balance)
(* (slot-value account 'balance) (/ opening-bonus-percentage 100)))))
(defparameter *account* (make-instance
'bank-account
:customer-name "Ed Monney"
:balance 1000000000
:opening-bonus-percentage 5))
I'm trying to access the :documentation slot-value of any said slot but have not been able to find info in the book i'm reading nor google....
My attempts include
(documentation *account* 'balance)
got this error
WARNING:
unsupported DOCUMENTATION: type BALANCE for object of type BANK-ACCOUNT
I tried
(slot-value bank-account ('balance :documentation))
I got
The variable BANK-ACCOUNT is unbound.
[Condition of type UNBOUND-VARIABLE]
I tried every other variation I could think of slot-value 'balance :documentation 'documentation bank-account and *account* I can think of but just got a lot of different errors any help on learning how to access the :documentation of a defclass slot
is much appreciated
Edit:
#Rainer Joswig that seems to work only right after i entered the defclass at the repl...I was hoping for a way that , if I had a set defclass in a library or something I could just run a command and access the doc. . They way you posted though if i run something else at the repl after the defclass ....I get an error when i run those 4 lines of code....I tried something like (documentation (slot-value account 'balance) t) after i've run my initialize-instance and my defparam account as in my post but get error....could you suggest a way to make the documentation easier to access.
This is not defined in the Common Lisp standard. Unfortunately this is also not beginners territory.
Implementations may provide a way to access the documentation string of a slot.
LispWorks example:
CL-USER 23 > (defclass foo ()
((bar :documentation "this is slot bar in class foo")))
#<STANDARD-CLASS FOO 40200032C3>
CL-USER 24 > (class-slots *)
(#<STANDARD-EFFECTIVE-SLOT-DEFINITION BAR 4020004803>)
CL-USER 25 > (first *)
#<STANDARD-EFFECTIVE-SLOT-DEFINITION BAR 4020004803>
CL-USER 26 > (documentation * 'slot-definition)
"this is slot bar in class foo"
It also works in Clozure CL.
For SBCL it works slightly different.
* (defclass foo ()
((bar :documentation "this is slot bar in class foo")))
#<STANDARD-CLASS FOO>
* (sb-mop:finalize-inheritance *)
NIL
* (sb-mop::class-slots **)
(#<SB-MOP:STANDARD-EFFECTIVE-SLOT-DEFINITION BAR>)
* (first *)
#<SB-MOP:STANDARD-EFFECTIVE-SLOT-DEFINITION BAR>
* (documentation * t)
"this is slot bar in class foo"

How can I quickly create many similar slots for a class?

I have the following classes, and more like them:
(defclass weapon ()
((base-slice-damage
:documentation "Base slice damage dealt by weapon"
:reader base-slice-damage
:initform 0
:initarg :base-slice-damage)
(base-blunt-damage
:reader base-blunt-damage
:initform 0
:initarg :base-blunt-damage)
(base-pierce-damage
:reader base-pierce-damage
:initform 0
:initarg :base-pierce-damage)))
(defclass dagger (weapon)
((base-slice-damage
:initform 3)
(base-pierce-damage
:initform 6)))
(defclass attack ()
((slice-damage-dealt
:initarg :slice-damage-dealt
:reader slice-damage-dealt)
(blunt-damage-dealt
:initarg :blunt-damage-dealt
:reader blunt-damage-dealt)
(pierce-damage-dealth
:initarg :pierce-damage-dealt
:reader pierce-damage-dealt)))
As you can see, there is a lot of repetition. For two of the classes, my slots all have the same option and vary only by whether they're slice, blunt, or pierce.
I've thought about using a macro to define attribute classes and then just mixing those in. This is what I have so far:
(defmacro defattrclass (attr-name &body class-options)
`(defclass ,(symb attr-name '-attr) ()
((,attr-name
,#class-options))))
But this really doesn't go far enough.
Edit:
I've come up with this, though I'm not completely happy with it:
(defmacro defattrclass (attr-name &body class-options)
`(defclass ,(symb attr-name '-attr) ()
((,attr-name
,#class-options))))
(defmacro defattrclasses (attr-names &body class-options)
`(progn
,#(loop for attr-name in attr-names collect
`(defattrclass ,attr-name ,#class-options))))
Not quite 100% coverage of the features you want, but I've been using this macro for a while:
(defmacro defclass-default (class-name superclasses slots &rest class-options)
"Shorthand defclass syntax; structure similar to defclass
Pass three values: slot-name, :initform, and :documentation
Everything else gets filled in to standard defaults"
`(defclass
,class-name
,superclasses
,(mapcar (lambda (x) `( ,(first x)
:accessor ,(first x)
:initarg ,(intern (symbol-name (first x)) "KEYWORD")
:initform ,(second x)
:documentation ,(third x)))
slots)
,#class-options))
To use:
CL-USER>
(defclass-default weapon ()
((base-slice-damage 0 "Base slice damage dealt by a weapon")
(base-blunt-damage 0 "Needs a doc")
(base-pierce-damage 0 "Needs a doc")))
#<STANDARD-CLASS WEAPON>
CL-USER>
IMHO it looks like you need a class damage with three fields (slice, blunt, pierce). You can use that class inside weapon, attack etc.

specifying a slot value as a key when removing duplicates

The following code does what I want:
1 (defclass some-class ()
2 ((some-slot
3 :initarg :somearg
4 :initform (error ":somearg not specified"))))
5 (defparameter *alpha* (make-instance 'some-class :somearg 3))
6 (defparameter *beta* (make-instance 'some-class :somearg 5))
7 (defparameter *gamma* (make-instance 'some-class :somearg 3))
8 (princ (slot-value *beta* 'some-slot)) (terpri)
9 (defparameter *delta* (list *alpha* *beta* *gamma*))
10 (princ *delta*) (terpri)
11 (princ (remove-duplicates *delta*
12 :test #'equal
13 :key (lambda (x) (slot-value x 'some-slot))))
14 (terpri)
5
(#<SOME-CLASS #x21C1D71E> #<SOME-CLASS #x21C1DAFE> #<SOME-CLASS #x21C1DC3E>)
(#<SOME-CLASS #x21C1DAFE> #<SOME-CLASS #x21C1DC3E>)
But is there a way to do this without having to write the function on line 13? Is there a shorthand way to specify as the key a slot value in the class instance?
The following blows up with a syntax error, of course, but it gives the general idea of what I'm seeking.
1 (princ (remove-duplicates *delta*
2 :test #'equal
3 :key '(slot-value 'some-slot)))
4 (terpri)
*** - FUNCALL: (SLOT-VALUE 'SOME-SLOT) is not a function name; try using a
symbol instead
You could try a :reader or :accessor.
Doing
(defclass some-class ()
((some-slot
:initarg :somearg :reader some-slot
:initform (error ":somearg not specified"))))
should let you re-write lines 11 through 13 as
(princ (remove-duplicates *delta* :test #'equal :key #'some-slot))
That is, (some-slot x) is equivalent to (slot-value x 'some-slot) if the slot in question has a reader/accessor.
After-Sleep Edit:
You also don't need to bother setting :initform to error; a slot will do that by default if you don't specify a default value and someone tries to read it. If you don't want the error, you do something like :initform nil. Check out this excellent CLOS tutorial as well as chapters 16 and 17 of Practical Common Lisp for more information about objects in Common Lisp.
Also, in the future if you have working code that you'd like style advice on, do check out codereview.stackexchange. There's a small, but active population of Lisp reviewers.
You could define a reader function for the slot in the defclass and provide that as key function to remove-duplicates. For example, add this line to the slot definition:
:reader some-slot
and then use this in the call of remove-duplicates:
:key #'some-slot