Defmethod on Arbitrary Type Specifiers?

common-lisp, lisp, types

Solution

Common Lisp defines two hierarchies which are related but not identical: the type hierarchy and the class hierarchy. Every class is a type, but the converse is not true — there are types that are not classes. For example, `integer` and `string` are classes, and therefore also types. On the other hand, `(integer 1 *)` and `(satisfies evenp)` are types, but not classes.

> (type-of "toto")
(SIMPLE-BASE-STRING 4)
> (class-of "toto")
#<BUILT-IN-CLASS STRING>

Parameter specialisers — the things that you put after parameters in `defmethod` — can only be class names (or of the form `(eql value)`). Since `(integer 1 *)` is not a class name, your code is not allowed by Common Lisp. There is an excellent reason for that: the compiler is always able to determine the class hierarchy, while the type language is way too powerful for that:

(defun satisfies-the-collatz-conjecture (n)
  (cond
    ((<= n 1) t)
    ((evenp n) (satisfies-the-collatz-conjecture (/ n 2)))
    (t (satisfies-the-collatz-conjecture (+ 1 (* n 3))))))

(subtypep 'integer '(satisfies satisfies-the-collatz-conjecture))
NIL ;
NIL

If you really need your code to be modular, you will need to first classify your values into something that can be made into a specialiser, and then dispatch on that:

(defmethod fn-generic (x (sign (eql 'positive)))
  "Positive integer")

(defmethod fn-generic (x (sign (eql 'negative)))
  "Negative integer")

(defun classify (x)
  (cond
    ((< x 0) 'negative)
    ((= x 0) 'null)
    ((> x 0) 'positive)))

(defun fn (x)
  (fn-generic x (classify x)))

Problem

What I'm trying to do is this: ``` (defgeneric fn (x)) (defmethod fn ((x (integer 1 *))) "Positive integer") (defmethod fn ((x (integer * -1))) "Negative integer") ``` I want a generic function that works with arbitrary type specifiers, including the list-based ones such as `(and x y)`, `(or x y)`, `(satisfies p)`, etc. Now when I attempt to run the above code, I get an "Invalid Specializer" error. A little bit of research reveals that `defgeneric` is designed to work with CLOS, not with arbitrary type specifiers. Is there a defgeneric-like system in Common Lisp that would get me the behavior I want for arbitrary type specifiers, not just classes?

Original source