Why does EK9 reject a deeply nested higher-order call chain like getMaker(x)(y)(z)?
← Code Quality · Ref: Q1342
EK9 allows single-level functional chaining (e.g. getTransformer("upper")("text") - a delegate-returning function whose delegate is then invoked) but rejects deeper nesting with E11069. A two-level chain such as getMaker("x")("seed")("text") forces inside-out mental evaluation: you must work out what getMaker("x") returns, then what calling that with ("seed") returns, then call THAT with ("text"). Each level hides a type transition. The fix is to extract each intermediate result into a named, typed local variable so every step is independently readable, inspectable, and reviewable. Single-level chaining remains valid and idiomatic.
See Q311 for quality checks.
Example
defines module qa.quality.call.chain defines function Transformer() as pure abstract -> input as String <- output as String? UpperTransformer() extends Transformer as pure -> input as String <- output as String: input.upperCase() Maker() as abstract -> seed as String <- transformer as Transformer? SimpleMaker() extends Maker -> seed as String <- transformer as Transformer: UpperTransformer getMaker() -> kind as String <- maker as Maker: SimpleMaker if kind == "simple" maker: SimpleMaker //THE FIX: extract each intermediate result into a named, typed local. //getMaker returns a Maker, the Maker returns a Transformer, the //Transformer returns a String. Each step is now independently clear, //instead of one inside-out chain getMaker("simple")("seed")("text"). buildResult() <- result as String? maker <- getMaker("simple") transformer <- maker("seed") result: transformer("text") defines program ExcessiveCallChainDemo() stdout <- Stdout() result <- buildResult() stdout.println(`Result is ${result}`)
Common mistakes
E11069 — The expression is a call applied to the result of a call applied to the result of a call - two levels of nesting beyond a plain call. This forces inside-out evaluation and hides each type transition. Extract each step into a named typed local (maker, transformer) so every line documents its type and the final call is single-level. Single-level chaining such as getTransformer("upper")("text") is fine. See ek9 -h E11069 for details.
Incorrect:
result: getMaker("simple")("seed")("text")
Correct:
maker <- getMaker("simple") transformer <- maker("seed") result: transformer("text")
Other ways to ask this
- What triggers E11069 EXCESSIVE_CALL_CHAIN?
- Why can't I chain more than one functional call in EK9?
- How do I fix a call applied to the result of a call applied to a call?
Coming from another language?
JavaScript/curried FP: f(a)(b)(c)(d) is unlimited and unchecked, leaving the reader to trace types inside-out. Haskell/Scala: deep currying is idiomatic but relies on a type-aware editor to reveal intermediate types. C: the same cognitive problem as nested pointer dereferences (*(**fp)(x))(y). EK9: compile-time error (E11069) at one level beyond single chaining, forcing extraction into named intermediate variables that document each type.
Keywords: intermediate, call, extract, chain, quality, E11069, higher-order, nested, delegate