One feature of CLOS which I really miss in Scala is method qualifiers.
I wonder what the best approach to working around this missing feature is.
Here is an example.
As an API, I have a method named foo
. All users of my library make calls to foo
. And foo
is well documented in .md
files, and source files, and in other documentation inside and outside the project file hierarchy. The method is defined on all classes in a class hierarchy, and the method takes exactly one argument which is of the same type as the top of the class hierarchy.
This means if I have n
leaf classes, there are possibly n^2
cases to consider. Usually several cases are considered together, but the unit testing framework needs to make sure that all n^2
cases are thoroughly tested.
Suppose the top class is A
and the leaf classes are V
, W
, X
, Y
, and Z
, perhaps with some intervening abstract classes.
If V.foo(a)
is the same algorithm independent of the class of its argument, that code can go into the method defined in the V
class—no problem.
But what about if a.foo(W)
is the same algorithm for all leaf classes. Where should that code go? Moreover, what if it is algorithmically better or even algorithmically necessary that whenever the argument has class W
, then the other code needs to be circumvented. How do make sure that the check for W
is made first, regardless of the class of the object on the left-hand-side of the dot?
There are two ugly ways that I can think of to implement this in Scala.
-
Write a function,
bar
(perhaps a method in A) and call it atop everyfoo
method, including program logic such asif a.bar(b).nonEmpty return its-value else ...
. Remember to add a comment telling the programmer, if you implement a newfoo
method for a new class, remember to callbar
on the first line of the method including logic to deal with its return value. This has the horrible disadvantage that iffoo
callssuper.foo(...)
thenbar
will be called twice. -
Rename the
foo
methods tofooDown
, but don’t rename the call-sites. Now re-implementfoo
as a call tobar
followed by a call tofooDown
. Now re-read all the documentation and selectively replacefoo
withfooDown
. So the API becomes, callfoo
but implementfooDown
.
In CLOS there is a concise feature to handle this. It is called an around method
. Any code which needs to happen as a precondition of calling the method goes in the around method. and when call-next-method
(the equivalent of super.foo()
) is called, that pre-condition code DOES NOT get re-evaluated.