UP | HOME

Defmethod-bind — CLOS Methods with Automatic Slot Binding

Table of Contents

Definition

defmethod-bind is a macro defined in tolk/utils that combines defmethod with automatic slot binding via metabang-bind. It eliminates the boilerplate of manually opening a bind call at the top of every method that destructures its specialised argument.

It uses closer-mop to inspect slot definitions at macro-expansion time, choosing between (:accessors ...) (for slots with reader methods, which is more efficient) and (:slots ...) (for slots without).

Motivation

A CLOS method that needs to access slots of its specialised parameter typically looks like this without defmethod-bind:

(defmethod interpret ((inst arith-binary-op))
  (bind (((:slots op l r) inst))
    (funcall op (interpret l) (interpret r))))

With defmethod-bind this becomes:

(defmethod-bind interpret ((arith-binary-op op l r))
    ()
  (funcall op (interpret l) (interpret r)))

The slots op, l, r are bound automatically. The generated code is identical to the manual version. The () in the second argument position is the list of additional bind clauses — empty here, but used when a method needs to bind further values beyond the slots. This is explained in the Additional Bind Clauses section below.

Signature

(defmethod-bind function-name
    ((class-name &rest slot-names) &rest ordinary-lambda-list)
    (&rest additional-bind-clauses)
  &body body)
class-name
The class that this method specialises on. The generated defmethod uses an anonymous gensym as the instance variable name, so the instance is never directly named in user code.
slot-names
The slots of class-name to bind. Order follows the order given, not necessarily the order of defclass slot definitions.
ordinary-lambda-list
Additional non-specialised parameters (e.g. env, store in later interpreters).
additional-bind-clauses
Extra bindings in bind style, placed after the slot bindings. Passed as the second argument (the (&rest bindings) list).
body
The method body. An optional docstring and declare forms may appear at the start.

How It Works: The Macro Expansion

Given:

(defmethod-bind interpret ((num n))
    ()
  n)

The macro:

  1. Generates a fresh gensym for the instance (say #:inst42).
  2. Calls class-readers (a tolk/utils helper using closer-mop) to find which slots of num have reader methods and which do not.
  3. For slot n of num: there is no reader method, so it uses (:slots n).
  4. Emits:
(defmethod interpret ((#:inst42 num))
  (bind (((:slots n) #:inst42))
    n))

For a class with a reader method (e.g. a slot val with reader val-of):

(defmethod-bind process ((my-class val other))
    ()
  (+ val other))

The macro would emit (:accessors (val val-of)) for val and (:slots other) for other, giving:

(defmethod process ((#:inst42 my-class))
  (bind (((:accessors (val val-of)) #:inst42)
         ((:slots other)            #:inst42))
    (+ val other)))

The eval-when Requirement

defmethod-bind introspects class definitions using closer-mop at macro-expansion time — when the compiler first reads the defmethod-bind form. For this to succeed, the class must already be defined and finalised in the compile-time environment.

Standard defclass forms are only evaluated at load time (unless explicitly wrapped). Tolk therefore wraps all AST class definitions in:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defclass ast-node () ())

  (defclass num (ast-node)
    ((n :type number :initarg :n)))

  (defclass arith-binary-op (ast-node)
    ((op :type function :initarg :op)
     (l  :type ast-node :initarg :l)
     (r  :type ast-node :initarg :r)))

  (defclass plus (arith-binary-op) ((op :initform #'+)))

  (defclass mult (arith-binary-op) ((op :initform #'*))))

The three situations eval-when covers:

:compile-toplevel
Evaluates the form when the compiler processes the file. This makes the class available in the compile-time environment so that defmethod-bind macros later in the file can introspect it.
:load-toplevel
Evaluates the form when the compiled file is loaded. This makes the class available at runtime.
:execute
Evaluates the form when the form itself is evaluated interactively (e.g. in a REPL, or when the file is =load=ed without compiling).

Without :compile-toplevel, the class would not exist when the compiler expands defmethod-bind, causing an error such as "there is no class named NUM".

Docstrings and Declarations

An optional docstring and/or (declare ...) forms at the start of body are correctly hoisted outside the bind into the defmethod body:

(defmethod-bind interpret ((num n))
    ()
  "Interpret a numeric literal: return its value."
  (declare (ignorable n))  ; hypothetical
  n)

The docstring and declare appear in the generated defmethod before the bind call, as required by the Common Lisp standard.

Additional Bind Clauses

The second argument (the () in most tolk examples) accepts additional bind clauses that are appended after the slot bindings:

(defmethod-bind interpret ((let1 var varex body) env store)
    ((result-val (interpret varex env store)))
  (interpret body
             (extend var result-val env)
             store))

Here the slots var, varex, body are bound first; then result-val is bound using the additional clause.

Source

defmethod-bind is defined in sources/advprog/tolk/src/utils.lisp. The helper class-readers (also in utils.lisp) walks the class precedence list using closer-mop:class-precedence-list and closer-mop:class-direct-slots to collect all reader methods including inherited ones.

Sources

Related

  • Closer-mop — MOP introspection used by defmethod-bind
  • Metabang-bind — binding macro used in the expansion
  • Tolk — defmethod-bind is central to tolk's method style
  • Abstract Syntax Tree — the CLOS classes defmethod-bind operates on