Directions in Future Languages - Predicate Dispatch (22 Jan 2005)
I'm certainly not the fist person to talk about predicate dispatch. Like almost everything else in the world of computing - LISP has already done it. But that doesn't mean that anyone actually uses it. (If you want papers see [it rocks] and [it's efficient].)
You probably know predicate dispatch from such
feature movies languages as Haskell and Erlang:
def fact(0): return 1 def fact(N): return N*fact(N - 1)
That's an example of pattern matching, but we can do more:
def is_even?(N) where N % 2 == 0: return 'yes' def is_even?(N): return 'no'
We're allowed to test whatever we wish as a predicate. Many people think that the predicates should be side-effect-free, but I'm not too bothered about that. If some coder has good reason for a side-effectful predicate then go right ahead.
No that, in the last example, the most specific function was called. This is determined by the dispatcher. The dispatcher collects and sorts a list of candidate functions and determines which are to be called. Even if I say most-specific-first I don't mean to say that the following will always work:
def foo(N) where N % 2 == 0: return True def foo(N) where N % 4 == 0: return False
Sure, the first function is more specific, but don't except your langauge to work that out. In the above case, both functions would be deemed equally specific so it would probably be a case of most-recently-defined first.
This brings us to a couple of other common tricks in these systems: call-next and :before, :after functions etc.
A call-next allows a more specific function to do stuff and then call the next most specific one etc:
def authenticate-user(Username) where Username == 'root': if global-disallow-root: return False call-next(Username)
In dynamic languages that function could be injected at any point and thus thus 'override' a generic authenticate-user function in a library.
:before, :after and :around are from LISP (note the colon) and allow an equally specific function to bump itself up the priority stack (:before), pre-process the return value before the caller sees it (:after) or both (:around)
Also, if one can define any dispatcher one likes (on a per-function-name basis) then they can do odd things. Like calling every appliciable function and summing their return values. (Probably best if the return function is commutative like addition, I think). I can't think, right now, of an example where it would be useful, but I'm sure someone will come across a suitable problem at some point in the history of the universe.
With predicate dispatch one can also build up common features of programming languages like, say, object orientation:
Let class be a type where SomeClass == SomeDerivedClass is true, but SomeDerivedClass == SomeDerivedClass is a more specific match. Now I can define objects:
def __init__(Self, X, Y) where Self == Rectangle: Self.X = X Self.Y = Y def type(Self) where Self == Rectangle: return 'Rectangle' Circle(Rectange) # make Circle == Rectange and # Circle < Rectange true def __init__(Self, X) where Self == Circle: Self.X = X Self.Y = Y def type(Self) where Self == Circle: return 'Circle'
Now one can override functions of a class 'in-place'. Just declare a more specific function (or use something like :before) and all calls to existing instances of that object are overridden:
def :before type(Self) where Self == Circle: return 'Ha, this will screw things up'
(p.s. I've no idea why I suddenly started Capatalising the first letter of variables in the examples. It's bad - don't do it.)
(you too can play with this stuff today by using PEAK)