Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defmethod on Arbitrary Type Specifiers?

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?

like image 802
Silvio Mayolo Avatar asked Nov 23 '14 03:11

Silvio Mayolo


3 Answers

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)))
like image 151
jch Avatar answered Nov 08 '22 22:11

jch


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:

  • manually declaring the precedence
  • some kind of priority value
  • runtime errors with the opportunity for the user to select one
  • a type language, which provides an order
  • giving up order all together

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.

like image 24
Rainer Joswig Avatar answered Nov 08 '22 21:11

Rainer Joswig


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

like image 1
Svante Avatar answered Nov 08 '22 20:11

Svante