source : Old McCarthy Had a Form - Atomized
[[EIEIO]] is an implementation of [[CLOS]] for [[Emacs]].
[[Classes]] in EIEIO/CLOS only encapsulate data, they do not have methods.
(defclass form/emms-player () ; parent class/es
((playing :type boolean :initform nil)) ; slot (field) defintions
:abstract t) ; EIEIO extension
[[EIEIO]] has [[abstract classes]], like [[Java]], and an EIEIO abstract class can be extended but not instantiated by itself.
The article provides an example extension as such:
(defclass form/emms-player-mpv (form/emms-player) ; Child of form/emms-player
()) ; no additional slots
Instantiating objects involves calling their constructor.
(form/emms-player-mpv) ; constructor
(let ((class 'form/emms-player-mpv))
(make-instance class)) ; instantiation based on a symbol value
(list #s(form/emms-player-mpv nil) ; use of the #s macro, which evaluates to an equivalent object
(read "#s(form/emms-player-mpv nil)"))
To access slots within a class, you can use the oref or oset macros.
(let ((p (form/emms-player-mpv)))
(progn (oset p playing t)
(oref p playing)))
You can also reference slots by symbol:
(let ((slot 'playing))
(slot-value (form/emms-player-mpv) slot)) ; gets the ~playing~ slot
There is also the with-slots macro:
(with-slots (playing) (form/emms-player-mpv)
(list playing (progn (setf playing t) playing)))
This is a bit like [[destructuring]].
A class can provide an :initform keyword, which is like the default constructor. It says how the class is initialized.
(defclass form/now ()
((time :initform (current-time-string))))
(list
(oref (form/now) time)
(progn (sleep-for 1)
(oref (form/now) time)))
You may also specify an :initarg form for a field. This allows a constructor to specify slot values on initialization, as such:
(defclass foo ()
((val :initarg :val)))
(let ((obj (foo :val 1)))
(oref obj val))
The rough equivalent in Java might be:
public class Foo {
private int val;
public Foo(val) {
this.val = val;
}
}
// elsewhere
Foo f = new Foo(1);
CLOS specifies something called [[generic functions]]. These are similar to [[interfaces]], in that they only specify a name and an argument list. In CLOS, [[methods]] are simply implementations of generic functions.
CLOS (and EIEIO) allow for method implementation. Given generic functions, as such:
(cl-defgeneric form/emms-playablep (player track))
(cl-defgeneric form/emms-start (player track))
(cl-defgeneric form/emms-stop (player t))
You could provide implementations for these functions as follows:
;; Method applies when PLAYER arg is an instance of form/emms-player-mpv (or a subclass)
(cl-defmethod form/emms-playablep ((player form/emms-player-mpv) track)
(not (eql :unplayable track))) ; MPV can play almost anything!
(cl-defmethod form/emms-start ((player form/emms-player-mpv) track)
"Started")
(cl-defmethod form/emms-stop ((player form/emms-player-mpv))
"Stopped")
CLOS uses qualifiers to specify four different kinds of methods. They are
:before, evaluated before the primary method:after, evaluated after the primary method:around, evaluated around all other method types. This value can actually modify the return value of a functionThis is similar to [[Emacs Lisp]]βs [[function advice]].
EIEIO also allows for multiple [[inheritence]], for example:
(defclass form/logger ()
((messages :initform nil)))
(cl-defmethod form/log ((logger form/logger) format-string &rest args)
(with-slots (messages) logger
(push (apply #'format format-string args) messages)))
(cl-defmethod form/logs ((logger form/logger))
(with-slots (messages) logger messages))
(cl-defmethod form/latest-log ((logger form/logger))
(car (form/logs logger)))
(let ((l (form/logger)))
(form/log l "Hello, %s!" "world")
(form/latest-log l))
EIEIO also allows for defining structs for when you only care about keeping track of organized data. This is done with cl-defstruct.