Can a dispatcher method dispatch on function types?
← Dispatcher Validation · Ref: Q1377
Yes. EK9 functions are polymorphic, so a dispatcher handles a function hierarchy exactly as it handles a class or trait hierarchy.
A FUNCTION IS NOT A DISPATCHER; A CLASS METHOD IS
The 'as dispatcher' marker goes on a method of a class. A function cannot itself be a dispatcher. What a function can be is the thing dispatched ON - the dispatcher method takes the abstract base function as its parameter, and the handlers take its sub-functions.
THE SHAPE
defines function Processor() as pure abstract -> inputText as String <- outputText as String?
UpperProcessor() extends Processor as pure -> inputText as String <- outputText as String: inputText.upperCase()
defines class Describer describe() as dispatcher -> p as Processor //the abstract base function <- rtn as String: "some Processor"
describe()
-> p as UpperProcessor //a sub-function handler
<- rtn as String: "an UpperProcessor"
DECLARE THE VARIABLE AT THE BASE TYPE
Routing happens at runtime on the actual type. If you declare the variable at the concrete type, normal overload resolution picks the handler directly at the call site and the dispatcher is never consulted. Declare it at the base function type to exercise dispatch:
u as Processor: UpperProcessor result <- describer.describe(u)
SUB-FUNCTIONS WITHOUT A HANDLER REACH THE FALLBACK
A sub-function with no matching handler runs the dispatcher method's own body, the same as an unhandled subclass does.
THE TWO RULES THE COMPILER ENFORCES
Genus must match (E50090): a FUNCTION base takes FUNCTION handlers. You cannot mix a function base with a class handler.
Handlers must be in the hierarchy (E05210): a handler taking an unrelated function could never match, so it is rejected rather than left as silently dead code.
FUNCTION TYPES ARE NOMINAL, NOT STRUCTURAL
A function only conforms to another function type if it 'extends' it. A function whose signature merely happens to match 'Supplier of String' is not a 'Supplier of String', so a handler for that type would never fire. Declare an abstract base function and extend it.
See Q614 for hierarchy rules on classes. See Q619 for two-parameter dispatchers.
Example
defines module qa.dispatchervalidation.functions defines function //The abstract base function - the function analogue of an abstract class or trait. Processor() as pure abstract -> inputText as String <- outputText as String? UpperProcessor() extends Processor as pure -> inputText as String <- outputText as String: inputText.upperCase() LowerProcessor() extends Processor as pure -> inputText as String <- outputText as String: inputText.lowerCase() //No handler for this one - it must reach the dispatcher's own body. ReverseProcessor() extends Processor as pure -> inputText as String <- outputText as String: inputText //Same SIGNATURE as Processor but does NOT extend it. EK9 function conformance is //NOMINAL, so a dispatcher over Processor can never route to a handler taking this - //which is what the E05210 pair below demonstrates. Unrelated() as pure -> inputText as String <- outputText as String: inputText defines class //CORRECT: the dispatcher entry takes the abstract base FUNCTION, handlers take sub-functions. Describer describe() as dispatcher -> p as Processor <- rtn as String: "some Processor" describe() -> p as UpperProcessor <- rtn as String: "an UpperProcessor" describe() -> p as LowerProcessor <- rtn as String: "a LowerProcessor" defines function testFunctionDispatch() describer <- Describer() //Declared at the BASE function type, so the call site resolves to the dispatcher entry //and the routing decision is made at runtime on the actual function type. u as Processor: UpperProcessor l as Processor: LowerProcessor r as Processor: ReverseProcessor require describer.describe(u) == "an UpperProcessor" require describer.describe(l) == "a LowerProcessor" //ReverseProcessor has no handler, so it runs the dispatcher method body. require describer.describe(r) == "some Processor"
Common mistakes
E50090 — The dispatcher entry takes the function Processor, so every handler parameter must also be a function. Mixing a FUNCTION base with a CLASS handler is an incompatible genus. See ek9 -h E50090 for details.
Incorrect:
describe()
-> p as Describer
Correct:
describe()
-> p as UpperProcessor
E05210 — A handler taking Unrelated can never match, because Unrelated does not extend Processor. Function conformance in EK9 is nominal - matching the signature is not enough. Declare the handler's type with 'extends Processor'. See ek9 -h E05210 for details.
Incorrect:
describe()
-> p as Unrelated
Correct:
describe()
-> p as LowerProcessor
Other ways to ask this
- Can I use a dispatcher with functions in EK9?
- How do I dispatch on an abstract function?
- Do EK9 dispatchers work with function types or only classes?
- Can a function be a dispatcher in EK9?
- How do I route to different handlers based on which function was passed?
- Dispatcher over a function hierarchy in EK9
- Are EK9 functions polymorphic enough for dispatch?
Coming from another language?
Java: no equivalent - functional interfaces are structural at the lambda site but you cannot dispatch on which lambda was passed; you would carry a tag or use the visitor pattern. Kotlin: function types are structural and erased, so 'when (f) { is (String)->String -> ... }' cannot discriminate. Scala: closest with sealed traits wrapping functions, but the wrapper is explicit. Rust: dyn Fn trait objects carry no discriminant; you would use an enum. EK9: functions form a real named hierarchy, so each concrete function is its own type and the dispatcher routes on it directly.
Keywords: abstract, handler, routing, genus, nominal, dispatcher, extends, hierarchy, fallback, polymorphic, dispatch, E05210, E50090, function