📝Common Lisp: classes

  • Built-in classes cannot be subclass’ed

(defclass name (direct-superclass-name*)

(defclass bank-account ()

;; `make-instance' is used to create an instance of a class.
(make-instance 'bank-account)

;; `slot-value' function takes an object and a name of the slot and
;; returns the value of that slot. It is also `setf'able.

;; If slot is unitialized, it is an error to access it.
(slot-value (make-instance 'bank-account) 'balance)
;; => The slot COMMON-LISP-USER::BALANCE is unbound in the object
;; #<BANK-ACCOUNT {10039F1403}>.
;;   [Condition of type UNBOUND-SLOT]

;; :initarg specifies an initializer keyword for `make-instance', and
;; :initform specifies the default value.
(defclass bank-account ()
    :initarg :customer-name)
    :initarg :balance
    :initform 0)))

(slot-value (make-instance 'bank-account) 'balance) ; => 0

;; :initform is only evaluated when :initarg is not passed
(defvar *account-numbers* 0)
(defclass bank-account ()
    :initarg :customer-name
    :initform (error "Must supply a customer name."))
    :initarg :balance
    :initform 0)
    :initform (incf *account-numbers*))))

;; :initform cannot access other slots of the object. For that, you
;; need to define `initialize-instance'.

;; Generic function `print-object' determines how an object is
;; printed.

;; Generic function `initialize-instance' (called by `make-instance')
;; can be overridden to control initialization.

;; public “reader”
(defgeneric balance (account))
(defmethod balance ((account bank-account))
  (slot-value account 'balance))
;; make slot `setf'able
(defun (setf customer-name) (name account)
  (setf (slot-value account 'customer-name) name))
;; then you can use it as
(setf (customer-name my-account) "Sally Sue")
;; or make it generic as well
(defgeneric (setf customer-name) (value account))
(defmethod (seft customer-name) (value (account bank-account))
  (setf (slot-value account 'customer-name) value))

;; `defclass' has :reader and :writer options to automatically define
;; readers and writers.
(defclass bank-account ()
    :initarg :customer-name
    :reader customer-name
    :writer (setf customer-name))
    :initarg :balance
    :initform 0
    :reader balance)))
;; :accessor can be used as a shortcut for both reader and setf writer:
(defclass bank-account ()
    :initarg :customer-name
    :accessor customer-name)
    :initarg :balance
    :initform 0
    :reader balance)))

;; `with-slots' and `with-accessors' macros help to shorten the code

;; `with-slots' accesses slot value directly:
(defmethod assess-low-balance-penalty ((account bank-account))
  (with-slots (balance) account
    (when (< balance *minimum-balance*)
      (decf balance (* balance 0.01)))))
;; two-item list form
(defmethod assess-low-balance-penalty ((account bank-account))
  (with-slots ((bal balance)) account
    (when (< bal *minimum-balance*)
      (decf bal (* bal 0.01)))))

;; `with-accessors' is similar but calls accessors. Generally, you
;; should prefer `with-accessors' unless you have a specific reason
;; not to.
(defmethod merge-accounts ((account1 bank-account) (account2 bank-account))
  (with-accessors ((balance1 balance)) account1
    (with-accessors ((balance2 balance)) account2
      (incf balance1 balance2)
      (setf balance2 0))))

;; :allocation options controls whether slot is instance- or
;; class-allocated. (Default is instance.)
;; :initform is evaluated at class definition time
;; If :initarg is passed to `make-instance', it will set the value,
;; affecting all instances.

;; Even though class-allocated slots are stored in class, they can
;; only be accessed by `slot-value' via an instance of a class.
;; That's why class-allocated slots are not equivalent to static or
;; class fields in other languages.
;; If Common Lisp implementation support the Meta Object Protocol
;; (MOP), it provides a `class-prototype' function that returns an
;; instance of a class that can be used to access class slots.