How to call-next-method in scala

#1

In CLOS a method can delegate to the next most specific method by calling call-next-method. This is different (in Lisp) from calling the method in the direct superclass, because the superclass of a class might depend on the order it is mixed in. Consequently there is also a function next-method-p which evaluates to true if there is a next method to call.

Is there some equivalent of this in Scala. Here is an example.

case class BddNode(label:Int, positive:Bdd, negative:Bdd) extends Bdd {

  override def toString = {
    (positive, negative) match {
      case (BddTrue, BddFalse) => label.toString
      case (BddFalse, BddTrue) => "!" + label
      case _ => call-next-method // i.e., do what you would do if the toString method on BddNode did not exist
    }
  }
}
#2

Do you mean super.toString?

#3

What is super? Is it the direct superclass or is it the class that comes next in the linearized class list which might be different depending on which classes/traits are mixed in?

In this case the superclass is Bdd, but in general is something extends BddNode (never mind for the moment that it is a case class), and also inherits from other classes as well, it is no longer guaranteed that the direct superclass is still Bdd, right?

#4

You would need to use super.toString, and the behavior will depend on Scala’s well-defined (but sometimes confusing) linearization rules.

If you need to customize this behavior, you have some options, but none of them are as “straightforward” (for some definitions of straightforward) as stackable modification. If you are really into OO-patterns, you could use something along the lines of a strategy pattern (possibly w/ factory, and possibly reflection) to determine what to do in this instance.

I would argue that, at least from an OO perspective, if you are using trait linearization to the point that things have gotten confusing, and you’re writing an application rather than a library, you might have failed to favor composition over inheritance.

When working functionally, in a language where functions are first class objects, you can obviously use higher-order functions (which can include your own) to work around this behavior. There’s an older controversial post that I think still does a decent job of outlining why relying on traits for mixing in behavior can be confusing.

Edit: The reason for the strict linearization rules is to avoid the complexity that comes along with diamond inheritance.

1 Like
#5

Something like what you’re describing is available as stackable traits but as the name says you can only do it with traits, as they’re the only things that can be mixed in in arbitrary order. The compiler will tell you at mix-in time if no implementation in the superclass is available.

#6

For the call-next-method principle to work, it doesn’t matter which linerization order is used (as long as it is topologically consistent), rather it depends on that the “next method” (super), is not necessarily the class mentioned in the method definition, but rather really the applicable method in the linearization order.

In the example I showed above, the obvious and desired semantics are that I want to modify the behavior of this.toString but only in the cases that one of the first two pattern cases match. Otherwise want toString to behave as it would have otherwise. I.e., if toString is define an a less specific superclass, then the most specific such one is called.

Here is what I hope about the linearization. That it is topologically consistent. I.e., whenever class A extends B, then A comes before B in the linearized list. And if the linearized list is A1, A2, A3, …, An, and the method being executed is Ai, then super.toString calls toString on the minimum Aj ( with j>i) for which toString is defined.

For this simple case, it doesn’t matter because I know exactly what my small 300 line program does. But the question is for my general knowledge.

#7

I second that. Composition is at least straightforward.

You can use https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern if it fits your problem.