πŸ““ literature/old-mccarthy-had-a-form.md by @ryan β˜†

Old McCarthy Had a Form

source : Old McCarthy Had a Form - Atomized

Notes

[[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

This 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.