📝Common Lisp: classes
Built-in classes cannot be subclass’ed
(defclass name (direct-superclass-name*)
(slot-specifier*))
(defclass bank-account ()
(customer-name
balance))
;; `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 ()
((customer-name
:initarg :customer-name)
(balance
: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 ()
((customer-name
:initarg :customer-name
:initform (error "Must supply a customer name."))
(balance
:initarg :balance
:initform 0)
(account-number
: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 ()
((customer-name
:initarg :customer-name
:reader customer-name
:writer (setf customer-name))
(balance
:initarg :balance
:initform 0
:reader balance)))
;; :accessor can be used as a shortcut for both reader and setf writer:
(defclass bank-account ()
((customer-name
:initarg :customer-name
:accessor customer-name)
(balance
: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.