Predicate Dispatching: A Uni ed Theory of Dispatch
Michael Ernst Craig Kaplan Craig Chamb ers
Technical rep ort UW-CSE-98-01-0 2
Department of Computer Science and Engineering
UniversityofWashington
Seattle, WA, USA 98195-2350
fmernst;csk;[email protected]
http://www.cs.washington.edu/research/projects/cecil/
12 January 1998
Abstract
Predicate dispatching generalizes previous metho d dispatch mechanisms by p ermitting arbitrary predicates
to control metho d applicability and by using logical implication b etween predicates as the overriding relation-
ship. The metho d selected to handle a message send can dep end not just on the classes of the arguments, as
in ordinary ob ject-oriented dispatch, but also on the classes of sub comp onents, on an argument's state, and
on relationships b etween ob jects. This simple mechanism subsumes and extends ob ject-oriented single and
multiple dispatch, ML-style pattern matching, predicate classes, and classi ers, which can all b e regarded
as syntactic sugar for predicate dispatching. This pap er intro duces predicate dispatching, gives motivating
examples adapted from a prototyp e implementation, and presents its static and dynamic semantics.
1 Intro duction
Many programming languages supp ort some mechanism for dividing the b o dy of a generic function into
a set of cases, with a declarative mechanism for selecting the right case for each dynamic invo cation of the
generic function. Case selection can b e broken down into tests for applicability a case is a candidate for
invo cation if its guard is satis ed and overriding which selects one of the applicable cases for invo cation.
Ob ject-oriented languages use overloaded metho ds as the cases. A metho d is applicable if the run-time
class of the receiver argument is the same as or a sub class of the class on which the receiver is sp ecialized.
+
Multiple dispatching [BKK 86, Cha92] enables testing the classes of all of the arguments. One metho d over-
rides another if its sp ecializer classes are sub classes of the other's, using either lexicographic CLOS [Ste90]
or p ointwise Cecil [Cha93a] ordering.
Predicate classes [Cha93b] automatically classify an ob ject of class A as an instance of virtual sub class
B a sub class of A whenever B 's predicate an arbitrary expression typically testing the runtime state of an
ob ject is true. This creation of virtual class hierarchies makes metho d dispatching applicable even in cases
where the e ective class of an ob ject maychange over time. Classi ers [HHM90b] and mo des [Tai93] are
similar mechanisms for reclassifying an ob ject into one of a numb er of sub classes based on a case-statement-
like test of arbitrary b o olean conditions.
Pattern matching as in ML [MTH90] bases applicability tests on the run-time datatyp e constructor
tags of the arguments and their sub comp onents. As with classi ers and mo des, textual ordering determines
+
overriding. Some languages, such as Haskell [HJW 92], allow arbitrary b o olean guards to accompany pat-
terns, restricting applicability. Views [Wad87] extend pattern matching to abstract data typ es by enabling
them to o er various concrete datatyp e-likeinterfaces.
Predicate dispatching integrates, generalizes, and provides a uniform interface to these similar but previ-
ously incomparable mechanisms. A metho d declaration sp eci es its applicability via a predicate expression, 1
E 2 expr The set of expressions in the underlying programming language
T 2 typ e The set of typ es in the underlying programming language
c 2 class-id The namespace of classes
m; f 2 metho d-id The namespace of metho ds and elds
p 2 pred-id The namespace of predicate abstractions
v; w 2 var-id The namespace of variables
Figure 1: Syntactic domains and variables
which is a logical formula over class tests i.e., tests that an ob ject is of a particular class or one of its sub-
classes and arbitrary b o olean-valued expressions from the underlying programming language. A metho d is
applicable when its predicate expression evaluates to true. Metho d m overrides metho d m when m 's pred-
1 2 1
icate logically implies that of m ; this relationship is computed at compile time. Static typ echecking veri es
2
that, for all p ossible combinations of arguments to a generic function, there is always a single most-sp eci c ap-
plicable metho d. This ensures that there are no message-not-understo o d errors called match-not-exhaustive
in ML or message-ambiguous errors at run-time.
Predicate expressions capture the basic primitive mechanisms underlying a wide range of declarative
dispatching mechanisms. Combining these primitives in an orthogonal and general manner enables new sorts
of dispatching that are not expressible by previous dispatch mechanisms. Predicate dispatching preserves
several desirable prop erties from its ob ject-oriented heritage, including that metho ds can b e declared in
any order and that new metho ds can b e added to existing generic functions without mo difying the existing
metho ds or clients; these prop erties are not shared by pattern-matching-based mechanisms.
Section 2 intro duces the syntax, semantics, and use of predicate dispatching through a series of examples.
Section 3 de nes its dynamic and static semantics formally. Section 4 discusses predicate tautology testing,
which is the key mechanism required by the dynamic and static semantics. Section 5 surveys related work,
and Section 6 concludes with a discussion of future directions for research.
2 Overview
This section demonstrates some of the capabilities of predicate dispatching byway of a series of examples. We
incrementally present a high-level syntax which app ears in full in Figure 5; Figure 1 lists supp orting syntactic
domains. Predicate dispatching is parameterized by the syntax and semantics of the host programming
language in which predicate dispatching is emb edded.
2.1 Dynamic dispatch
Each metho d implementation has an attached predicate expression which sp eci es when the metho d is ap-
plicable. Predicate expressions include class tests and negations, conjunctions, and disjunctions of predicate
expressions. An omitted predicate expression indicates that its metho d handles all typ e-correct arguments.
metho d-sig ::= signature m h T i : T
metho d-decl ::= metho d m h formal-pattern i [ when pred-expr ] metho d-b o dy
pred-expr ::= expr @ c succeeds if expr evaluates to an instance or
sub class of class c
j not pred-expr negation
j pred-expr and pred-expr conjunction short-circuited
j pred-expr or pred-expr disjunction short-circuited
Metho d signature declarations give the typ e signature shared by a family of metho d implementations.
A message send expression need examine only the corresp onding metho d signature declaration to determine
its typ e-correctness, while a set of overloaded metho d implementations must completely and unambiguously
implement the corresp onding signature in order to b e typ e-correct. 2
Predicate dispatching can simulate b oth singly- and multiply-dispatched metho ds by sp ecializing formal
parameters on a class via the \@class" syntax. Sp ecialization limits the applicability of a metho d to ob jects
that are instances of the given class or one of its sub classes. ML-style pattern matching is mo deled by
considering each constructor of a datatyp e to b e a class and sp ecializing metho ds on the constructor classes.
More generally, predicate dispatching supp orts the construction of arbitrary conjunctions, disjunctions, and
negations of class tests. The following example uses predicate dispatching to implement the Zip function
1
which converts a pair of lists into a list of pairs:
type List;
class Cons subtypes List { head:Any, tail:List };
class Nil subtypes List;
signature ZipList, List:List;
method Zipl1, l2 when l1@Cons and l2@Cons {
return ConsPairl1.head, l2.head, Zipl1.tail, l2.tail; }
method Zipl1, l2 when l1@Nil or l2@Nil { return Nil; }
The rst Zip metho d tests the classes of b oth arguments, and it only applies when b oth are instances
of Cons or some sub class; this is an implicit conjunction of two class tests. The second Zip metho d uses
explicit disjunction to test whether either argument is an instance of Nil or some sub class. The typ e
checker can verify statically that the two implementations of Zip are mutually exclusive and exhaustiveover
all p ossible arguments that match the signature, ensuring that there will b e no \message not understo o d"
or \message ambiguous" errors at run-time, without requiring the cases to b e put in any particular order.
ML-style pattern matching requires all cases to b e written in one place and put in a particular total
order, resolving ambiguities in favor of the rst successfully matching pattern. In a traditional singly- or
multiply-dispatched ob ject-oriented language without the ability to order cases, either the base case of Zip
must b e written as the default case for all pairs of List ob jects unnaturally, and unsafely in the face of
future additions of new sub classes of the default typ e, or three separate but identical base metho ds must b e
written one for NilAny, one for AnyNil, and a third for NilNil to resolve the ambiguitybetween the
rst two. In our exp erience with ob ject-oriented languages using a p ointwise, not lexicographic, ordering,
these triplicate base metho ds for binary messages o ccur frequently.
As a syntactic convenience, class tests can b e written in the formal argument list:
formal-pattern ::= [ v ][@ c ] like v @ c in pred-expr
The rst Zip metho d ab ovewould then b e rewritten as
method Zipl1@Cons, l2@Cons {
return ConsPairl1.head, l2.head, Zipl1.tail, l2.tail; }
2.2 Pattern matching
Predicates can test the run-time classes of comp onents of an argument, just as pattern matching can query
substructures, by suxing the @class test with a record-like list of eld names and corresp onding class tests;
names can b e b ound to eld contents at the same time. The ability in pattern matching to test for particular
constants of built-in typ es is a simple extension of class tests.
pred-expr ::= . . .
expr @ sp ecializer
sp ecializer ::= c [ f h eld-pat i g ]
eld-pat ::= m [ = v ][@ sp ecializer ]
As with pattern matching, testing the representation of comp onents of an ob ject makes sense when the
ob ject and the tested comp onents together implement a single abstraction. We do not advo cate using pattern
matching to test comp onents of ob jects in a way that crosses natural abstraction b oundaries.
1
Any is the top class, sub classed by all other classes, and Pair returns an ob ject containing its two arguments. 3
Our syntax for pattern matching on records is analogous to that for creating a record: { x := 7, y :=
22 } creates a two-comp onent record, binding the x eld to 7 and the y eld to 22, while { x = xval }
pattern-matches against a record containing an x eld, binding the new variable xval to the contents of that
eld and ignoring any other elds that might b e present. The similaritybetween the record construction and
matching syntaxes follows ML. Our presentation syntax also uses braces for record typ e sp eci ers as in the
declaration of the Cons class, ab ove and to delimit co de blo cks as in the de nitions of the Zip metho ds,
ab ove.
The following example, adapted from our implementation of an optimizing compiler, shows howa
ConstantFold metho d can dispatch for binary op erators whose arguments are constants and whose op-
erator is integer addition:
type Expr;
signature ConstantFoldExpr:Expr;
-- default constant-fold optimization: do nothing
method ConstantFolde { return e; }
type AtomicExpr subtypes Expr;
class VarRef subtypes AtomicExpr { ... };
class IntConst subtypes AtomicExpr { value:int };
... -- other atomic expressions here
type Binop;
class IntPlus subtypes Binop { ... };
class IntMul subtypes Binop { ... };
... -- other binary operators here
class BinopExpr subtypes Expr { op:Binop, arg1:AtomicExpr, arg2:AtomicExpr, ... };
-- override default to constant-fold binops with constant arguments
method ConstantFolde@BinopExpr{ op@IntPlus, arg1@IntConst, arg2@IntConst } {
return new IntConst { value := arg1 + arg2 }; }
... -- many more similarly expressedcases for other operators here
class UnopExpr subtypes Expr { op:Unop, arg:AtomicExpr, ... };
...
2.3 Bo olean expressions
To increase the expressiveness of predicate dispatching, predicates may include arbitrary b o olean expressions
from the underlying programming language. Additionally, names may b e b ound to values, for use later in
the predicate expressions and in the metho d b o dy. Expressions from the underlying programming language
that app ear in predicate expressions should have no externally observable side e ects.
pred-expr ::= . . .
j test expr succeeds if expr evaluates to true
j let v := E bind v to E ; always succeeds
The following extension to the ConstantFold example illustrates these features.
-- Hand le case of adding zero to anything but don't be ambiguous
-- with existing method for zero plus a constant.
method ConstantFolde@BinopExpr{ op@IntPlus, arg1@IntConst{ value=v }, arg2=a2 }
when testv == 0 and nota2@IntConst {
return a2; }
method ConstantFolde@BinopExpr{ op@IntPlus, arg1=a1, arg2@IntConst{ value=v } }
when testv == 0 and nota1@IntConst { 4
return a1; }
... -- other special cases for operations on 0,1 here
2.4 Predicate abstractions
Named predicate abstractions can factor out recurring tests and give names to semantically meaningful
concepts in the application domain. To allow abstraction over b oth tests and variable bindings, predicate
abstractions can return a record-like list of bindings. These bindings resemble the elds of a record or
class, and similar supp ort is given to pattern matching against a subset of the results of a named predicate
invo cation. Predicate abstractions thus can act like views or virtual sub classes of some ob ject or tuple of
ob jects, with the results of predicate abstractions acting like the virtual elds of the virtual class. If the
prop erties of an ob ject tested by a collection of predicates are mutable, the ob ject may b e given di erent
virtual sub class bindings at di erent times in its life, providing the b ene ts of using classes to organize co de
even in situations where an ob ject's \class" is not xed.
Because ob ject identity is not a ected by these di erent views on an ob ject, named predicate abstractions
are more exible than co ercions in environments with side-e ects. A single ob ject can b e classi ed in
multiple indep endentways by di erent predicate abstractions without b eing forced to de ne all the p ossible
conjunctions of indep endent predicates as explicit classes, relieving some of the problems asso ciated with a
mix-in style of class organization [HHM90b, HHM90a].
pred-sig ::= predsignature p h T i return f h f : T i g
pred-decl ::= predicate p h formal-pattern i
[ when pred-expr ][return f h f := expr i g ]
pred-expr ::= . . .
j p h expr i [ => f h eld-pat i g ] test predicate abstraction p
sp ecializer ::= pred-sp ec [ f h eld-pat i g ]
pred-sp ec ::= c expr @ c is a class test
j p expr @ pf :::g is alternate syntax
for pexpr => f :::g
A predicate abstraction takes a list of arguments and succeeds or fails as determined by its own predicate
expression. A succeeding predicate abstraction invo cation can return anyvalue computed in its predicate
expression, and the caller can retrieveany subset of the predicate abstraction's result bindings. Predicate
signatures sp ecify the typ e interface used in typ echecking predicate abstraction callers and implementations.
In this presentation, we prohibit recursive predicates.
Simple predicate abstractions are used just like ordinary classes:
predicate on_x_axisp@point
when p@cartesianPoint and testp.y == 0
or p@polarPoint and testp.theta == 0 or testp.theta == pi;
method drawp@point { ... } -- draw the point
method drawp@on_x_axis { ... } -- use a contrasting color so point is visible
In the following example, CFG_2succ is a CFG no de with two successors. Each successor is marked
with whether it is a lo op exit information which, in our implementation, is dynamically maintained when
the CFG is mo di ed and the greatest lo op it do es not exit. It is advantageous for an iterative data ow
algorithm to propagate values along the lo op exit only after reaching a xed p oint within the lo op; suchan
algorithm would dispatchontheLoopExit predicate. Similarly, the algorithm could switch from iterative
to non-iterative mo de when exiting the outermost lo op, as indicated by TopLevelLoopExit.
predsignature LoopExitCFGnode
return { loop:CFGloop };
predicate LoopExitn@CFG_2succ{ next_true: t, next_false: f }
when testt.is_loop_exit or test_f.is_loop_exit 5
return { loop := outermostt.containing_loop, f.containing_loop };
predicate TopLevelLoopExitn@LoopExit{ loop@TopLevelScope };
2.5 Classi ers
Classi ers are a convenient syntax for imp osing a linear ordering on a collection of predicates, ensuring
mutual exclusion. They combine the state testing of predicate classes and the total ordering of pattern
matching. An optional otherwise case, which executes if none of the predicates in the classi er evaluates to
true, adds the guarantee of exhaustion. Multiple indep endent classi cations of a particular class or ob ject
do not interfere with one another.
classi er-decl ::= classify h formal-pattern i
h as p when pred-expr [ return f h f := expr i g ] i
[ as p otherwise [ return f h f := expr i g ]]
Here is an example of the use of classi ers:
class Window { ... }
classifyw@Window
as Iconified when testw.iconified
as FullScreen when testw.area == RootWindow.area
as Big when testw.area > RootWindow.area/2
as Small otherwise;
method movew@FullScreen, x@int, y@int { } -- nothing to do
method movew@Big, x@int, y@int { ... } -- move a wireframe outline
method movew@Small, x@int, y@int { ... } -- move an opaque window
method movew@Iconified, x@int, y@int { ... } -- modify icon, not window, coordinates
-- resize, maximize, iconify similarly test these predicates
To force the classi cation to b e mutually exclusive, each case is transformed into a predicate which
includes the negation of the disjunction of all previous predicates. Therefore, an ob ject is classi ed by some
case only when it cannot b e classi ed byany earlier case.
3 Dynamic and static semantics
The rest of this pap er formalizes the dynamic and static semantics of a core predicate dispatching sublan-
guage. Figure 2 presents the abstract syntax of the core sublanguage. App endix A de nes desugaring rules
that translate the high-level syntax of Figure 5 into the core syntax.
In the sequel, we assume that all variables are distinct so that the semantic rules can ignore the details
of avoiding variable capture.
3.1 Dynamic semantics
This section explains how to select the most-sp eci c applicable metho d at each message send. This selection
relies on twokey tests on predicated metho ds: whether a metho d is applicable to a call, and whether one
metho d overrides another.
A metho d is applicable if its predicate evaluates to true; predicate evaluation also provides an extended
environment in which the metho d's b o dy is executed. Figure 3 de nes the execution mo del of predicate
0
evaluation in terms of the elab oration op erator and several help er functions. Wesay hP; K ihb; K i
when the predicate P evaluates in the environment K to the b o olean result b, pro ducing the new environment
0 0
K . If the result b is false, then the resulting environment K is ignored. 6
metho d-sig ::= signature m h T i :T
metho d-decl ::= metho d m h v i when pred-expr metho d-b o dy
pred-expr ::= true always applies
j test v applies if v is true
j v isa c applies if v is an instance of c or a sub class
j let v := E bind v to E ; always applies
j p h v i => f h f = v i g test predicate abstraction p
j not pred-expr negation
j pred-expr and pred-expr conjunction short-circuited
j pred-expr or pred-expr disjunction short-circuited
pred-sig ::= predsignature p h T i return f h f : T i g
pred-decl ::= predicate p h v i when P return f h f := v i g
Figure 2: Abstract syntax of the core language. Words and symb ols in b oldface represent terminals. Angle
brackets denote zero or more comma-separated rep etitions of an item. Square brackets contain optional ex-
pressions. We freely use parentheses around pred-expr s to indicate order of op erations. Recursive predicates
are forbidden.
Predicate dispatching considers one metho d m to override another metho d m exactly when m 's pred-
1 2 1
icate implies m 's predicate and not vice versa. Section 4 describ es how to compute the overriding relation,
2
which can b e p erformed at compile time.
Given the evaluation mo del for predicate expressions and the ability to compare predicate expressions for
overriding, the execution of generic function invo cations is straightforward. Supp ose that generic function
m is de ned with the following cases:
metho d mv , :::, v when P fB g
1 n 1 1
metho d mv , :::, v when P fB g
1 n 2 2
.
.
.
metho d mv , :::, v when P fB g
1 n k k
Toevaluate the invo cation mE , :::, E in the environment K ,we rst obtain =evalE ;K for all
1 n i i
i =1;:::;n. Then, for j =1;:::;k,we obtain a truth value b and a new environment K through
j j
2
hP ;K[v := ;:::;v := ]i hb ;K i.
j 1 1 n n j j
Now let I b e the set of integers i such that b = true, and nd i 2 I such that P overrides all others
i 0 i
0
in fP g . The result of evaluating mE ;:::;E is then the result of evaluating B in the environment
i i2I 1 n i
0
K , so that variables b ound in the predicate can b e referred to in the b o dy. If no such i exists, then an
i 0
0
exception is raised a \message not understo o d" error if I is empty, or a \message ambiguous" error if there
is no unique minimal elementofI .
A clever implementation can makea numb er of improvements to this base algorithm. Here we brie y
mention just a few such optimizations. First, common sub expression elimination over predicate expressions
can limit the computation done in evaluating guards. Second, precomputed implication relationships can
prevent the necessity for evaluating every predicate expression. If a more sp eci c one is true, then the less
sp eci c one is certain to b e satis ed; however, such satisfaction is irrelevant since the more sp eci c predicate
will b e chosen. Third, clauses and metho ds can b e reordered to succeed or fail more quickly.
3.2 Static semantics and typ echecking
The op erational mo del of predicate dispatch describ ed in Section 3.1 can raise a run-time exception at a
message send if no metho d is applicable or if no applicable metho d overrides all the others. We extend the
typ echecking rules of the underlying language to guarantee that no such exception o ccurs.
Figure 4 presents the static semantic domains, help er functions, and typ echecking rules for the core
predicate dispatching sublanguage.
2
Since we assume that all variables are distinct, we can safely use the dynamic environment at the call site instead of
preserving the static environment at the predicate abstraction's de nition p oint. 7
; 2 value Values in the underlying programming language
b 2ftrue; falseg Mathematical b o oleans
K 2 var-id ! value [ pred-id ! pred-decl Environments mapping variables to values
and predicate names to predicate declarations
lo okupv; K ! Lo ok up the value of variable v in the environment K , returning the value .
0
K [v := ] ! K Bind the name v to the value in the environment K , resulting in the new envi-
0
ronment K .Ifv is already b ound, the existing binding is overridden.
evalE; K ! Evaluate the expression E in the environment K , returning the value .
instanceof ; c ! b Determine whether the value is an instance of c or any sub class of c.
accept ! b Co erce arbitrary program values to true or false.
htrue;Kihtrue;Ki
lo okupv; K = accept = b
hv; K ihb; K i
lo okupv; K = instanceof ; c= b
hv isa c; K ihb; K i
0
evalE; K = K [v := ]= K
0
hlet v := E; K ihtrue;K i
0
8i 2f1;:::;ng evalv ;K=
i
i
lo okupp; K = predicate pv ;:::;v when P return ff := E , :::, f := E , :::g
1 n 1 1 m m
0
hP; K [v := ;:::;v := ]ihfalse;K i
1 1 n n
0 0
hpv , :::, v => ff = w , :::, f = w g;Kihfalse;Ki
1 1 m m
1 n
0
8i 2f1;:::;ng evalv ;K=
i
i
lo okupp; K = predicate pv ;:::;v when P return ff := E , :::, f := E , :::g
1 n 1 1 m m
0
hP; K [v := ;:::;v := ] ihtrue;K i
1 1 n n
0
8i 2f1;:::;mg evalE ;K =
i i
00
K [w := ;:::;w := ]= K
1 1 m m
00 0 0
=> ff = w , :::, f = w g;Kihtrue;K i , :::, v hpv
1 1 m m
n 1
0
hP; K ihb; K i
hnot P; K ih:b; K i
0
hP; K ihfalse;K i
hP and Q; K ihfalse;Ki
0 0 00
hP; K ihtrue;K i hQ; K ihfalse;K i
hP and Q; K ihfalse;Ki
0 0 00
hP; K ihtrue;K i hQ; K ihtrue;K i
00
hP and Q; K ihtrue;K i
0
hP; K ihtrue;K i
hP or Q; K ihtrue;Ki
0 00
hP; K ihfalse;K i hQ; K ihtrue;K i
hP or Q; K ihtrue;Ki
0 00
hP; K ihfalse;K i hQ; K ihfalse;K i
hP or Q; K ihfalse;Ki
Figure 3: Dynamic semantic domains, help er functions, and evaluation rules 8
0 0
T T Typ e T is a subtyp e of T .
0 0
conformant-typ e T; c Return the most-sp eci c typ e T such that every sub class c of c that conforms to T
0
also conforms to T . This help er function is supplied by the underlying programming
language.
0 00 0 0 0