Can functions form multi-level inheritance chains?
← Function Extension · Ref: Q593
EK9 functions can form multi-level chains: an abstract function at the top, with concrete implementations at each level. Functions are closed by default, like classes: a concrete function can only be extended when it is declared 'as open'.
A MULTI-LEVEL CHAIN
An abstract function can extend another abstract function, and a concrete function at the end of the chain implements it:
defines function BaseOp as pure abstract -> x as Integer <- rtn as Integer?
PositiveOp is BaseOp as pure abstract -> x as Integer <- rtn as Integer?
DoubleOp is PositiveOp as pure -> x as Integer <- rtn as Integer: x + x
CONCRETE FUNCTIONS ARE CLOSED BY DEFAULT
A function that provides an implementation cannot be extended unless it is declared 'as open' (for example DoubleOp is PositiveOp as pure open). Extending a closed function is error E05030, not open to be extended - the same rule as classes.
MULTIPLE IMPLEMENTATIONS
An abstract function can have many direct implementations, creating a flat hierarchy with one abstract parent and multiple concrete children.
See Q588 for function extension basics. See Q589 for multiple implementations. See Q594 for extension rules. See Q595 for pure abstract function constraints.
Example
defines module qa.function.multilevelchain defines constant MIN_FILTER_LENGTH <- 3 defines function //Abstract function: base of hierarchy Filter as pure abstract -> input as String <- passes as Boolean? //A second abstract level: extends Filter and is itself extended below MinLengthFilter is Filter as pure abstract -> input as String <- passes as Boolean? //Multiple direct implementations (flat hierarchy) NonEmptyFilter is Filter as pure -> input as String <- passes as Boolean: length input > 0 //LongEnoughFilter is a concrete function three levels from the top of this chain LongEnoughFilter is MinLengthFilter as pure -> input as String <- passes as Boolean: length input > MIN_FILTER_LENGTH ContainsAtFilter is Filter as pure -> input as String <- passes as Boolean: input contains "@" defines program MultilevelFunctionChainDemo() stdout <- Stdout() //All implementations share the abstract type filters <- List() of Filter filters += NonEmptyFilter filters += LongEnoughFilter filters += ContainsAtFilter testValues <- ["", "hi", "hello", "user@host"] for testVal in testValues stdout.println(`Testing: "${testVal}"`) for filterFn in filters stdout.println(` Passes: ${filterFn(testVal)}`)
Common mistakes
E08120 — A function declared as pure must not mutate variables. The += operator mutates input in place, so the compiler reports E08120. See ek9 -h E08120 for details.
Incorrect:
NonEmptyFilter is Filter as pure -> input as String <- passes as Boolean: length input > 0 input += "x"
Correct:
NonEmptyFilter is Filter as pure -> input as String <- passes as Boolean: length input > 0
Other ways to ask this
- Can a concrete function be extended further?
- How deep can function hierarchies go?
- Can I have Abstract -> Middle -> Concrete function chains?
Coming from another language?
Java: functional interfaces are flat (one method). Kotlin: function types are structural. Rust: Fn traits have no hierarchy. EK9: abstract functions can have multiple concrete implementations, concrete functions are closed unless declared as open.
Keywords: chain, extension, multi-level, function, extend, hierarchy, abstract, closed, concrete