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?
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)))
There is nothing like CLOS which would provide such a feature.
It actually does not fit well into CLOS either. Think about the following, we have the following call of a generic function:
(generic-function-foo 2)
Now we have methods defined for the following types:
(integer 0 9)
(integer 1 9)
(integer 0 99)
(integer 1 99)
(integer -1000 1000)
(or (satisfies evenp) (integer 0 30))
(satisfies evenp)
(satisfies divisible-by-two)
(satisfies all-numbers-which-are-in-my-list-of-numbers)
Which of the methods which all match for 2 should run? If I call CALL-NEXT-METHOD
, which one would be the next?
Now we can say order them by appearance in the source. But in Common Lisp you can add, remove or redefine methods at runtime. The behavior would be more or less random.
We would need some other conflict resolution scheme. For example:
There have been attempts to provide more expressive dispatch to CLOS. I'm not aware of adding types to CLOS, though. See predicate dispatch and filtered dispatch.
Other than that I would look for a rule-based system, but that usually is very different from CLOS, the Common Lisp Object System, unless it is in some way integrated.
What you actually seem to be looking for is pattern matching, like in ML or Erlang. That is a rather different concept from dispatch, although they have similar purposes.
One popular pattern matching library for Common Lisp is optima (available from Quicklisp).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With