How do I pass functions as delegates in EK9?
← Getting Started · Ref: Q55
Because EK9 functions are types, you can store them in variables, pass them as parameters, and return them from other functions. A variable that holds a function reference is called a delegate.
STORING A FUNCTION IN A VARIABLE
Declare a variable typed as the abstract function, assign a concrete implementation:
processor as Processor: UpperProcessor result <- processor("Hello") //Calls UpperProcessor
Without parentheses, 'UpperProcessor' is a REFERENCE. With parentheses, 'UpperProcessor("Hello")' is a CALL. This distinction is critical.
REASSIGNING DELEGATES
Change the function a delegate points to at runtime:
processor: LowerProcessor result <- processor("Hello") //Now calls LowerProcessor
PASSING FUNCTIONS AS PARAMETERS
Functions can accept other functions as parameters. Use the abstract function type:
applyToGreeting()
-> transform as Processor
<- result as String: transform("Hello World")
Call with any implementation:
upper <- applyToGreeting(UpperProcessor) lower <- applyToGreeting(LowerProcessor)
USING BUILT-IN GENERIC FUNCTION TYPES
For common patterns, use the built-in abstract function types instead of defining your own:
Consumer of T //Pure: takes T, returns nothing Acceptor of T //Not pure: takes T, returns nothing Predicate of T //Pure: takes T, returns Boolean UnaryOperator of T //Pure: takes T, returns T Supplier of T //Pure: takes nothing, returns T Comparator of T //Pure: compares two T values
These save you from defining abstract functions for standard patterns. See Q54 for the pure/non-pure distinction.
FUNCTIONS IN COLLECTIONS
Store delegates in a list using the idiomatic literal syntax:
processors <- [UpperProcessor, LowerProcessor] for proc in processors stdout.println(proc("Hello"))
This creates a List of Processor inferred from the contents. Much cleaner than explicit List construction.
See Q51 for abstract functions. See Q56 for higher-order functions that return delegates. See Q57 for using delegates to change class behaviour. See Q59 for dynamic functions as pipeline stage delegates. See Q89 for using function delegates in stream pipelines. See Q109 for composition using delegation. See Q235 for complete stream operations reference. See Q262 for observer pattern using function delegates as event listeners.
Example
defines module qa.functiondelegate defines function Processor() as abstract -> text as String <- result as String? UpperProcessor() extends Processor -> text as String <- result as String: text.upperCase() LowerProcessor() extends Processor -> text as String <- result as String: text.lowerCase() applyToGreeting() -> transform as Processor <- result as String: transform("Hello World") defines program DelegateDemo() stdout <- Stdout() // Direct call — with parentheses stdout.println(`direct: ${UpperProcessor("Hello")}`) // Delegate — variable holds function reference processor as Processor: UpperProcessor stdout.println(`delegate: ${processor("Hello")}`) // Reassign delegate processor: LowerProcessor stdout.println(`reassigned: ${processor("Hello")}`) // Pass function as parameter upper <- applyToGreeting(UpperProcessor) lower <- applyToGreeting(LowerProcessor) stdout.println(`upper: ${upper}`) stdout.println(`lower: ${lower}`) // Functions in a list — idiomatic literal syntax processors <- [UpperProcessor, LowerProcessor] for proc in processors stdout.println(`list: ${proc("World")}`)
Common mistakes
E50030 — Without 'extends Processor', UpperProcessor is a standalone function with no type relationship to Processor. Assigning it to a Processor variable fails because the types are incompatible. Use 'extends' or 'is' to establish the function hierarchy. See ek9 -h E50030 for details.
Incorrect:
UpperProcessor() as pure
Correct:
UpperProcessor() extends Processor
Other ways to ask this
- How do function delegates work in EK9?
- How do I store a function in a variable in EK9?
- How do I pass a function as a parameter in EK9?
Coming from another language?
Java: functional interfaces as parameter types, method references with :: syntax, no first-class function variables (only interface references), lambda-to-interface conversion is implicit structural matching. Python: functions are first-class values, pass by name, no type contract on parameters, duck typing. JavaScript: functions are first-class, pass by reference, no type safety, callback hell pattern. Rust: fn pointers for simple functions, Fn/FnMut/FnOnce trait objects for closures, dyn Fn for dynamic dispatch, Box<dyn Fn> for ownership, complex lifetime annotations. Go: function values, pass by name, no type hierarchies, limited type safety. C#: delegates and Func/Action types, explicit delegate declaration needed, += multicast delegates add complexity. Kotlin: function types (String) -> String are structural, no nominal type identity, SAM conversion for interfaces. Swift: function types (String) -> String are structural, @escaping for stored closures. EK9: functions ARE types, store in variables typed as abstract function, pass as parameters using abstract function type, collect in lists with literal syntax, delegate vs call distinguished by parentheses, built-in generic function types for common patterns.
Keywords: beginner, list, store, delegate, first, variable, pass, parameter, predicate, start, reference, consumer, collection, intro, call, parentheses, function