3

There is a generic method, say incx. There are two versions of incx. One specialized on type a, and one specialized on type b. Type b is a subclass of a. You are given an object of type b, the derived type - but you want to call the method that is specialized on type a. You could do this easily if there wasn't already a method of the same name specialized on type b, but alas, there is such a method.

So how do you call the method specialized on type a in such a situation?

(defclass a () ((x :accessor x :initform 0)))
(defclass b (a) ((y :accessor y :initform 0)))

(defgeneric inc (i))

(defmethod inc ((i a)) (incf (x i)))
(defmethod inc ((i b)) (incf (y i)))

(defvar r (make-instance 'b))

As promised by CLOS, this calls the most specialized method:

* (inc r) 
* (describe r)
    ..
  Slots with :INSTANCE allocation:
    X  = 0
    Y  = 1

But this in this particular case, (not in general) what I want is to access the less specialized version. Say something like:

(inc (r a)) ; crashes and burns of course, no function r or variable a
(inc a::r)  ; of course there is no such scoping operator in CL

I see the call-next-method function can be used from within a specialized method to get the next less specialized method, but that isn't what is wanted here.

In the code this was cut out of, I do need something similar to call-next-method, but for calling a complementary method. Rather than calling a method of the same name in the next less specialized class, we need to call its complementary method, which has a different name. The complementary method is also specialized, but calling this specialized version doesn't work - for much the same reasons that call-next-method was probably included for. It isn't always the case that the required method specialized on the super class has the same name.

(call-next-method my-complement)  ; doesn't work, thinks my-complement is an arg

Here is another example.

There is a base class describing electron properties and a derived class describing the properties of a "strange-electron". Methods specialized on the strange electron desire to call methods specialized on the electron. Why? because these methods do the normal electron part of the work for the program. The non-electron part of the strange electron is almost trivial, or rather it would be if it didn't duplicate the electron code:

(defgeneric apply-velocity (particle velocity))
(defgeneric flip-spin (particle))

;;;; SIMPLE ELECTRONS

(defclass electron ()
  ((mass
      :initform 9.11e-31
      :accessor mass)
   (spin
      :initform -1
      :accessor spin)))

(defmacro sq (x) `(* ,x ,x))

(defmethod apply-velocity ((particle electron) v)
  ;; stands in for a long formula/program we don't want to type again:
  (setf (mass particle) 
        (* (mass particle) (sqrt (- 1 (sq (/ v 3e8)))))))

(defmethod flip-spin ((particle electron))
  (setf (spin particle) (- (spin particle))))

;;;; STRANGE ELECTRONS

(defclass strange-electron (electron)
  ((hidden-state
      :initform 1
      :accessor hidden-state)))

(defmethod flip-spin ((particle strange-electron))
  (cond
    ((= (hidden-state particle) 1)
     (call-next-method)

     ;; CALL ELECTRON'S APPLY-VELOCITY HERE to update
     ;; the electron. But how???
     )
    (t nil)))

;; changing the velocity of strange electrons has linear affect!
;; it also flips the spin without reguard to the hidden state!
(defmethod apply-velocity ((particle strange-electron) v)
  (setf (mass particle) (* (/ 8 10) (mass particle)))

  ;; CALL ELECTRON'S SPIN FLIP HERE - must be good performance,
  ;; as this occurs in critical loop code, i.e compiler needs to remove
  ;; fluff, not search inheritance lists at run time
  )

It all reduces to a simple question:

How to call the less specialized method if a more specialized one has been defined?

coredump
  • 37,664
  • 5
  • 43
  • 77
  • Is there a bigger problem you are trying to solve with this approach? – coredump Feb 03 '16 at 08:58
  • I don't have much time right now, but: (1) Use (expt x 2) for squaring (2) you could define auxiliary inlined functions to share common code. I will look at this later. – coredump Feb 03 '16 at 12:06
  • @coredump, yes the bigger problem is that of the design pattern of calling method functions to operate on the portions of inherited data that they were written for. When A is inherited into B, and I need to manipulate the the A data, I want to call the functions that were written for operating on A, but CLOS isn't letting me due to aliasing against another method of the same name defined to operate on the derived class B. This occurs within the definition of B. –  Feb 04 '16 at 06:34
  • You should put earmuffs (`*`) around all special variables (e.g. defined by `defvar` and `defparameter`). Also, your `sqr` macro is expanding into an expression where the given `x` is evaluated twice, which is bad if it ever has side-effects, or at least inefficient contrary to what its purpose might have been. – acelent Feb 10 '16 at 11:21
  • 8
    This question is being [discussed on meta](http://meta.stackoverflow.com/q/316885/211627). – JDB Feb 15 '16 at 16:57

5 Answers5

16

Your question contains two questions:

  1. How to call a specific effective method?
  2. How to avoid copy-pasting in the case of the electron simulation?

This answer is a merge of my other answer and is inspired partially by Dirk's good answer for the concrete example. I'll cover first the question as asked (calling a specific method) and explain why you should instead try another approach, notably for your example.

Calling an effective method

Yes, you can call the function associated with a method instead of the generic function. For a portable approach, first load closer-mop:

(ql:quickload :closer-mop)

Define some classes and a simple generic function:

(defclass a () ())
(defclass b (a) ())
(defclass c (b) ())
(defgeneric foo (x)
  (:method ((x a)) 0)
  (:method ((x b)) (+ (call-next-method) 1))
  (:method ((x c)) (* (call-next-method) 2)))

We have a class hierarchy (a < b < c) and a generic function dispatching on the first argument only.

Now, we compute the applicable methods for class b and use the resulting list to define a function which calls the effective method of foo specialized on b.

(destructuring-bind (method . next)
    (closer-mop:compute-applicable-methods-using-classes
     #'foo
     (list (find-class 'b)))
  (let ((fn (closer-mop:method-function method)))
    (defun %foo-as-b (&rest args)
      (funcall fn args next))))

And here you have the two different behaviors:

(let ((object (make-instance 'c)))
  (list
    (%foo-as-b object)
    (foo object))

=> (1 2)

This is however not recommended. CLOS provides a way to combine effective methods and you should try to use it as intended instead of hijacking it. Indeed, suppose that I evaluate the following:

(defmethod foo :before ((i a)) (print "Before A"))

The foo generic function, called on an instance c of c, will print the string. But when using %foo-as-b on c, no string is printed, even though we are calling the function as-if c was instead an instance of b and the method is specialized on a.

This is of course because compute-applicable-methods-using-classes depends on the set of methods known when called. In that case function %foo-as-b is still using an outdated list of methods. The effect is amplified if you define several such functions or specialize over multiple classes. If you want to always keep %foo-as-b synchronized with your environment, you somehow need to recompute the list at each invocation of this function (instead of having a let-over-lambda, you'd recompute the value inside the lambda). Another possibility is to introduce hooks into CLOS to recompute the function when required, but this is madness.

Do not overuse inheritance for sharing code

Consider the Liskov substitution principle. The overuse of inheritance for sharing code (i.e. implementation details) instead of polymorphism is what springs advices like "Favor Composition over Inheritance". See Where does this concept of “favor composition over inheritance” come from? and Code Smell: Inheritance Abuse for more details on this.

Use functions

In C++, where base::method() can be found, you are just calling a different function with a similar name: there is no dynamic dispatch when you tell your compiler which method you want to call, so this is in fact as-if you called a regular function.

With your requirements, I would write what follows. It is based on Dirk's version and makes use of auxiliary inlined local functions, which are perfectly adequate when you want to avoid repetition:

(defclass electron ()
  ((mass :initform 9.11e-31 :accessor mass)
   (spin :initform -1 :accessor spin)))

(defclass strange-electron (electron)
  ((hidden-state :initform 1 :accessor hidden-state)))

(let ((light-speed 3e8)
      (mysterious-velocity 0d0))
  (flet ((%flip (p)
           (setf (spin p) (- (spin p))))
         (%velocity (p v)
           (setf (mass p)
                 (* (mass p)
                    (sqrt
                     (- 1 (expt (/ v light-speed) 2)))))))
    (declare (inline %flip %velocity))
    
    (defgeneric flip-spin (particle)
      (:method ((p electron))
        (%flip p))
      (:method ((p strange-electron))
        (when (= (hidden-state p) 1)
          (call-next-method)
          (%velocity p mysterious-velocity))))

    (defgeneric apply-velocity (particle velocity)
      (:method ((p electron) v)
        (%velocity p v))
      (:method ((p strange-electron) v)
        (setf (mass p)
              (* (/ 8 10) (mass p)))
        (%flip p)))))

The problem is solved and is hopefully quite readable: there is no need to hack something else in CLOS. Auxiliary functions that are shared by different methods are easily identified and if you need to recompile any of them, you'll have to recompile the whole form, which ensures the existing coupling between classes is taken into account in all methods.

Use composition

What happens if we apply the above recommendation and use composition instead? Let's change your strange-electron so that it contains a simple-electron. That may sound weird with respect to actual electrons, but it makes sense if we consider the objects used to simulate; also, note that in your question you actually wrote about an "electron part" and the "non-electron part of the strange electron". First, the main classes:

;; Common base class
(defclass electron () ())

;; Actual data for mass and spin
(defclass simple-electron (electron)
  ((mass :initform 9.11e-31 :accessor mass)
   (spin :initform -1 :accessor spin)))

;; A strange electron with a hidden state
(defclass strange-electron (electron)
  ((simple-electron :accessor simple-electron :initarg :electron)
   (hidden-state :initform 1 :accessor hidden-state)))

Notice how the strange-electron does not inherit anymore from simple-electron (we don't need to store a separate mass and spin) but contains an instance of simple-electron. Note also that we added a common electron base class, which is not strictly necessary in that case. I'll skip the part where we define generic functions and just describe methods. In order to get/set the mass and spin of those strange electrons, we have to delegate to the inner object:

(macrolet ((delegate (fn &rest args)
             `(defmethod ,fn (,@args (e strange-electron))
                (funcall #',fn ,@args (simple-electron e)))))
  (delegate mass)
  (delegate spin)
  (delegate (setf mass) new-value)
  (delegate (setf spin) new-value))

Before we continue, what does the above code do? If we expand the last form inside the macrolet, namely the one with (setf spin), we obtain a method which sets the slot of the inner particle:

(defmethod (setf spin) (new-value (e strange-electron))
  (funcall #'(setf spin) new-value (simple-electron e)))

That's great. Now, we can define flip-spin and apply-velocity quite simply. The basic behavior is tied to the simple-electron class:

(defmethod flip-spin ((e simple-electron))
  (setf (spin e) (- (spin e))))

(defmethod apply-velocity ((e simple-electron) velocity)
  (setf (mass e)
        (* (mass e)
           (sqrt
            (- 1 (expt (/ velocity +light-speed+) 2))))))

This is the same equation as in your original question, but specialized on simple-electron. For strange electrons, you rely on the internal object:

(defmethod flip-spin ((e strange-electron))
  (when (= (hidden-state e) 1)
    (flip-spin (simple-electron e))
    (apply-velocity (simple-electron e) 0d0)))

(defmethod apply-velocity ((e strange-electron) velocity)
  (setf (mass e) (* (/ 8 10) (mass e)))
  (flip-spin (simple-electron e)))

One of your objective is to have a CLOS interface and not a "static interface", and this is exactly the case here.

Conclusion

Calling a less specific method explicitely is a code smell. I don't exclude the possibility that it might be a sensible approach in some cases, but I'd suggest to consider alternative designs first.

Common code can be shared through regular functions, like it was always done (for a convenient definition of always). Alternatively, prefer composition.

Moritz Petersen
  • 59
  • 1
  • 1
  • 8
coredump
  • 37,664
  • 5
  • 43
  • 77
  • you left the inheritance off of class b, should be (defclass b (a)). Does that make a difference to your answer? It certainly makes a difference to the problem specification. Can you add that? –  Feb 03 '16 at 09:12
  • And yes, I need it, and so will many others, as it is a normal part of the object oriented programming paradigm to use superclass defined methods for manipulating the superclass parts of the derived class, from within derived class specialized methods. Will the mop code compile away, or is this going to be a huge performance hit over the current approach of cutting and pasting of superclass specialized method code into methods that work on the derived class. –  Feb 03 '16 at 09:15
  • @user244488 I was editing the code to add the inheritance to a while you commented ;-) The normal part of object oriented programming paradigm as defined in other languages like C++ may not apply here. I fail to see a use case where this is needed. – coredump Feb 03 '16 at 09:20
  • @user244488 As far as I know, there is no language that natively supports this kind of behavior. The only thing I can think of that is similar is intentionally leaving off the `virtual` keyword in C++, but that would prevent you from using run time polymorphism. – malisper Feb 03 '16 at 09:29
  • @coredump Use cases occur anywhere someone wants to manipulate data in an instance of a superclass, from within a method specialized on a derived class. I'm having to use an editor to cut out code from a less specialized method that works on superclass instance data, so as to have that same work done in a method specialized on a derived class instance, because I can't call the other one, only because it has a different name (otherwise call-next-method would work) I would love to know a better way than editor cut and paste inheritance. –  Feb 03 '16 at 09:42
  • @malisper gosh, we must not be communicating well, as calling methods from a base class in a child class is in every other object oriented implementation I've seen. I hope I'm not overlooking something obvious here. Of course, that is why I came to stack exchange here to ask about this. –  Feb 03 '16 at 09:44
  • @user244488 Can you give a complete example of the behavior you are looking for and what it would look like in another language? – malisper Feb 03 '16 at 09:51
  • @malisper, alright let me gen up another example, with inheritance. I'll add it to the bottom of the current post. The library I'm working on will hopefully be GNU lesser licensed released in a week or two. –  Feb 03 '16 at 09:56
  • From what you have posted previously, what you are looking for may just be `(other-method (make-instance 'b))`. – malisper Feb 03 '16 at 09:59
  • 1
    @user244488 It might be worthwhile to post the concrete problem you face with cut-and-paste inheritance as another question. The current one is already well defined and can have answers independently of the intended usage. I just had to ask if this was necessary because it surely looks like an XY-problem to me. – coredump Feb 03 '16 at 10:41
  • @malisper Alright I've added another example. It is about as concrete as it can be on stack exchange (rather than github). The design pattern comes out pretty clearly in this example. –  Feb 03 '16 at 10:59
  • @malisper -- yes (other-method (make-instance b)) would solve the problem. However, would this not create a superfluous instance? Remember, if there was not a method specialized on b aliasing against the method specialized on a, the call could be done directly without any conversions. It is because CLOS picks the most specialized method, and there happens to be another mores specialized in this case, that the issue comes up. I'm looking at the other posters suggestion of #compute-applicable-methods now, but Im afraid of the overhead if I use it. –  Feb 03 '16 at 11:30
  • @user244488 There is no superfluous instance. Any method that works on a class `a` will automatically work on any subclass `b` without any conversion. If you don't believe me, put `(print (type-of object))` inside the definition of `other-method`. This is the same as it is for any other object system. The only difference is that you using `(other-method object)` instead of `object.other_method()`. – malisper Feb 03 '16 at 18:41
  • @malisper need some clarification, 'r in the example is already (make-instance 'b). So to get (inc a::r), are you suggesting (inc (make-instance 'a :x (x b))) ? I'm not quite following. Yes, of course #'inc specialized on class 'a will work on an instance of class 'b, but the problem here is that there is another #'inc, with different functionality, that is specialized on class b, so CLOS calls the most specific function, the one specialized on 'b, rather than one specialized on 'a. Cool if you could add an answer, showing the solution. –  Feb 04 '16 at 05:43
  • @coredump In your solution code "(funcall fn args next)" doesn't that args list need to be broken open? –  Feb 06 '16 at 09:00
  • @user244488 The `fn` function accepts two arguments, both of them are lists: the first is the argument to the generic function and the other is a list of methods – coredump Feb 06 '16 at 10:07
  • On the subject of good design, of course your points here are generally valid, but they are not universal. Obviously, at some point an implementation must have knowledge of the class, or code can't be written. I've added an example which I hope clarifies this hole in the generalization. In appendix 0, you can see that a method specialized on a given class, can not use other methods also specialized on the class. It is common to build code up in layers, this is the whole idea of subroutines. Dirk's approach can be a general solution by keeping the static part local using such a wrapper. –  Feb 10 '16 at 03:33
  • It also seems that you are arguing that if a physical simulation can not be implemented in CLOS then it is the real world that is at fault. If a strange electron "knows to much" about electrons, that is just the way it is, and it is CLOS that has failed to be useful in this particular situation. –  Feb 10 '16 at 03:47
  • @user244488 I am not talking about the real world. I am saying that applying C++ recipes when coding in Lisp is not necessarily a good idea because the situation is different. With CLOS, you dispatch according to the actual object's classes, not on variable's types (which let you look at an object as if it had a different type). That means that a class can always subclass another in order to provide a more specialized method for a generic function *foo*: the caller does not decide which specialized method shall be called. If you don't need dynamic dispatch, use regular functions. – coredump Feb 10 '16 at 12:49
16

I'd prefer the explicit approach here:

(defun actually-inc-a (value) (incf (x value)))
(defun actually-inc-b (value) (incf (y value)))

(defmethod inc ((object a)) (actually-inc-a object))
(defmethod inc ((object b)) (actually-inc-b object))

i.e., place the part of the implementation you want to share into a separate function.

(defun apply-velocity-for-simple-electron (particle v)
  (setf (mass particle) (* (mass particle) (sqrt (- 1 (sq (/ v 3e8)))))))

(defun flip-spin-for-simple-electron (particle)
  (setf (spin particle) (- (spin particle))))

(defmethod apply-velocity ((particle electron) v)
  (apply-velocity-for-simple-electron particle v))

(defmethod flip-spin ((particle electron))
  (flip-spin-for-simple-electron particle))

(defmethod apply-velocity ((particle strange-electron) v)
  (setf (mass particle) (* (/ 8 10) (mass particle)))
  (flip-spin-for-simple-electron particle))

(defmethod flip-spin ((particle strange-electron))
  (when (= (hidden-state particle) 1)
    (call-next-method)
    (apply-velocity-for-simple-electron particle #| Hu? What's the V here? |#)))

Given, that I don't know anything about electrons, whether plain or strange, spinning or not, I couldn't really think of a meaningful name for those base helper functions. But apart from that...

Dirk
  • 30,623
  • 8
  • 82
  • 102
  • I was thinking at dinner to use macros to define the guts of methods, so that I wouldn't have to reproduce that code, came back and saw this. It is an interesting concept to provide a second static interface that is higher performance but perhaps fewer features. I'm scratching my head at considering the implications of using your approach in general. I could write a macro that defines both the function and the method, but we quickly run into the problem that all the specializations in the generic function have the same name. If we start munging names, it looks like Stroutrup's front end. –  Feb 04 '16 at 06:28
  • The initial questions was inquired about the general case. Coredump asked for a concrete example, and you provided a good spot solution for the concrete example. However, as noted above, generalizing your answer is tantamount to writing a static object system (which is not a bad idea .. seems it must exist). So anyway, I chose the generic answer. –  Feb 06 '16 at 11:40
  • @user244488 Out of curiosity, do you have a reference implementation for those equations? If you look at the above answer, we can see we don't know what to supply as the `v` parameter in the last method. – coredump Feb 06 '16 at 15:32
  • @coredump for this strange electron, v is a function of parameters, but for sake in this case you may safely set it to zero. –  Feb 07 '16 at 07:18
  • @Dirk, I have happened on a way to bring your solution into CLOS. Note appendix 0 above, it is a small variation of this. One creates a method that does not get further specialized and then calls the static function within it. The static function then does not get used elsewhere, it remains private, rather the method gets used - and thus it stays friendly with CLOS. I think I'll put this in as an answer with an example. Though the name munging problem still exists. –  Feb 10 '16 at 03:22
3

It may be possible using MOP (MetaObect Protocol). It seems as if compute-applicable-methods might be exactly what you want.

It might also be possible to play rather horrible tricks using change-class.

Just a note, the methods in CLOS are not "methods on classes", they're "methods on generic functions". So you can't really call "a method of a different name, in the parent class", you can only call a different generic function.

Vatine
  • 20,782
  • 4
  • 54
  • 70
  • I see quotes in your comment, but don't see those quoted items in my post. I think/hope/tried to be careful to use the CLOS paradigm here and speak of methods that are specialized on classes. I went back and cleaned that language up a bit. –  Feb 03 '16 at 09:47
  • @user244488 You wrote, several times, about "calling a method on the parent class", which is a clear sign of Java/C++/Python/Smalltalk classes/methods. – Vatine Feb 04 '16 at 07:57
  • 'clear sign' OMG no! ;-) Anyone who has done a lot of object oriented programming starts to wonder why methods "belong" to classes especially those that operate on more than one type object, as in fact they are selected upon type signatures. Put another way, why should a message carry one object, to another, and not the other way? It makes sense to break the false ownership and leave it to (dynamic for LISP) signature matching. It makes sense to have a generic function interface rather than an abstract base class. I have no confusion on these points. This is not the source of my question. –  Feb 04 '16 at 13:59
3

PS: I know this answer is late, but I still find it a strong option not yet regarded in other answers.


Note: For methods specialized on a single parameter, it may make sense to say that the next method is a method specialized on a superclass of the argument provided for the specialized parameter.

However, this doesn't hold in general, for instance, with a method specializes on one parameter and another method on another parameter, or with methods specialized on more than one parameter.


Nonetheless, for the practical problem you have at hand, you may use another approach, which is to use a special variable to tell your own methods to simply call-next-method:

(defvar *strange-electron-bypass* nil)

(defmethod flip-spin ((particle strange-electron))
  (let ((bypass *strange-electron-bypass*)
        (*strange-electron-bypass* nil))
    (cond (bypass
           (call-next-method))
          ((= (hidden-state particle) 1)
           (call-next-method)
           (let ((*strange-electron-bypass* t))
             ;; where does v come from?
             (apply-velocity particle v)))
          (t
           nil))))

(defmethod apply-velocity ((particle strange-electron) v)
  (let ((bypass *strange-electron-bypass*)
        (*strange-electron-bypass* nil))
    (cond (bypass
           (call-next-method))
          (t
           (setf (mass particle)
                 (* (/ 8 10) (mass particle)))
           (let ((*strange-electron-bypass* t))
             (flip-spin particle))))))

The performance of the call to flip-spin (strange-electron) inside apply-velocity (strange-elector t) will not get hurt much if you only ever specialize on classes. In most (if not all) CLOS implementations, the applicable methods will be memoized (cached) based on the argument's classes in this case, so only the first call on an instance of strange-electron itself will pay the price for computing the applicable methods.

This approach has the advantage that it is generalizable, as it will call whatever is the next most specific method, and it doesn't require messing with CLOS, which usually would mean losing optimizations performed by the Common Lisp implementation.

EDIT: As you can see, the variable *strange-electron-bypass* is rebound to nil on method entry to support recursion, mutual or otherwise. In this case, there's no recursion, but if you want to generalize this solution to other situations where there might be recursion (i.e. the same method is applicable twice in the call stack), especially in a composition case, the methods will be reentrant.

acelent
  • 7,965
  • 21
  • 39
  • This solution is not thread safe. Suggest instead adding 'strange-electron-bypass' as an optional parameter that defaults to nil. –  Feb 15 '16 at 02:43
  • 1
    @user244488 It is customary for multi-threaded implementations to make special variables thread-local. Two different threads can coexist with their own dynamic scope. That being said, the answer suffers from the same minor problem as with optional parameters, being that the caller might explicitely set the bypass to true. – coredump Feb 15 '16 at 08:35
  • 1
    @user244488, if you add the optional parameter to the protocol, you're forcing an implementation detail into the protocol. By use of a dynamic variable, you hide this detail. And like coredump- said, current implementations perform a thread-local binding on a special variable. Also, I believe composition is the actual solution to this kind of problem, to avoid inheritance and next methods altogether. I posted this answer as an option to solve the practical problem with minimal changes, but I wouldn't rely on this technique if I'd care about maintainability. – acelent Feb 15 '16 at 09:57
2

Dirk's answer has a couple of problems which can be fixed, as shown here.

Firstly, it does not generalize without becoming a new static object system. When attempting generalization, one quickly runs into the fact that all methods belonging to the same generic definition have the same name. In order to fix this problem one is left to give the functions munged names reflecting their type signature (as per Stroustrup's famous macro processor).

Secondly, when generalized it becomes a separate static object oriented system. As a static system it does not play well with CLOS. It becomes a case of mixed paradigms.

However, Dirks approach of avoiding code duplication can be kept local without exporting the auxiliary routines to the interface. This can be accomplished by wrapping them in CLOS methods. These CLOS methods then become branches in the specialization tree, one's that can be specialized separately from other branches. The name change then represents a branch rather than a type signature (more manageable).

So here is the encapsulated auxiliary function approach applied to the inc example. Note that inc-a becomes a less specialized function that can be called by others, including methods specialized on the inherited b class, as no methods in the b class specialize it further (unlike for inc).

(defclass a () ((x :accessor x :initform 0)))
(defclass b (a) ((y :accessor y :initform 0)))

(defgeneric inc (i))
(defgeneric inc-a (i)) ; same as inc, but won't be further specialized

(defmacro inc-a-stuff (i) ; this is not exported! not an interface
  `(incf (x ,i))
  )

(defmethod inc ((i a)) (inc-a-stuff i))
(defmethod inc ((i b)) (incf (y i)))

;; provides a method to generalize back to class a
;; this method does not get further specialization by b, thus
;; remains a window into the "a part"
(defmethod inc-a ((i a)) (inc-a-stuff i))

(defvar r (make-instance 'b))

(inc r) ; all good, increments y

;;(inc (r a)) ; ah how do you get this?
;;
(inc-a r) ; 

(describe r)

#|
Slots with :INSTANCE allocation:
  X  = 1
  Y  = 1
|#

This solution is hazard free for dynamic changes to the object schema. I.e. it works within CLOS.

  • 3
    I don't see a relevant difference from Dirk's answer, which you've accepted before you posted your own answer. There's nothing here that makes the functions or macros private (i.e. `flet`, `labels` or `macrolet`), in fact, the macro `inc-a-stuff` is superfluous. – acelent Feb 16 '16 at 10:41
  • 1
    You say you understand the separation of classes and generic functions in CLOS, but the words you choose in the question and in comments to other answers indicate otherwise. In CLOS, we don't talk about the methods of the base or super class(es), we talk about applicable methods. The system doesn't provide a way to skip applicable methods when invoking a generic function. The generic function `a` (or say, `apply-velocity`) may have different specializations than generic function `b` (or say, `flip-spin`), you just showed a specific case where they happen to have the same specializations. – acelent Feb 16 '16 at 10:44
  • 1
    So, although other OO languages support calling a base class's methods (e.g. `super` in Java, `base` in C#, a specific base type scope in C++), CLOS doesn't. It only supports calling the next applicable method from within a primary or around method, it doesn't support specifying a call to a generic function assuming a type different than the specific argument type (or list of types, for more than one specialized parameter). You could probably add this support via MOP, but I believe this is clearly way more trouble than composition or the option I provided. – acelent Feb 16 '16 at 10:51
  • @acelent, there is an important difference in this answer to that of Dirk's - dirk exports a static interface. In contrast I export a CLOS interface. The problem with Dirk's answer was that it defeated CLOS, this one does not. Furthermore, I pointed this out as a comment below Dirk's answer, suggested it be made into a new answer, and left the explanation in the text of the question for review -- and there was no response. It would be quite fine with me if Dirk added the CLOS wrapper to his answer. I was just trying to present clean code for the sake of others to come here. –  Feb 16 '16 at 11:00
  • 1
    Ok, I understand that, but note that, if you don't want to expose the implementation details, it doesn't matter if those functions are generic or not. The end effect is exactly the same whether you have simple static functions or specialized methods as a backend support for the exposed methods. Unless you want to expose the backend (which you never indicated), your answer is equivalent to Dirk's answer. – acelent Feb 16 '16 at 11:07
  • Two reasons they are not the same. One is that run time changes can be made to the CLOS interface - including the possible addition of more specialized methods which would change what gets called. That can't happen on the static interface. Secondly, the static interface is not supported. If you export it, you get name conflicts - and would have to invent some sort of name munging scheme .. i.e. you are quickly writing a custom static OO system. Hence the wrapper suggest here is important, and it was the last piece that made it practical on a large project. –  Feb 16 '16 at 11:15
  • Is it really necessary to pick on my words and accuse me of "not understanding" .. is that going to be useful for those who come to read this thread later? Also, it certainly makes it longer and of lower signal to noise ratio. –  Feb 16 '16 at 11:19
  • 1
    Since you're introducing name munging yourself, such as `inc-a` as a name to do what `inc` does on `a`, what does it matter if it is a static function or a generic function? In what does it differ from Dirk's answer, other than using `defmethod` instead of `defun` for `inc-a`? Are you expecting to add around, before and after methods to `inc-a`? If you only specialize `inc-a` on `a`, what do you get that you wouldn't with the code in a defun? If you specialize `inc-a` on more than `a`, why do you call it `inc-a` to begin with, and what shall be its purpose for other specializations? – acelent Feb 16 '16 at 11:24
  • 2
    You're probably feeling attacked because of coredump-'s reaction on [meta], but I'm genuinely trying to make a point that the terms you used didn't match the understanding of CLOS's separation between classes and generic functions. I believe that you understand now that saying something along the line of "invoking a base class method in CLOS" doesn't make sense in CLOS. I'm sorry if the statement sounds like an accusation, I'll certainly refrain from exposing this kind of assumptions I make in the future, I understand they can be uncomfortable. – acelent Feb 16 '16 at 11:30
  • 1) Name munging would be more extensive on the static interface, necessarily involving signatures. It is what Stroustrup did no his macro front end. In the case of providing alternative branches into the specialization tree, as inc-a has done here, one could simply suffix them by branch identifier. (Your proposed solution also introduced a new symbol.) 2) The static interface does not support dynamic operations, it also introduces two flavors of oo in the same system. It is not a valid argument that programmers don't need dynamic oo, especially when we already chose to use CLOS. –  Feb 16 '16 at 11:35
  • I'm am not aware of having said "base class method". Rather I have been careful to speak in terms of specialization -- and if you read the comments in this thread, you will realize that we have already been down this road. If being familiar with other oo paradigms, how they relate to CLOS, and seeing similarities and differences in patterns, disqualifies me from asking questions on Stackoverflow on CLOS, then you are completely correct to provoke this topic a second time. –  Feb 16 '16 at 11:38
  • 1
    ask a simple question on stack overflow - get brow beaten for a week. I simply don't have time for this any longer. Can you please delete this question so that I don't have the obligation/liability to continue this? –  Feb 16 '16 at 11:41
  • 1
    @user244488 You have no right to remove your question once it has gotten answers, but in extreme cases, you can ask a moderator to disassociate it from your account. – Magisch Feb 16 '16 at 14:44
  • 3
    I haven't upvoted or downvoted this question or any of its answers, I'm not a moderator and I'm not into any fight here. Actually, it seems I was the first one supporting this question as legitimate (it shows both research effort and explanation effort) and interesting. Please, don't delete or ask to delete it, this kind of situation somehow happens at [so] a bit too often. – acelent Feb 16 '16 at 14:48
  • 1
    As for mentioning calling a method on a base class, see [this comment](http://stackoverflow.com/questions/35171694/clos-how-to-call-a-less-specific-method/35400784?noredirect=1#comment58064244_35172339). You also mentioned in one of your edits: `(call-next-method my-complement) ; doesn't work, thinks my-complement is an arg`. It's pretty clear to me you wanted something akin to Java's `super` or C#'s `base` in CLOS, but **in general** it doesn't make sense to have it, although it might make sense in particular cases. In those particular cases, the option I provided is probably the easiest. – acelent Feb 16 '16 at 14:49
  • 1
    @user244488 you should add your explanations to user:acelent in the comments, to your answer proper, especially as the point is subtle. An answer should stand on its own, even if all comments get deleted (and they often do). -- re: meta, you're not the first to feel this way, rest assured. :) Best just ignore the noise, especially the (tentative "me too") downvotes. Not everything is noise though - in general we're not supposed to respond to answers by changing the question; asking a *new* question is often better, reformulated to reflect the new stuff. *Small steps*. – Will Ness Feb 16 '16 at 18:22
  • 1
    so even if first few answers are unsatisfactory, leave the question be, and ask the new one linking to the previous one, and explaining what you still seek that's different from answers there. That's the SO way AFAIUI. you get more upvotes, the answerers do too, and the Q&A entries are neat and clear. win-win-win fro everyone, including the future readers who are supposed to be able to understand it without reading any comments whatever. – Will Ness Feb 16 '16 at 18:26
  • 1
    @acelent actually, it's perfectly OK and even recommended that you upvote any Q and A that you find interesting and useful, incl. on the entry that you participate in. :) – Will Ness Feb 16 '16 at 18:41
  • @acelent come to think of it there is nothing wrong with the term 'base class method' per se, as it just means that a method that is specialized on a class, one which is not derived from a super class in the class hierarchy. In point of fact, Lisp does have classes, and it does have inheritance between those classes. I think the meaning is clear enough, if the terminology is not 'conventional pure Lisp argo' feel free to edit. As evidence that this is brow beating, note that you have said the question is an interesting one - you did the opposite of invalidating it. –  Feb 17 '16 at 10:10
  • @user244488 have you noticed that user:acelent already [said they're sorry for giving you the uncomfortable feeling](http://stackoverflow.com/questions/35171694/clos-how-to-call-a-less-specific-method#comment58560750_35400784), and that you have received [quite a few upvotes](http://stackoverflow.com/users/3446498/user244488?tab=reputation) as well? :) :) – Will Ness Feb 17 '16 at 11:04
  • @acelent, In the case of a single argument method specialized on a class, there is an isomorphism with the class declared 'belonging to' semantic in other languages. (note that type signature of the object is typically the first parameter in said languages). So my discomfort extends from a false distinction being made that somehow is supposed to indicate I shouldn't have asked this question, or that this answer is insufficient. This answer provides for needed branches in an otherwise rectilinear specialization system. It stems from this need, not from my lack of understanding. –  Feb 17 '16 at 11:10
  • @user244488 There is no need to have any direct inheritance relationship: I can just write `(defmethod m0 ((s string)) :string)` and `m0` can be called on built-in strings. Sure, at some point a method can only be specialized on a class which makes it a 1-to-1 association, but there is no aggregation or composition in effect (https://stackoverflow.com/questions/885937/difference-between-association-aggregation-and-composition). When you insist on bringing concepts from other languages that do not *generally* apply to CL, you only indicate that you are working against the language, not with it. – coredump Feb 17 '16 at 11:20
  • All this shows is that string is class. It is not a functional exception to the isomorphism (a disproof of) posited. i.e. it is just playing word games. I'm not opposed to word games of this sort - I do think the CLOS vocabulary for expressing the object oriented programming paradigm has some elegance to it. All I am saying here, is that people who commented here (actually not so much you) were too quick to notice the foreign argo and to then say the question, or solution, was due to my not understanding. Please excuse me now, as due to a lack of time I will bow out of stackoverfow. –  Feb 17 '16 at 11:45
  • 1
    @user244488, I kind of agree, I mention specific cases like this, such as standard class specialization on one argument (rule out: `eql` specializer, method qualifiers, non-standard combinations, specialization on different or multiple arguments, etc.). But at most, the method may *belong* to the class, the generic function is still a separate entity. No one is saying you shouldn't have asked, what I said is part of the question didn't made sense, and I hope my answer says why. Questions don't have to be perfect, otherwise, why would one even post the question if they already knew it all? – acelent Feb 17 '16 at 11:46
  • 1
    @user244488, for something supporting your inheritance argument: [CLHS 7.6.7 Inheritance of Methods](http://www.lispworks.com/documentation/HyperSpec/Body/07_fg.htm). I understand the merit of your solution over Dirk's one is to use CLOS for the backend, so I think this answer shouldn't be downvoted. **To other users**, this answer (ever since first posted) gives proper credit to Dirk's (most upvoted) answer, it's the actual solution that the op will use, it properly justifies the differences and the op doesn't get rep from self-answering, so think twice before (or instead of) downvoting it. – acelent Feb 17 '16 at 11:59
  • @user244488 I was also responding to your *deleted* comment and you perfectly know it. – coredump Feb 17 '16 at 12:10