0

I defined a generic function taking 2 arguments:

(defgeneric interact (a b))

The order of the arguments should not be important, so (interact x y) and (interact y x) should be the same, but I don't want to define two methods that do the same for every combination of different objects.

A Method-Combination of this type should help:

(defmethod interact :around (a b)
  (if (some-function a b)
    ;;some-function has to be true if (eq (class-of a) (class-of b))
    ;;else (some-function a b) is (not (some-function b a))
    ;;similar #'<=
    (call-next method)
    (interact b a))

But I would have to know #'some-function and be able to know the type of the arguments I have to define.

cl-porky11
  • 339
  • 1
  • 11

2 Answers2

1

Edit: both proposed approaches have a few limitations discussed in the comments below. Please read them before using this answer!

Can I suggest two options - a working but hacky option for when you only have two arguments, and a vaguely sketched out generic approach which I think should work but I haven't written:

Option 1:

(defparameter *in-interact-generic-call* nil)

(defgeneric interact (x y))

(defmethod interact ((x T) (y T))
  ; this can be called on pretty much anything
  (if *in-interact-generic-call*
    (cause-some-kind-of-error) ; Replace this with a more sensible error call
    (let ((*in-interact-generic-call* T))
      (interact y x))))

(defmethod interact ((x integer) (y string))
  ; example
  (print x )(prin1 y))

(interact 5 "hello") ; should print 5 "hello"
(interact "hello" 5) ; should print 5 "hello"
;(interact "hello" "hello") ; should cause an error

Essentially the idea is to define a generic function which always matches anything, use it to try to swap the arguments (to see if that matches anything better) and if it's already swapped the arguments then to raise some kind of error (I've not really done that right here).

Option 2

Define the generic function as something like interact-impl. Actually call the standard function (defined by defun) interact.

In interact, define a loop over all permutations of the order of your arguments. For each permutation try calling interact-impl (e.g. using (apply #'interact-impl current-permutation).)

At least in sbcl, no matching arguments gives me a simple-error. You probably would want to do a more detailed check that it's actually the right error. Thus the code in interact looks something like

; completely untested!
(do (all-permutations all-permutations (cdr all-permutations))
   (...) ; some code to detect when all permutations are exhausted and raise an error
   (let (current-permutation (first all-permutations))
      (handler-case
         (return (apply #'interact-impl current-permutation))
         (simple-error () nil)) ; ignore and try the next option
   )
 )
DavidW
  • 29,336
  • 6
  • 55
  • 86
  • Option 1 is not amenable to recursion. Perhaps it could be if there was an `:around` method that explicitly binds the variable to `nil` in the first place, but you'd have to restrain from defining further `:around` specializations. – acelent Mar 30 '15 at 08:57
  • Option 2 could specialize `no-applicable-method`, because as it stands you don't know if the `simple-error` is due to a missing specialization or from the method itself. You could take the same approach, but you'd use a more specialized condition that you'd signal (with `error`) in `no-applicable-method` and handle in the loop. – acelent Mar 30 '15 at 09:01
  • Good catch about Option 1 and recursion - I missed that, but as you imply I don't think it's easily solved. Some variation of option 2 using [no-applicable-method](http://www.lispworks.com/documentation/HyperSpec/Body/f_no_app.htm) would probably be a better solution. Someone who isn't me will have to write it (and if they do, OP should accept their answer instead!). – DavidW Mar 30 '15 at 13:15
  • If I have 2 classes a and b, (subtypep 'b 'a), and my method specializers for interact are 1: (a a), 2: (b b) and 3: (a b) then if I call (interact b a) 1 will be called instead of 3, what it should – cl-porky11 Mar 31 '15 at 10:02
  • Yes - fair point. As you say in your comment on the question, defining a macro is almost certainly the right way to do it (you should probably answer the question yourself with that suggestion). My approach has too many edge cases that it fails on. – DavidW Mar 31 '15 at 15:54
0

So what you are looking for is an arbitrary linear order on the class objects. How about string order on class names?

(defun class-less-p (a b)
  "Lexicographic order on printable representation of class names."
  (let* ((class-a (class-of a))
         (name-a (symbol-name class-a))
         (pack-a (package-name (symbol-package name-a)))
         (class-b (class-of b))
         (name-b (symbol-name class-b))
         (pack-b (package-name (symbol-package name-b))))
    (or (string< pack-a pack-b)
        (and (string= pack-a pack-b)
             (string<= name-a name-b)))))
sds
  • 58,617
  • 29
  • 161
  • 278
  • I don't think, this will work, if the methods are defined for superclasses, then the order may be different – cl-porky11 Mar 29 '15 at 15:08