Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the equivalent of constructors in CLOS?

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

like image 701
Tobias Brandt Avatar asked Apr 18 '13 17:04

Tobias Brandt


People also ask

How do we define a constructor?

A constructor is a special method of a class or structure in object-oriented programming that initializes a newly created object of that type. Whenever an object is created, the constructor is called automatically.

What is a class constructor?

A class constructor is a special member function of a class that is executed whenever we create new objects of that class. A constructor will have exact same name as the class and it does not have any return type at all, not even void.

Are constructors methods?

A Constructor is a block of code that initializes a newly created object. A Method is a collection of statements which returns a value upon its execution. A Constructor can be used to initialize an object. A Method consists of Java code to be executed.

Which of the following Cannot be used as constructors?

Abstract class cannot have a constructor. Explanation: No instance can be created of abstract class.


2 Answers

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.

like image 153
Rainer Joswig Avatar answered Sep 21 '22 17:09

Rainer Joswig


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.

like image 21
Reb.Cabin Avatar answered Sep 24 '22 17:09

Reb.Cabin