How do dynamic functions replace nested functions in EK9?
← Functions and Methods · Ref: Q601
EK9 does not support nested function definitions. You cannot define a named function inside another function. Dynamic functions with closures serve the same purpose: they are created inline, capture variables from the enclosing scope, and exist only within that scope.
WHY NO NESTED FUNCTIONS
Nested function definitions complicate scope analysis and name resolution. They create hidden coupling between the inner function and the outer function's variables. EK9 keeps function definitions flat (at module level) to make dependencies explicit.
DYNAMIC FUNCTIONS AS LOCAL HELPERS
Define an abstract function type at module level, then create dynamic implementations inline where needed:
Validator as pure abstract -> text as String <- valid as Boolean?
Inside a program or function body, create a dynamic function:
checker <- (minLen) is Validator as pure function valid:=? length text >= minLen
The dynamic function captures 'minLen' from the enclosing scope and implements the Validator contract. It behaves exactly like a nested function: scoped locally, accessing outer variables, callable only within the enclosing scope.
INLINE SINGLE-EXPRESSION FORM
For simple helpers, use inline syntax:
notEmpty <- () is Validator as pure (valid:=? length text > 0)
MULTIPLE LOCAL HELPERS
Create several dynamic functions in the same scope, each capturing different values:
short <- (shortLen: 3) is Validator as pure (valid:=? length text >= shortLen) long <- (longLen: 10) is Validator as pure (valid:=? length text >= longLen)
POLYMORPHIC USE
Because all dynamic functions implement the same abstract type, they can be stored in lists and iterated polymorphically.
See Q52 for dynamic function syntax. See Q53 for closure capture mechanics. See Q115 for dynamic classes. See Q590 for dynamic function extension. See Q596 for function vs method distinction.
Example
defines module qa.functionsAndMethods.dynamicAsNested defines function //Abstract function type: the contract for validators Validator as pure abstract -> text as String <- valid as Boolean? //Named implementation for comparison NotBlankValidator is Validator as pure -> text as String <- valid as Boolean: length text > 0 defines program DynamicAsNestedDemo() stdout <- Stdout() // === DYNAMIC FUNCTION AS LOCAL HELPER === //Captures minLen from enclosing scope minLen <- 5 lengthChecker <- (minLen) is Validator as pure function valid:=? length text >= minLen stdout.println(`"hello" valid (min 5): ${lengthChecker("hello")}`) stdout.println(`"hi" valid (min 5): ${lengthChecker("hi")}`) // === INLINE SINGLE-EXPRESSION FORM === notEmpty <- () is Validator as pure (valid:=? length text > 0) stdout.println(`"" not empty: ${notEmpty("")}`) stdout.println(`"x" not empty: ${notEmpty("x")}`) // === MULTIPLE LOCAL HELPERS WITH DIFFERENT CAPTURES === shortLen <- 3 longLen <- 10 shortChecker <- (shortLen) is Validator as pure (valid:=? length text >= shortLen) longChecker <- (longLen) is Validator as pure (valid:=? length text >= longLen) testWord <- "medium" stdout.println(`"${testWord}" short enough: ${shortChecker(testWord)}`) stdout.println(`"${testWord}" long enough: ${longChecker(testWord)}`) // === POLYMORPHIC: all are Validator type === validators <- [NotBlankValidator, notEmpty, lengthChecker, shortChecker, longChecker] for validator in validators stdout.println(`validates "test": ${validator("test")}`)
Common mistakes
E05150 — When the abstract function is declared as pure, all implementations must also be pure. Omitting 'as pure' triggers E05150 because 'pure' in super requires 'pure' for this definition. See ek9 -h E05150 for details.
Incorrect:
NotBlankValidator is Validator -> text as String <- valid as Boolean: length text > 0
Correct:
NotBlankValidator is Validator as pure -> text as String <- valid as Boolean: length text > 0
E50060 — Boolean has no toString() method in EK9. Use the $ prefix operator or string interpolation. See ek9 -h E50060 for details.
Incorrect:
stdout.println(lengthChecker("hello").toString())
Correct:
stdout.println(`"hello" valid (min 5): ${lengthChecker("hello")}`)
Other ways to ask this
- Can I define a function inside another function in EK9?
- What replaces nested functions or local functions in EK9?
- How do I create helper functions scoped to another function?
Coming from another language?
Java: no nested methods (until Java 22 unnamed classes), inner classes as workaround, lambdas for inline behaviour. Python: def inside def creates nested functions, closures capture by reference (late-binding bug). JavaScript: function inside function creates closures, common pattern but 'this' binding is error-prone. Rust: closures defined inline with |args| body, cannot define named fn inside fn (unstable), closures capture by reference or move. Go: anonymous functions inline, func() {} closures, capture by reference. Kotlin: local functions (fun inside fun) supported, closures capture mutable state. Swift: nested functions supported, closures for inline behaviour. EK9: no nested function definitions, dynamic functions with explicit capture serve the same purpose, scoped to enclosing function, polymorphic through abstract function types.
Keywords: function, capture, anonymous, dynamic, parameter, inline, nested, method, helper, local, abstract, closure, scope