What's the equivalent of constructors in CLOS?
Asked Answered
S

2

8

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)?

Saransarangi answered 18/4, 2013 at 17:47 Comment(0)
R
15

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.

Realist answered 18/4, 2013 at 20:2 Comment(2)
Doesn't CALL-NEXT-METHOD just call the next method with the same arguments. How would I implement my example, i.e., converting the integer argument to a string and passing it to the super class method?Saransarangi
@Tobias Brandt: see my exampleRealist
S
3

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.

Snide answered 14/2, 2016 at 2:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.