Lecture
In computer programming, predicate dispatch — is a generalization of multiple dispatch (“multimethods”) that allows the method to call at run time to be selected based on arbitrary decidable logical predicates and/or pattern matching attached to the method declaration.
Put more simply:
Raku supports predicate dispatch using “where” conditions, which can execute arbitrary code for any parameter of a function or method.
In Julia there is the PatternDispatch.jl package for this, but otherwise it natively supports multiple dispatch.
Experimental implementations have been created for Common LISP and for Java (JPred).
This makes it possible to freely extend previously declared methods at a fine-grained level, but multiple extensions with identical or overlapping predicates created by different developers can interfere with each other in unforeseen ways. In this respect it resembles aspect-oriented programming.

In pseudocode:
draw(shape::Circle) draw(shape::Rectangle)
The choice depends only on the type of the argument.
draw(Circle(...)) -> first method draw(Rectangle(...)) -> second method
Suppose there is a function that calculates a discount.
discount(customer) where customer.vip discount(customer) where customer.age >= 65 discount(customer) where true
Calls:
discount(Customer(vip=true, age=30))
→ the VIP version will be selected.
discount(Customer(vip=false, age=70))
→ the senior-citizen version.
discount(Customer(vip=false, age=40))
→ the default version.
Here all the objects are of the same type, Customer, but the choice of method is based on the values of its fields.
Raku has built-in where predicates.
multi sub sign(Int $x where * > 0) {
"positive"
}
multi sub sign(Int $x where * < 0) {
"negative"
}
multi sub sign(Int $x where * == 0) {
"zero"
}
Usage:
say sign(10); # positive say sign(-5); # negative say sign(0); # zero
Here all the methods accept the same type, Int, but the dispatcher selects the appropriate variant based on the condition.
Suppose HTTP requests are being handled.
handle(request) where request.method == "GET" handle(request) where request.method == "POST" handle(request) where request.method == "DELETE"
Call:
handle(Request("POST"))
→ the POST handler will be called.
Range checking:
tax(amount) where amount < 1000 tax(amount) where amount >= 1000 && amount < 5000 tax(amount) where amount >= 5000
Calls:
tax(300) tax(2000) tax(10000)
The type of the argument is always the same (Number), but the selection logic differs.
Imagine that two developers independently added methods.
Developer A:
process(x) where x > 0
Developer B:
process(x) where x % 2 == 0
What to do with the call:
process(10)
Both predicates are true:
10 > 0 10 % 2 == 0
The system must somehow resolve the conflict:
This is precisely why predicate dispatch is significantly more complex than ordinary multiple dispatch.
| Mechanism | Basis for method selection |
|---|---|
| Single dispatch | Type of a single object |
| Multiple dispatch | Types of all the arguments |
| Predicate dispatch | Arbitrary logical conditions over the arguments |
For example, the call
pay(user, amount)
can select an implementation based on a condition:
where user.vip where amount > 10000 where user.country == "UA" where amount < 0
which makes it much more flexible, but also much harder to analyze and resolve conflicts in.
Comments