How do I dispatch over a mix of classes and functions in one dispatcher?
← Dispatcher Validation · Ref: Q1378
Declare the dispatcher entry point with 'Any' as its parameter type. 'Any' is the ONLY common super of an aggregate and a function, so it is the only way to express dispatch over a mix of the two.
WHY 'Any' RATHER THAN A BASE YOU DECLARE
Normally a dispatcher takes an abstract class, a trait or an abstract function as its base, and handlers take types under it. That is preferred: the base names the contract, and the compiler can check handlers against it.
But some sets of types have no declarable common base. Built-in types such as Integer, Float and Date are closed - you cannot make them extend a type of yours. A class and a function are in different genus families entirely. For those, 'Any' is not laziness; it is the only supertype that exists.
THE SHAPE
describe() as dispatcher -> item as Any //the entry point, and the fallback body <- rtn as String: "something else"
describe()
-> item as Integer //a built-in
describe()
-> item as Renderable //a trait - routes the classes implementing it
describe()
-> item as Point //a record
describe()
-> item as Formatter //an abstract FUNCTION - routes its sub-functions
A heterogeneous list holds them together, because the compiler infers 'List of Any':
items as List of Any: [ 1, Widget(), point, ShoutFormatter ] for item in items stdout.println(d.describe(item))
WHAT YOU KEEP, AND WHAT YOU GIVE UP
An 'Any' base is still checked. Handlers are still checked for ambiguity, still checked for reachability (E05280), and the entry point still requires a body (E07120) which catches every type no handler matches.
What you give up is exhaustiveness. With a sealed trait the compiler can prove every permitted type has a handler (E05260); with 'Any' the set is open, so the fallback body is the only guarantee. Write it as a real answer, not as a placeholder.
EVERY HANDLER STILL NEEDS SOMETHING THAT CAN REACH IT
'Any' admits every type, but a handler is still dead if nothing can have its type. A handler for a trait nothing implements, or for an abstract function nothing extends, is reported as E05280. This bites most often with parameterised generic function types: a named function cannot extend 'Consumer of Integer', so the conforming value has to be a dynamic function.
See Q1377 for dispatching over a function hierarchy alone. See Q614 for the hierarchy rules on classes. See Q254 for the Any type itself.
Example
defines module qa.dispatchervalidation.anymixed defines trait Renderable render() as pure <- rtn as String? defines record Point x as Integer: 0 y as Integer: 0 default operator ? defines function //An abstract FUNCTION. Nothing but 'Any' is a common super of this and a class. Formatter() as pure abstract -> inputText as String <- outputText as String? ShoutFormatter() extends Formatter as pure -> inputText as String <- outputText as String: inputText.upperCase() defines class Widget with trait of Renderable override render() as pure <- rtn as String: "Widget" default operator ? //CORRECT: 'Any' is the only base that can take a built-in, a trait, a record AND a function. Describer describe() as dispatcher -> item as Any <- rtn as String: "something else" describe() -> item as Integer <- rtn as String: `Integer ${item}` describe() -> item as Renderable <- rtn as String: `Renderable ${item.render()}` describe() -> item as Point <- rtn as String: `Point ${item.x},${item.y}` describe() -> item as Formatter <- rtn as String: `Formatter says ${item("hi")}` defines function testAnyMixedDispatch() describer <- Describer() point <- Point() point.x: 2 point.y: 3 //The compiler infers 'List of Any' - the only type that can hold all four. items as List of Any: [ 1, Widget(), point, ShoutFormatter ] //Each element is typed 'Any', so every call resolves to the dispatcher ENTRY POINT and the //routing decision is made at runtime on the actual type. Declare at a concrete type instead //and normal overload resolution picks the handler directly, never consulting the dispatcher. results <- List() of String for item in items results += describer.describe(item) require results contains "Integer 1" require results contains "Renderable Widget" require results contains "Point 2,3" require results contains "Formatter says HI" //Nothing handles a Date, so it reaches the dispatcher's own body. other as Any: 2026-09-10 require describer.describe(other) == "something else"
Common mistakes
E05280 — With nothing extending the abstract function Formatter, no value can ever have that type, so the handler can never be selected. An 'Any' base does not change this - it admits every type that EXISTS. See ek9 -h E05280 for details.
Incorrect:
ShoutFormatter() as pure
Correct:
ShoutFormatter() extends Formatter as pure
E07120 — The dispatcher entry point must provide a body. With an 'Any' base the type set is open, so that body is the only thing standing between an unhandled type and no answer at all. See ek9 -h E07120 for details.
Incorrect:
describe() as dispatcher -> item as Any <- rtn as String?
Correct:
describe() as dispatcher -> item as Any <- rtn as String: "something else"
Other ways to ask this
- Can one dispatcher handle both objects and functions in EK9?
- Why would I use Any as a dispatcher base type?
- What is Any good for on a dispatcher?
- How do I dispatch over a heterogeneous List of Any?
- Can a dispatcher route a record, a class and a function?
- Dispatching across unrelated types in EK9
- What is the common supertype of a class and a function in EK9?
Coming from another language?
Java: no single mechanism - you would use instanceof chains or pattern matching over Object, and a lambda cannot be told apart from another lambda at runtime. Kotlin: 'when (x) { is ... }' over Any, but function types are erased so the function arm cannot discriminate. Python: functools.singledispatch works on any first argument, closest in spirit, but has no static checking. EK9: the dispatch is declared once in the receiver, the compiler checks every handler is reachable and unambiguous, and functions are real named types so they route like any other.
Keywords: E07120, heterogeneous, E05280, mixed, genus, record, common supertype, dispatcher, trait, Any, fallback, dispatch, List of Any, function