Question: How does the List type work in EK9?

What is the literal syntax for lists in EK9?

How do I add and remove items from a list in EK9?

Answer:
EK9 has a generic List type for ordered, indexable collections. Lists use square bracket literal syntax [1, 2, 3] and the generic declaration List of T.

LIST CREATION
Four ways to create lists:
  numbers <- [1, 2, 3, 4, 5]      literal syntax, compiler infers List of Integer
  emptyList <- List() of String    typed empty list
  single <- List(42)               single-element construction
  fruits <- [
    "apple",
    "banana"
    ]                              multi-line literal for readability

GENERIC SYNTAX
EK9 uses 'of' instead of angle brackets: List of String, List of Integer. No <> needed.

ADDING ELEMENTS
+= mutates the list in place:
  names += "Dave"             appends Dave
  names += ["Eve", "Frank"]   appends both
+ creates a new list (original unchanged):
  moreNames <- names + "Grace"

REMOVING ELEMENTS
-= mutates the list:
  fruits -= "banana"          removes banana
- creates a new list without the element:
  without <- fruits - "banana"

ACCESSING ELEMENTS
Safe element access with getOrDefault:
  first <- numbers.getOrDefault(0, 0)                   first element
  last <- numbers.getOrDefault(length numbers - 1, 0)   last element
getOrDefault always returns a value: either the element at the index, or the provided default. No exceptions, no unset results.

LENGTH AND EMPTY CHECK
  len <- length numbers        number of elements
  isEmpty <- numbers is empty  true if no elements

ITERATION
  for fruit in fruits
    stdout.println(fruit)

EMPTY LIST IS SET
CRITICAL: An empty list IS set. Creating List() of String gives you a valid, set, empty list. Empty is not the same as unset.

See Q88 for operations (contains, reverse, copy, merge, comparison). See Q89 for stream pipelines (filter, map, collect). See Q37 for string interpolation with lists. See Q39 for integers used in list indexing. See Q120 for sorting lists. See Q125 for head/tail/skip to limit streams. See Q130 for mutating vs non-mutating operators. See Q131 for Python comprehension equivalents. See Q180 for list add. See Q183 for list first and last.

defines module qa.list

  defines function

    isEven() as pure
      -> num as Integer
      <- rtn as Boolean: num mod 2 == 0

    intToString() as pure
      -> num as Integer
      <- rtn as String: $num

  defines program
    ListDemo()
      stdout <- Stdout()

      // === LIST CREATION ===

      // Literal syntax — compiler infers List of Integer
      numbers <- [1, 2, 3, 4, 5]
      stdout.println(`Numbers: ${numbers}`)

      // Typed empty list
      emptyList <- List() of String
      stdout.println(`Empty: ${emptyList}`)

      // Single-element construction
      single <- List(42)
      stdout.println(`Single: ${single}`)

      // Multi-line literal
      fruits <- [
        "apple",
        "banana",
        "cherry"
        ]
      stdout.println(`Fruits: ${fruits}`)

      // === ADDING ELEMENTS ===

      names <- List() of String
      names += "Alice"
      names += "Bob"
      names += "Charlie"
      stdout.println(`After +=: ${names}`)

      // + creates a new list (original unchanged)
      moreNames <- names + "Dave"
      stdout.println(`Original: ${names}`)
      stdout.println(`With Dave: ${moreNames}`)

      // === REMOVING ELEMENTS ===

      without <- fruits - "banana"
      stdout.println(`Without banana: ${without}`)

      fruits -= "cherry"
      stdout.println(`After -=: ${fruits}`)

      // === ACCESSING ELEMENTS ===

      first <- numbers.getOrDefault(0, 0)
      last <- numbers.getOrDefault(length numbers - 1, 0)
      stdout.println(`First: ${first}, Last: ${last}`)

      third <- numbers.getOrDefault(2, 0)
      stdout.println(`Index 2: ${third}`)

      // === LENGTH AND EMPTY ===

      lenNumbers <- length numbers
      stdout.println(`Length: ${lenNumbers}`)

      numbersEmpty <- numbers is empty
      stdout.println(`Numbers empty: ${numbersEmpty}`)

      listEmpty <- emptyList is empty
      stdout.println(`Empty list empty: ${listEmpty}`)

      // === BASIC ITERATION ===

      for fruit in fruits
        stdout.println(`Fruit: ${fruit}`)

      // === EMPTY LIST IS SET ===

      require emptyList?
      require emptyList is empty
      stdout.println(`Empty list isSet: ${emptyList?}`)

Java: ArrayList<T> with verbose generics, no literal syntax (List.of() since Java 9 but immutable). Python: built-in list with [] literal, no static type safety. JavaScript: Array with [] literal, no type safety. Rust: Vec<T> with vec![] macro. Go: slices with make()/append(), no generics until Go 1.18. Kotlin: listOf()/mutableListOf(). EK9: [1, 2, 3] literal, List of T generic (no angle brackets), getOrDefault for safe access, += and + for adding, -= and - for removing.

add array beginner collection empty first for generic getOrDefault intro iterate length list literal migrate remove start

Explanation: Generic types like List only take the number of parameters that actually parameterize them

Explanation: The generic type must be parameterized with a type


Question: How does the Dict type work in EK9?

How do I create and use dictionaries in EK9?

How do I look up values safely in an EK9 Dict?

How do I iterate over dictionary entries in EK9?

Answer:
EK9 has a generic Dict type for key-value mappings. Dicts use curly brace literal syntax {key: value} and the generic declaration Dict of (K, V).

DICT CREATION
Three ways to create dicts:

1. Literal syntax (compiler infers the types):
  ages <- {"Alice": 30, "Bob": 25, "Charlie": 35}
The compiler infers Dict of (String, Integer) from the literal contents.

2. Empty typed dict:
  scores <- Dict() of (String, Integer)
You must specify both key and value types when creating an empty dict because there are no entries to infer from.

3. Single-entry construction:
  single <- Dict("Alice", 30)
Creates a Dict of (String, Integer) with one entry.

TWO-PARAMETER GENERIC SYNTAX
Dict requires two type parameters in parentheses: Dict of (K, V). Compare with List which uses a single parameter: List of T. The parentheses are required for multi-parameter generics.

DICTENTRY BASICS
DictEntry of (K, V) represents a single key-value pair. Create with DictEntry(key, value). Access the key with .key() and the value with .value(). DictEntry is the unit of insertion and is yielded when iterating over a Dict.

ADDING ENTRIES
+= mutates the dict in place:
  ages += DictEntry("Dave", 28)
+ creates a new dict (original unchanged):
  moreAges <- ages + DictEntry("Eve", 32)

REMOVING ENTRIES
-= removes by key:
  ages -= "Bob"

SAFE VALUE ACCESS
.getOrDefault(key, default) returns the default if the key is missing:
  aliceAge <- ages.getOrDefault("Alice", 0)       returns 30
  missing <- ages.getOrDefault("Unknown", 0)      returns 0
Always returns a usable value. No exceptions, no unset results.

LENGTH AND EMPTY CHECK
  len <- length ages              number of entries
  isEmpty <- ages is empty        true if no entries

ITERATION
A for loop over a Dict yields DictEntry values:
  for entry in ages
    stdout.println(`${entry.key()}: ${entry.value()}`)
You can also use a helper function to format each entry.

See Q90 (What operations does Dict support?) for contains, keys/values iterators, merging, copy, comparison, JSON, and stream pipelines. See Q45 (How does the List type work?) for the sister collection type. See Q29 (How do unset variables work?) for tri-state semantics and why empty dicts are set. See Q129 for safe access patterns with missing keys. See Q126 for choosing the right collection type. See Q167 for dict iteration.

See Q260 for Dict key type requirements and using custom types as keys.

Use 'ek9 -h Dict' and 'ek9 -h DictEntry' to see the full API.

defines module qa.dict

  defines function

    entryToString() as pure
      -> entry as DictEntry of (String, Integer)
      <- rtn as String: `${entry.key()}: ${entry.value()}`

  defines program
    DictDemo()
      stdout <- Stdout()

      // === DICT CREATION ===

      // Literal syntax — compiler infers Dict of (String, Integer)
      ages <- {"Alice": 30, "Bob": 25, "Charlie": 35}
      stdout.println(`Ages: ${ages}`)

      // Typed empty dict
      scores <- Dict() of (String, Integer)
      stdout.println(`Empty: ${scores}`)

      // Single-entry construction
      single <- Dict("Alice", 30)
      stdout.println(`Single: ${single}`)

      // === DICTENTRY BASICS ===

      entry <- DictEntry("Dave", 28)
      stdout.println(`Entry key: ${entry.key()}, value: ${entry.value()}`)

      // === ADDING ENTRIES ===

      // += mutates the dict
      ages += DictEntry("Dave", 28)
      stdout.println(`After += Dave: ${ages}`)

      // + creates a new dict (original unchanged)
      moreAges <- ages + DictEntry("Eve", 32)
      stdout.println(`Original: ${ages}`)
      stdout.println(`With Eve: ${moreAges}`)

      // === REMOVING ENTRIES ===

      // -= removes by key
      ages -= "Bob"
      stdout.println(`After -= Bob: ${ages}`)

      // === SAFE VALUE ACCESS ===

      aliceAge <- ages.getOrDefault("Alice", 0)
      stdout.println(`Alice age: ${aliceAge}`)

      missingAge <- ages.getOrDefault("Unknown", 0)
      stdout.println(`Missing age (default 0): ${missingAge}`)

      // === LENGTH AND EMPTY CHECK ===

      lenAges <- length ages
      stdout.println(`Length: ${lenAges}`)

      agesEmpty <- ages is empty
      stdout.println(`Ages empty: ${agesEmpty}`)

      scoresEmpty <- scores is empty
      stdout.println(`Scores empty: ${scoresEmpty}`)

      // === ITERATION ===

      // For loop yields DictEntry
      for ageEntry in ages
        stdout.println(`${ageEntry.key()} -> ${ageEntry.value()}`)

      // Using a helper function for formatting
      for ageEntry in ages
        stdout.println(entryToString(ageEntry))

Java: HashMap with .get() returning null (NPE risk), no literal syntax. Python: built-in dict with {} literal, .get(key, default). JavaScript: Object/Map, optional chaining. Rust: HashMap, .get() returns Option. Go: map with comma-ok idiom, nil map panics on write. Kotlin: mapOf()/mutableMapOf(), closest in convenience. EK9: {key: value} literal, Dict of (K, V), .getOrDefault() always returns a value, DictEntry for iteration.

DictEntry add beginner dict dictionary entry first generic getOrDefault intro iterate key literal lookup map remove start

scores <- HashMap() of (String, Integer)

Explanation: EK9 uses 'Dict' not 'HashMap' or 'Map'. There is no HashMap type in EK9. See ek9 -h E50010 for details.

Explanation: Dict is a generic type requiring two type parameters. An empty Dict() without 'of (K, V)' cannot be resolved. See ek9 -h E06010 for details.

Explanation: EK9 Dict does not have a '.get()' method. Use '.getOrDefault(key, default)' which always returns a usable value. See ek9 -h E50060 for details.

Explanation: EK9 uses 'length' as a prefix operator, not Python's 'len()' function. Write 'length ages' not 'len(ages)'. See ek9 -h E50001 for details.


Question: How does the Optional type work in EK9?

How do I handle missing values safely in EK9?

How does EK9 prevent null pointer exceptions?

What is the EK9 equivalent of Rust Option?

Answer:
EK9 has a generic Optional type that replaces null with compiler-enforced safety. The compiler tracks whether the '?' (isSet) operator has been called and REFUSES to compile code that accesses an Optional without a proper guard. There are no escape hatches: no .unwrap(), no !!, no force-unwrap.

CREATING OPTIONALS
Two forms:
  maybe <- Optional() of Integer      empty, must specify type
  item <- Optional(42)                with value, type inferred

CHECKING WITH ?
The ? operator checks if an Optional has a value:
  if item?
    value <- item.get()               safe: ? in same if block
The verbose form 'item is not empty' also works but ? is preferred.

DECLARATION GUARD
Combine creation with safety check in one expression:
  if o <- getOptional()
    value <- o.get()                  safe: declaration implies ? check
The variable 'o' only exists inside the if block. If unset, the block is skipped entirely.

GET OR DEFAULT
Extract the value with a fallback:
  value <- o.getOrDefault("default")
Returns the contained value if set, otherwise returns the default. No guard needed.

THINK EK9 — NOT JAVA, NOT RUST
  WRONG (Java):    if (opt.isPresent()) opt.get()
  WRONG (Kotlin):  opt?.let { use(it) } ?: default()
  WRONG (Rust):    match opt { Some(v) => v, None => default }
  RIGHT (EK9):     if opt? then use(opt.get())

See Q84 for Optional operations (comparison, copy, merge, ternary guard). See Q85 for Optional in stream pipelines (flatten, collect). See Q74-Q78 for guard patterns in all flow controls (if, switch, for, while, try). See Q48 for the Result type (success-or-error container). See Q29 for tri-state semantics. See Q54 for Consumer and Acceptor callbacks. See Q126 for choosing the right collection type (List, Dict, Optional, Result, PriorityQueue). See Q198 for built-in generics. See Q243 for coalescing operators (??, ?:) that work with Optional values.

Use 'ek9 -h Optional' to see the full API. See Q251 for fixing unset variable compiler errors. See Q316 for how discarded Optional returns are compiler errors. See Q634 for guard-based safe access. See Q636 for chained guard access. See Q747 for migrating Swift optional binding chains to EK9.

defines module qa.optional

  defines function

    <?-
      Returns an Optional with a value.
    -?>
    getOptional()
      <- rtn <- Optional("Steve")

    <?-
      Returns an empty Optional.
    -?>
    getEmptyOptional()
      <- rtn <- Optional() of String

  defines program
    OptionalBasics()
      stdout <- Stdout()

      // === CREATING OPTIONALS ===

      // Empty — must specify type
      maybe <- Optional() of Integer
      stdout.println(`Empty isSet: ${maybe?}`)

      // With value — type inferred
      item <- Optional(42)
      stdout.println(`Item: ${item}, isSet: ${item?}`)

      // === DECLARATION GUARD ===

      // Preferred: combines creation + safety check
      if o <- getOptional()
        extracted <- o.get()
        stdout.println(`Guard: ${extracted}`)

      // Empty Optional — guard block not entered
      if empty <- getEmptyOptional()
        stdout.println("Should not print")

      // Explicit ? check form
      o2 <- getOptional()
      if o2?
        checked <- o2.get()
        stdout.println(`Checked: ${checked}`)

      // === GET OR DEFAULT ===

      name <- getOptional()
      assured <- name.getOrDefault("Default")
      stdout.println(`GetOrDefault (set): ${assured}`)

      emptyName <- getEmptyOptional()
      defaulted <- emptyName.getOrDefault("Fallback")
      stdout.println(`GetOrDefault (empty): ${defaulted}`)

Java: Optional<T> with .get() that throws NoSuchElementException if empty, .isPresent() check not enforced by compiler, .orElse() for defaults. Python: no Optional type, uses None with 'is None' checks, no compile-time safety. Rust: Option<T> with .unwrap() that panics if None, pattern matching for safe access, compiler enforces match exhaustiveness but allows .unwrap() escape hatch. Go: no Optional, uses comma-ok idiom and nil checks. Kotlin: nullable types T? with safe call ?. and Elvis ?:, !! force-unwrap throws NPE. Swift: T? optional with if let/guard let for safe unwrapping, ?? nil coalescing for defaults, ! force-unwrap crashes at runtime (escape hatch EK9 does not allow), optional chaining with ?. for method calls. EK9: Optional of T with compiler-enforced guard patterns, .get() requires ? check in same control structure, no escape hatches, ZERO runtime crashes from Optional misuse.

absent beginner compiler empty enforce first get getOrDefault guard intro isSet isset migrate nil none npe null null-safe nullable optional replace safe safety set start swift unset

Explanation: Calling .get() on an Optional without first checking with ? triggers E08030 — has not been checked before access. The compiler enforces guard-before-access with no escape hatches. See ek9 -h E08030 for details.

getOptional()
      assured <- getOptional().getOrDefault("Default")

Explanation: Calling a function that returns Optional but discarding the result triggers E11052 — Result or Optional result is discarded and must always be checked. You must always handle the return value. See ek9 -h E11052 for details.


Question: How does the Result type work in EK9?

How do I handle success and error values in EK9?

What is the difference between Result and Optional in EK9?

How do I safely access ok and error values from a Result?

How do I handle errors without exceptions in EK9?

What do I use instead of try-catch in EK9?

How do I handle a function that might fail in EK9?

Answer:
Result of (O, E) is a generic type for operations producing a success value, error value, or BOTH. Unlike Rust's either/or Result, EK9's can hold both simultaneously.

FOUR POSSIBLE STATES
1. Neither ok nor error: Result() of (String, Integer)
2. Ok only: Result("Steve", Integer())
3. Error only: Result(String(), -1)
4. Both ok AND error: Result("Default", -1)
The fourth state handles cases like a failed config lookup that still returns a fallback with an error code.

RESULT CREATION
  r1 <- Result("Steve", Integer())            two-arg, ok only
  r2 <- Result() of (String, Integer)          empty
  r3 <- (Result() of (String, Integer)).asOk("Steve")   factory
  r4 <- (Result() of (String, Integer)).asError(-1)      factory

CHECKING STATE
  if r?                checks isOk
  if r.isOk()          explicit ok check
  if r.isError()       explicit error check
  r is empty           neither ok nor error
isOk() and isError() are INDEPENDENT — both can be true.

BASIC GUARD PATTERNS
Compiler enforces guard-before-access:
  if r <- getResult()
    okData <- r.ok()
  if r.isError()
    errData <- r.error()

OK OR DEFAULT / ERROR OR DEFAULT
  assured <- r.okOrDefault("fallback")
  errVal <- r.errorOrDefault(0)

See Q86 for advanced guard patterns. See Q87 for Result operations (whenOk/whenError, merge, copy, comparison, iterator, contains). See Q47 for Optional. See Q29 for tri-state semantics. See Q54 for Consumer/Acceptor. See Q126 for choosing collection types.

Use 'ek9 -h Result' for the full API.

See Q139 for try/catch vs Result. See Q243 for coalescing operators. See Q251 for unset variable errors. See Q259 for four-state Result design. See Q316 for discarded Result errors.

defines module qa.result

  defines function

    <?-
      Returns a Result with ok value set.
    -?>
    getOkResult()
      <- rtn <- Result("Steve", Integer())

    <?-
      Returns a Result with error value set.
    -?>
    getErrorResult()
      <- rtn <- Result(String(), -1)

    <?-
      Returns a Result with both ok and error set.
    -?>
    getBothResult()
      <- rtn <- Result("Default", -1)

    <?-
      Returns an empty Result.
    -?>
    getEmptyResult()
      <- rtn <- Result() of (String, Integer)

  defines program
    ResultBasics()
      stdout <- Stdout()

      // === FOUR STATES ===

      okResult <- Result("Steve", Integer())
      errorResult <- Result(String(), -1)
      bothResult <- Result("Default", -1)
      emptyResult <- Result() of (String, Integer)

      stdout.println(`Ok result: ${okResult}`)
      stdout.println(`Error result: ${errorResult}`)
      stdout.println(`Both result: ${bothResult}`)
      stdout.println(`Empty result: ${emptyResult}`)

      // === CHECKING STATE ===

      // ? checks isOk, isOk/isError are independent
      stdout.println(`okResult?: ${okResult?}, isOk: ${okResult.isOk()}, isError: ${okResult.isError()}`)
      stdout.println(`errorResult?: ${errorResult?}, isOk: ${errorResult.isOk()}, isError: ${errorResult.isError()}`)
      stdout.println(`bothResult?: ${bothResult?}, isOk: ${bothResult.isOk()}, isError: ${bothResult.isError()}`)
      stdout.println(`emptyResult is empty: ${emptyResult is empty}`)

      // === CREATION WITH FACTORY METHODS ===

      factoryOk <- (Result() of (String, Integer)).asOk("Alice")
      factoryErr <- (Result() of (String, Integer)).asError(99)
      stdout.println(`Factory ok: ${factoryOk}`)
      stdout.println(`Factory error: ${factoryErr}`)

      // === BASIC GUARD PATTERNS ===

      // Declaration guard — ? check implied
      if r <- getOkResult()
        okData <- r.ok()
        stdout.println(`Declaration guard: ${okData}`)

      // Explicit isOk check
      r1 <- getOkResult()
      if r1.isOk()
        stdout.println(`isOk guard: ${r1.ok()}`)

      // Explicit isError check
      r2 <- getErrorResult()
      if r2.isError()
        stdout.println(`isError guard: ${r2.error()}`)

      // Dual guard — both ok and error
      r3 <- getBothResult()
      if r3.isOk()
        stdout.println(`Both ok side: ${r3.ok()}`)
      if r3.isError()
        stdout.println(`Both error side: ${r3.error()}`)

      // Empty result — neither guard enters
      if r4 <- getEmptyResult()
        stdout.println("Should not print")

      // === OK OR DEFAULT / ERROR OR DEFAULT ===

      r5 <- getOkResult()
      assured <- r5.okOrDefault("Fallback")
      stdout.println(`okOrDefault (set): ${assured}`)

      r6 <- getEmptyResult()
      defaulted <- r6.okOrDefault("Fallback")
      stdout.println(`okOrDefault (empty): ${defaulted}`)

      r7 <- getErrorResult()
      errVal <- r7.errorOrDefault(0)
      stdout.println(`errorOrDefault (set): ${errVal}`)

      r8 <- getEmptyResult()
      errDefault <- r8.errorOrDefault(0)
      stdout.println(`errorOrDefault (empty): ${errDefault}`)

Java: no Result, try-catch. Rust: Result<T,E> either/or only, .unwrap() panics. Go: (value, error) tuple, no enforcement. Kotlin/Swift: Result exists but no compiler-enforced access. EK9: four states (both ok AND error), compiler-enforced guards, okOrDefault/errorOrDefault.

beginner catch compiler error errorOrDefault exception fail failure first guard handle intro isError isOk isset migrate null-safe ok okOrDefault result safe start success swift try type

Explanation: Calling .ok() on a Result without first checking isOk() triggers E08030 — has not been checked before access. The compiler enforces guard-before-access for both .ok() and .error(). See ek9 -h E08030 for details.

Explanation: Result requires two different types for ok and error values. Using the same type for both triggers E06190 — Result must be used with two different types. This ensures the compiler can distinguish ok from error. See ek9 -h E06190 for details.

getOkResult()
      assured <- getOkResult().okOrDefault("Fallback")

Explanation: Calling a function that returns Result but discarding the result triggers E11052 — Result or Optional result is discarded and must always be checked. Always assign and check Results. See ek9 -h E11052 for details.


Question: How do I define a function in EK9?

How do EK9 functions differ from functions in other languages?

How do I create a named function in EK9?

Answer:
EK9 functions are fundamentally different from functions in other languages because they are first-class TYPES, not just callable code blocks. Every function in EK9 is a nominal type with identity, and functions can participate in type hierarchies via 'is' and 'extends'. This makes them far more powerful than functions in Java, Python, Go, or even Rust.

Functions are defined inside a 'defines function' block within a module. Parameters use '->' for inputs and '<-' for the named return value:
  greet()
    -> name as String
    <- message as String: "Hello, " + name

Multiple parameters use block style (indented under '->'):
  add() as pure
    ->
      a as Integer
      b as Integer
    <- result as Integer: a + b

There is NO return statement in EK9. Instead, you declare a named return variable with '<-'. The compiler ensures all code paths initialise that variable. If you provide a default value (': a + b'), the function body is optional. See Q50 for the full power of named returns.

When computation requires multiple steps, add a body after the return declaration:
  clamp() as pure
    ->
      number as Integer
      min as Integer
      max as Integer
    <- result as Integer: number
    if number < min
      result: min
    else if number > max
      result: max

Because functions are types, they can be stored in variables, passed as parameters, returned from other functions, and collected in Lists. This enables patterns that require interfaces or abstract classes in other languages. See Q51 for abstract functions, Q52 for dynamic functions, Q54 for pure functions and the Consumer/Acceptor distinction, Q55 for delegates, and Q56 for higher-order functions.

EK9 also enforces quality limits on ALL functions at compile time: cyclomatic complexity must be less than 11, nesting depth less than 4, statement count less than 20, and variable names must be descriptive. These limits cannot be disabled and apply equally to all function types.

See Q93 for defining classes. See Q106 for traits. See Q256 for the Void type when functions return nothing. See Q294 for function naming conventions.

Use 'ek9 -h function' to see the full syntax reference. See Q319 for unused parameter detection. See Q588 for function extension and type hierarchies. See Q596 for function vs method distinction. See Q597 for function parameter patterns in depth.

defines module qa.function

  defines function

    greet() as pure
      -> name as String
      <- message as String: "Hello, " + name

    add() as pure
      ->
        a as Integer
        b as Integer
      <- result as Integer: a + b

    clamp() as pure
      ->
        number as Integer
        min as Integer
        max as Integer
      <- result as Integer: number
      if number < min
        result: min
      else if number > max
        result: max

    factorial() as pure
      -> n as Integer
      <- result as Integer: 1
      for i in 1 ... n
        result: result * i

  defines program

    FunctionDemo()
      stdout <- Stdout()

      // Single-parameter function call
      stdout.println(greet("World"))

      // Multi-parameter function call
      stdout.println(`add(3, 4): ${add(3, 4)}`)

      // Function with body logic
      stdout.println(`clamp(15, 0, 10): ${clamp(15, 0, 10)}`)
      stdout.println(`clamp(-5, 0, 10): ${clamp(-5, 0, 10)}`)
      stdout.println(`clamp(7, 0, 10): ${clamp(7, 0, 10)}`)

      // Pure function
      stdout.println(`factorial(5): ${factorial(5)}`)

Java: methods live inside classes, no standalone functions, static methods are a workaround, functional interfaces require SAM conversion, no compile-time quality enforcement. Python: def creates functions but they have no type contract, no compile-time parameter type checking, no quality enforcement. JavaScript: function declarations and arrow functions have no type safety, no quality enforcement, 'this' binding is confusing. Rust: fn defines functions with strong typing but functions are not types in the OOP sense, no compile-time quality limits. Go: func defines functions, first-class values but no type hierarchies, no purity enforcement, no quality limits. C#: methods inside classes, delegates are separate concept, no quality enforcement. Kotlin: fun defines functions, similar to Java methods, no standalone function types, no quality enforcement. Swift: func defines functions, closures are structural not nominal, no quality enforcement. EK9: functions ARE types with identity and inheritance hierarchies, compile-time quality enforcement on all functions, no return statement needed, named return variables ensure all paths initialise.

beginner block complexity define first function immutable intro migrate named parameter pure quality return side-effect start type

function
define function
create function

Explanation: The function 'add' takes exactly two Integer parameters. Passing three arguments triggers E06270 — parameter mismatch. EK9 does not support varargs or default parameters. See ek9 -h E06270 for details.

Explanation: A typo in the function name 'gret' means the compiler cannot find any identifier with that name. EK9 is case-sensitive and requires exact spelling. See ek9 -h E50001 for details.

add() as pure
      ->
        a as Integer
        b as Integer
      <- result as Integer: a + b

    add() as pure
      ->
        a as Integer
        b as Integer
        c as Integer
      <- result as Integer: a + b + c

Explanation: EK9 does not support function overloading. Defining two functions with the same name but different parameters triggers E01030. Use distinct names like 'add2' and 'add3', or use a single function with a List parameter. See ek9 -h E01030 for details.

<- result as Integer: number
      return result

Explanation: EK9 does not have a 'return' statement. Declare the return variable with '<-' and the compiler ensures all code paths initialise it. See ek9 -h E01072 for details.

Oracle can generate a function with parameters and return value declaration.


Question: How do function returns work without a return statement in EK9?

Why does EK9 not have a return statement?

What does the question mark mean on a function return declaration?

How does the guarded assignment operator work with function returns?

Answer:
EK9 deliberately has NO return statement. Instead, you declare a named return variable with '<-' and the compiler ensures all code paths initialise it. This eliminates an entire class of bugs where code paths miss a return.

INITIALISED RETURNS
The simplest pattern provides a default value at declaration:
  add() as pure
    -> a as Integer, b as Integer
    <- result as Integer: a + b
Here 'result' is initialised to 'a + b' and the function needs no body.

RETURNS WITH BODY
When you need conditional logic, declare a default then modify:
  clamp() as pure
    -> number as Integer, min as Integer, max as Integer
    <- result as Integer: number
    if number < min
      result: min
    else if number > max
      result: max

UNINITIALISED RETURNS — THE '?' SUFFIX
The '?' on '<- result as Float?' does NOT mean Optional. It means the return variable is declared but NOT initialised at declaration. It WILL be assigned during the function body. This is essential for pure functions where you want to use ':=?' (guarded assignment) across different conditional branches:
  ratingPercentage() as pure
    -> percentage as Float
    <- rtn as String: given percentage
      <- rating as String: String()
      when <= 0.5
        rating:=? "Low"
      when <= 0.8
        rating:=? "Medium"
      default
        rating:=? "High"

The ':=?' operator assigns ONLY if the target is unset. Each branch sets the value exactly once. If a branch accidentally executed twice, ':=?' would fail rather than silently overwrite. This guarantees single-assignment semantics in pure functions.

UNSET RETURN AS SIGNAL
Another powerful pattern uses an unset return to signal 'not found':
  findIndex()
    -> items as List of Integer, target as Integer
    <- index as Integer: Integer()
    low <- 0
    high <- length items - 1
    while low <= high and ~index?
      mid <- (low + high) / 2
      checkValue <- items.getOrDefault(mid, 0)
      if checkValue == target
        index: mid
      else if checkValue < target
        low: mid + 1
      else
        high: mid - 1
The return 'index' starts as unset (Integer() creates an unset Integer). The caller checks 'if result?' to see if a value was found. The '~index?' in the while condition means 'index is not set' — the loop exits when found.

No return statement means no early exits, no forgotten returns, and no unreachable code. The compiler verifies every path. See Q29 for unset variable semantics, Q51 for abstract function implementations that use ':=?' patterns, and Q54 for how purity connects to ':=?'. See Q93 for class methods which use the same return patterns. See Q256 for the Void type and implicit returns. See Q274 for why AI generates return statements. See Q289 for converting multiple returns to EK9. See Q316 for how EK9 detects discarded return values.

defines module qa.functionreturns

  defines function

    add() as pure
      ->
        a as Integer
        b as Integer
      <- result as Integer: a + b

    clamp() as pure
      ->
        number as Integer
        min as Integer
        max as Integer
      <- result as Integer: number
      if number < min
        result: min
      else if number > max
        result: max

    rate() as pure
      -> score as Integer
      <- rating as String?
      premiumThreshold <- 80
      standardThreshold <- 60
      if score >= premiumThreshold
        rating:=? "Excellent"
      else if score >= standardThreshold
        rating:=? "Good"
      else
        rating:=? "Needs work"

    findIndex()
      ->
        items as List of Integer
        target as Integer
      <- index as Integer: Integer()
      low <- 0
      high <- length items - 1
      while low <= high and ~index?
        mid <- (low + high) / 2
        checkValue <- items.getOrDefault(mid, 0)
        if checkValue == target
          index: mid
        else if checkValue < target
          low: mid + 1
        else
          high: mid - 1

  defines program

    ReturnDemo()
      stdout <- Stdout()

      // Initialised return — no body needed
      stdout.println(`add(3, 4): ${add(3, 4)}`)

      // Return with body — default modified conditionally
      stdout.println(`clamp(15, 0, 10): ${clamp(15, 0, 10)}`)

      // Uninitialised return with :=? guarded assignment
      stdout.println(`rate(85): ${rate(85)}`)
      stdout.println(`rate(70): ${rate(70)}`)
      stdout.println(`rate(40): ${rate(40)}`)

      // Unset return as signal — findIndex
      numbers <- [1, 3, 5, 7, 9, 11]
      found <- findIndex(numbers, 7)
      if found?
        stdout.println(`Found 7 at index: ${found}`)

      notFound <- findIndex(numbers, 4)
      if ~notFound?
        stdout.println("4 not found in list")

Java: return statement required, easy to forget on some paths, compiler warns but does not always catch all cases, no guarded assignment concept. Python: return statement, implicit None return if omitted, no compile-time path analysis. JavaScript: return statement, undefined if omitted, no compile-time checking. Rust: last expression is implicit return OR explicit return keyword, no named return variable, no guarded assignment. Go: named returns exist but return statement still required, bare return uses named values but is considered bad practice. C#: return statement required, no guarded assignment. Kotlin: return statement or last expression, no named return variable, no guarded assignment. Swift: return statement or implicit single-expression return, no named return, no guarded assignment. EK9: NO return statement at all, named return variable with '<-', compiler verifies all paths initialise, '?' suffix for uninitialised declaration, ':=?' guarded assignment ensures single assignment in pure functions, unset return as signal pattern eliminates sentinel values.

assignment beginner first function guarded immutable intro isset mark named null-safe path pure question return safe side-effect signal start statement uninitialised variable

function return
return value
named return

<- rating as String?
      if score >= 80
        rating:=? "Excellent"
      else if score >= 60
        rating:=? "Good"

Explanation: If the return variable is uninitialised (? suffix) the compiler requires all paths to assign it. Missing the else branch means 'rating' could remain uninitialised. See ek9 -h E11064 for details.

Explanation: A String literal cannot initialise an Integer return variable. EK9 is strongly typed and requires compatible types in assignments. See ek9 -h E50030 for details.


Question: What are abstract functions in EK9?

How do abstract functions work as standalone types in EK9?

How do I implement an abstract function in EK9?

How do I use function polymorphism in EK9?

Answer:
Abstract functions in EK9 are STANDALONE TYPES that define a callable contract. This is unique to EK9 — in every other mainstream language, abstract behaviour requires classes or interfaces. In EK9, a function can be abstract on its own:
  mathOperation() as pure abstract
    -> x as Float, y as Float
    <- result as Float?

This declares a TYPE called 'mathOperation'. Any function that says 'is mathOperation' or 'extends mathOperation' becomes a concrete implementation of that type.

IMPLEMENTING WITH 'is' OR 'extends'
Named functions implement an abstract function using 'is' or 'extends' (both are equivalent for functions):
  addOp() is mathOperation as pure
    -> x as Float, y as Float
    <- result as Float: x + y

  subtractOp() is mathOperation as pure
    -> x as Float, y as Float
    <- result as Float: x - y

FUNCTION POLYMORPHISM
Because abstract functions are types, you get true polymorphism. Store implementations in a list and iterate:
  for op in [addOp, subtractOp, multiplyOp]
    stdout.println(`Result: ${op(10.0, 3.0)}`)

This is polymorphic dispatch — each function in the list is called through the same abstract type. No interfaces, no classes, no boilerplate.

FUNCTION DELEGATES
Variables can hold function references via their abstract type:
  currentOp as mathOperation: addOp
  result <- currentOp(5.0, 3.0)
  currentOp: subtractOp
  result2 <- currentOp(5.0, 3.0)

Without parentheses, 'addOp' is a REFERENCE (delegate). With parentheses, 'addOp(5.0, 3.0)' is a CALL. This distinction is fundamental.

Why is this more powerful than other languages? In Java, you need a functional interface (a class-like construct) to achieve the same. In Python, functions are first-class but have no type contract. In Rust, closures use Fn traits (structural) but you cannot name closure types directly. In Go, function types exist but cannot form inheritance hierarchies. EK9 abstract functions give you type safety, polymorphism, and named types — all without classes.

See Q52 for dynamic functions that implement abstract functions inline. See Q55 for passing functions as delegates. See Q56 for higher-order functions that return delegates. See Q59 for using function delegates in stream pipelines. See Q89 for how abstract functions serve as stream pipeline stage types. See Q103 for abstract classes and methods. See Q235 for complete stream operations reference. See Q588 for function extension and type hierarchies. See Q589 for abstract function implementation patterns.

defines module qa.abstractfunction

  defines function

    mathOperation() as pure abstract
      ->
        x as Float
        y as Float
      <- result as Float?

    addOp() is mathOperation as pure
      ->
        x as Float
        y as Float
      <- result as Float: x + y

    subtractOp() is mathOperation as pure
      ->
        x as Float
        y as Float
      <- result as Float: x - y

    multiplyOp() is mathOperation as pure
      ->
        x as Float
        y as Float
      <- result as Float: x * y

  defines program

    AbstractFunctionDemo()
      stdout <- Stdout()

      // Polymorphic dispatch — iterate over implementations
      ops <- [addOp, subtractOp, multiplyOp]
      for op in ops
        stdout.println(`op(10.0, 3.0) = ${op(10.0, 3.0)}`)

      // Function delegate — variable holds a reference
      currentOp as mathOperation: addOp
      stdout.println(`delegate: ${currentOp(5.0, 3.0)}`)

      // Reassign to different implementation
      currentOp: multiplyOp
      stdout.println(`reassigned: ${currentOp(5.0, 3.0)}`)

Java: requires functional interfaces (SAM types) like Predicate<T> or Function<T,R> which are class-like constructs, not standalone function types, @FunctionalInterface annotation is documentation only. Python: no abstract function concept, abc.abstractmethod requires a class wrapper, functions have no type contract. JavaScript: no abstract functions, no type system for functions, TypeScript has callable interfaces but they are structural. Rust: Fn/FnMut/FnOnce traits are structural not nominal, cannot name closure types without impl Trait or dyn Trait, no standalone abstract function type. Go: type aliases for function signatures (type MathOp func(float64, float64) float64) but no inheritance hierarchy, no polymorphic dispatch. C#: delegates define callable types but require separate delegate declarations, not function inheritance. Kotlin: functional types are structural ((Int, Int) -> Int), fun interface for SAM but still requires class-like construct. Swift: closures are structural, protocol-based approach needed for nominal typing. EK9: abstract functions ARE standalone types with nominal identity, implementations use 'is' or 'extends' for true function type hierarchies, polymorphic dispatch through abstract function variables, no class wrapper needed.

abstract beginner call contract delegate extends first function handler implement intro is migrate polymorphism reference sealed standalone start type visitor

Explanation: Functions are closed by default. Extending a non-abstract, non-open function triggers E05030 — not open to be extended. Only abstract or open functions can be extended. See ek9 -h E05030 for details.


Question: What are dynamic functions and how do they differ from lambdas?

How do EK9 dynamic functions differ from lambdas in other languages?

Why does EK9 use dynamic functions instead of lambdas?

What is the inline dynamic function syntax in EK9?

Answer:
EK9 dynamic functions look superficially like lambdas but are fundamentally different in four critical ways. Understanding these differences is key to understanding EK9's power.

1. NOMINALLY TYPED
Every dynamic function MUST implement a named abstract function type. You cannot create an anonymous untyped lambda. This ensures every closure has a documented, compiler-verified contract:
  myAdd <- () is mathOperation as pure function
    result:=? x + y

In Java, a lambda like '(x, y) -> x + y' is structurally typed — the compiler guesses what interface it matches. In EK9, you explicitly declare 'is mathOperation', making the intent clear.

2. PARAMETER INFERENCE
Dynamic functions inherit parameter names from their abstract parent. You do NOT re-declare them. The abstract function 'mathOperation' declares parameters 'x' and 'y', so the dynamic function body can use 'x' and 'y' directly. This is DRY — the contract is defined once.

3. BLOCK vs INLINE SYNTAX
Block form uses 'as function' keyword with an indented body:
  myAdd <- () is mathOperation as pure function
    result:=? x + y

Inline form wraps the body in parentheses — this is the closest EK9 gets to a lambda:
  myAdd <- () is mathOperation as pure (result:=? x + y)

Inline is for single expressions. Multi-statement bodies require block form.

4. QUALITY ENFORCEMENT
Dynamic functions are subject to the SAME compile-time quality rules as named functions: cyclomatic complexity less than 11, nesting less than 4, descriptive variable names. In Java and Python, lambdas have zero quality enforcement — you can write arbitrarily complex, unreadable lambdas.

BOTH 'is' AND 'extends' WORK
Both keywords are equivalent for function implementation:
  using_is <- () is mathOperation as pure function
    result:=? x + y
  using_extends <- () extends mathOperation as pure
    result:=? x - y

WHY NOT ANONYMOUS LAMBDAS?
EK9 deliberately chose this design:
- READABILITY: Every dynamic function declares what type it implements
- TYPE SAFETY: Compiler verifies the signature match (not structural guessing)
- QUALITY: Same enforcement as named functions
- TRACEABLE: Data flow is explicit, not hidden

The result is that EK9 dynamic functions are more verbose than '(x, y) -> x + y' but dramatically safer, more readable, and maintainable. See Q53 for how variable capture works. See Q51 for abstract function types. See Q59 for using dynamic functions in stream pipelines. See Q89 for stream pipeline basics. See Q115 for dynamic classes. See Q235 for complete stream operations reference. See Q601 for how dynamic functions replace nested functions.

defines module qa.dynamicfunction

  defines function

    mathOperation() as pure abstract
      ->
        x as Float
        y as Float
      <- result as Float?

  defines program

    DynamicFunctionDemo()
      stdout <- Stdout()

      // Block form with 'as function'
      dynamicAdd <- () is mathOperation as pure function
        result:=? x + y

      stdout.println(`block add: ${dynamicAdd(7.0, 2.0)}`)

      // Block form with 'extends'
      dynamicSub <- () extends mathOperation as pure
        result:=? x - y

      stdout.println(`block sub: ${dynamicSub(7.0, 2.0)}`)

      // Inline form — closest EK9 gets to a lambda
      inlineMul <- () is mathOperation as pure (result:=? x * y)

      stdout.println(`inline mul: ${inlineMul(7.0, 2.0)}`)

      // Multiple inline functions in a list
      ops <- [
        () is mathOperation as pure (result:=? x + y),
        () is mathOperation as pure (result:=? x - y),
        () is mathOperation as pure (result:=? x * y)
      ]

      for op in ops
        stdout.println(`op(6.0, 3.0) = ${op(6.0, 3.0)}`)

Java: lambdas are structurally typed against functional interfaces, (x, y) -> x + y lets compiler guess the target type, no explicit type declaration at usage, lambda bodies can be arbitrarily complex with no quality enforcement, method references (::) are a separate syntax. Python: lambda limited to single expression, def functions have no type relationship, closures capture by reference with notorious late-binding bug. JavaScript: arrow functions ((x, y) => x + y) are structural, no type contract, no quality enforcement, 'this' binding confusion, mutable closure state is major bug source. Rust: closures are structurally typed via Fn/FnMut/FnOnce traits, cannot name closure types without impl Trait, no compile-time quality limits. Go: anonymous functions func(x, y float64) float64 are structural, no type hierarchies, no quality enforcement. Kotlin: lambdas { x, y -> x + y } are structural, fun interface for SAM but structural matching, no quality enforcement on lambdas. Swift: closures { (x: Double, y: Double) -> Double in x + y } are structural, no nominal typing for closures. EK9: dynamic functions are NOMINALLY typed (must implement named abstract function), parameters inherited from abstract parent (DRY), block and inline syntax, same compile-time quality enforcement as named functions.

abstract anonymous beginner block capture closure dynamic enforcement first function implement inference inline intro lambda migrate nominal parameter quality start structural swift typed

dynamic function
lambda
anonymous function

dynamicAdd <- () is mathOperation as pure function
        result += x

Explanation: If the abstract function is declared 'as pure', the dynamic function body cannot use mutation operators like +=. This triggers E08120 — mutating variables not allowed in pure scope. Use reassignment with :=? or : instead. See ek9 -h E08120 for details.


Question: How do closures and variable capture work in EK9?

How does EK9 capture variables in dynamic functions?

What is the difference between capture by value and capture by reference?

How do named capture parameters work in EK9?

Answer:
EK9 dynamic functions can capture variables from their enclosing scope, making them closures. But EK9's capture mechanism is fundamentally different from every other mainstream language in three ways.

1. EXPLICIT CAPTURE
Captured variables are listed explicitly in parentheses BEFORE the 'is' keyword:
  scaleFactor <- 10.0
  scaled <- (scaleFactor) is mathOperation as pure function
    result:=? x * scaleFactor + y

In Java, JavaScript, and Python, capture is AUTOMATIC and HIDDEN — any variable from the enclosing scope can be silently captured. This leads to accidental captures, unexpected dependencies, and hard-to-trace bugs. EK9 makes data flow visible.

2. CAPTURE BY VALUE
Captured variables are COPIED at creation time. The dynamic function gets its own independent copy. Changing the original variable after creation does NOT affect the captured value:
  factor <- 10.0
  scaled <- (factor) is mathOperation as pure function
    result:=? x * factor
  factor: 999.0
  scaled(3.0, 1.0)    //Still uses 10.0, not 999.0

This eliminates an entire class of bugs. In Java, closures capture by reference (must be effectively final as a workaround). In JavaScript and Python, closures capture by reference and CAN mutate — leading to the notorious Python late-binding bug and JavaScript loop-capture bugs.

3. NAMED CAPTURE PARAMETERS
Captures can have explicit values, making the function self-documenting:
  configured <- (factor: 2.0, offset: 5.0) is mathOperation as pure function
    result:=? x * factor + offset

The names and values are visible at the creation site. No need to look at surrounding code to understand what values the function uses.

MULTIPLE CAPTURES
Any number of variables can be captured:
  (min, max, label) is validator as function
    ...

CAPTURE CONSTRAINTS
- Variables must be named — literals cannot be captured directly
- Unused captures are a compiler error (E11018)
- Captured variables count toward coupling metrics
- The compiler tracks captures for quality enforcement

CAPTURE IN INLINE SYNTAX
Inline dynamic functions also support captures:
  biased <- (bias) is mathOperation as pure (result:=? x + y + bias)

See Q52 for dynamic function syntax. See Q57 for using captures to change class behaviour without subclassing. See Q58 for how captures enable stream pipeline integration. See Q59 for using captures in stream pipeline stages. See Q89 for stream pipeline basics. See Q115 for dynamic classes which use the same capture syntax. See Q235 for complete stream operations reference. See Q319 for unused closure capture detection (E11018). See Q601 for how dynamic functions with captures replace nested functions.

defines module qa.closurecapture

  defines function

    mathOperation() as pure abstract
      ->
        x as Float
        y as Float
      <- result as Float?

  defines program

    CaptureDemo()
      stdout <- Stdout()

      // Explicit capture by value
      factor <- 10.0
      scaled <- (factor) is mathOperation as pure function
        result:=? x * factor + y

      stdout.println(`captured: ${scaled(3.0, 1.0)}`)

      // Changing original does NOT affect the captured copy
      factor: 999.0
      stdout.println(`factor is now: ${factor}`)
      stdout.println(`after change: ${scaled(3.0, 1.0)}`)

      // Named capture parameters — self-documenting
      configured <- (multiplier: 2.0, offset: 5.0) is mathOperation as pure function
        result:=? x * multiplier + offset

      stdout.println(`named: ${configured(4.0, 0.0)}`)

      // Multiple captures
      base <- 100.0
      adjustment <- 0.1
      adjusted <- (base, adjustment) is mathOperation as pure function
        result:=? base + (x * adjustment) + y

      stdout.println(`multiple: ${adjusted(50.0, 3.0)}`)

      // Inline capture
      bias <- 10.0
      biased <- (bias) is mathOperation as pure (result:=? x + y + bias)
      stdout.println(`inline: ${biased(1.0, 2.0)}`)

Java: closures capture by reference, must be effectively final (workaround for mutable state bugs), no explicit capture list, accidental capture of large objects causes memory leaks, lambda capture is hidden and automatic. Python: closures capture by reference with notorious late-binding bug (loop variable captured by reference not value), nonlocal keyword for mutation, no explicit capture list, no compile-time capture validation. JavaScript: closures capture by reference, mutable shared state is a major bug source, classic 'var in for loop' capture bug, no explicit capture list, no quality enforcement. Rust: closures can capture by reference (borrow) or by value (move), move keyword forces value capture, compiler enforces borrow rules but capture is still implicit, Fn/FnMut/FnOnce traits determine what captures can do. Go: closures capture by reference, mutable shared state, goroutine + closure capture bugs are common, no explicit capture list. Kotlin: closures capture by reference, CAN mutate captured vars (unlike Java), no explicit capture list. Swift: closures capture by reference by default, capture list [weak self, unowned x] for value capture, @escaping annotation required. EK9: capture is EXPLICIT (listed in parentheses), by VALUE (independent copy), named capture parameters for self-documenting code, unused captures are compiler errors, captures count toward quality metrics.

beginner bug capture closure copy explicit first function independent intro migrate mutable named parameter reference start state swift value variable

closure
variable capture
captured variable

scaled <- (factor, base) is mathOperation as pure function
        result:=? x * factor + y

Explanation: Capturing a variable that has not been defined yet in the program flow triggers E08010 — variable not defined. The variable 'base' is declared later in the program, so it cannot be captured at this point. See ek9 -h E08010 for details.

Explanation: In a pure dynamic function, mutating captured variables is forbidden. This triggers E08120 — mutating variables not allowed in pure scope. Captured values are immutable copies in pure functions. See ek9 -h E08120 for details.


Question: What are pure functions and what is the difference between Consumer and Acceptor?

Why does EK9 have both Consumer and Acceptor?

What built-in abstract function types does EK9 provide?

Answer:
Pure functions in EK9 are declared with 'as pure' and the compiler ENFORCES purity — no side effects, no mutable external state, no calling impure functions. This is not a hint or annotation — it is a compile-time guarantee.

DECLARING PURE FUNCTIONS
  factorial() as pure
    -> n as Integer
    <- result as Integer: 1
    for i in 1 ... n
      result: result * i

WHY NOT 'result *= i'?
Mutation operators like *=, +=, :=: are NOT pure — they mutate the LHS in place and COULD also affect the RHS as a side effect. Reassignment ('result: result * i') uses the pure '*' operator to create a new value, then reassigns. Only the LHS changes, the RHS is guaranteed untouched.

PURITY IS INHERITED
When an abstract function is declared pure, ALL implementations MUST also be pure:
  mathOperation() as pure abstract
    -> x as Float, y as Float
    <- result as Float?

  add() is mathOperation as pure    //Must be pure — compiler enforces
    -> x as Float, y as Float
    <- result as Float: x + y

CONSUMER vs ACCEPTOR — THE KEY DISTINCTION
EK9 provides built-in abstract function types. The most important pair is Consumer and Acceptor — they have the SAME signature but different purity:

  Consumer of type T as pure abstract    //PURE — cannot mutate
    -> t as T

  Acceptor of type T as abstract         //NOT pure — can mutate
    -> t as T

Both take a single parameter and return nothing. Consumer is pure — it can only READ the value. Acceptor is NOT pure — it can modify state. This distinction is why Optional and Result have both whenOk(Consumer) for read-only access and whenOk(Acceptor) for mutation.

OTHER BUILT-IN ABSTRACT FUNCTION TYPES
EK9 provides a complete set, all generic:
  Supplier of T as pure abstract         //No input, returns T
    <- r as T?
  Predicate of T as pure abstract        //Takes T, returns Boolean
    -> t as T
    <- r as Boolean?
  UnaryOperator of T as pure abstract    //Takes T, returns T
    -> t as T
    <- r as T?
  Comparator of T as pure abstract       //Compares two T values
    -> t1 as T, t2 as T
    <- r as Integer?
  Assessor of T as abstract              //Like Predicate but NOT pure
    -> t as T
    <- r as Boolean?

And Bi-parameter variants: BiConsumer of (T, U), BiAcceptor of (T, U), BiPredicate of (T, U).

NOTICE THE PATTERN: Pure variants (Consumer, Predicate, UnaryOperator, Comparator) guarantee safety. Non-pure variants (Acceptor, Assessor) allow mutation when needed. This gives you precise control over what operations can and cannot do.

No other mainstream language provides this. Java has Consumer but it can mutate freely. Rust has Fn/FnMut but these are structural traits. EK9 gives you NOMINAL purity enforcement with a complete type hierarchy.

See Q49 for defining functions. See Q50 for how ':=?' guarded assignment is essential in pure returns. See Q53 for how purity affects closure capture. See Q55 for passing these as delegates. See Q48 for how Result uses Consumer and Acceptor. See Q89 for how Predicate, Comparator, and UnaryOperator are used as stream pipeline stages. See Q106 for traits which define behavioral contracts. See Q215 for sanitized parameters. See Q235 for complete stream operations reference mapping each operation to its function type. See Q262 for observer pattern using function delegates as event listeners. See Q273 for purity as security boundary.

defines module qa.pureconsumeracceptor

  defines function

    mathOperation() as pure abstract
      ->
        x as Float
        y as Float
      <- result as Float?

    addOp() is mathOperation as pure
      ->
        x as Float
        y as Float
      <- result as Float: x + y

    factorial() as pure
      -> n as Integer
      <- result as Integer: 1
      for i in 1 ... n
        result: result * i

    printValue()
      -> item as String
      stdout <- Stdout()
      stdout.println(`Processing: ${item}`)

  defines program

    PureDemo()
      stdout <- Stdout()

      // Pure function — compiler enforces no side effects
      stdout.println(`factorial(6): ${factorial(6)}`)

      // Pure abstract function — implementations must be pure
      op as mathOperation: addOp
      stdout.println(`pure delegate: ${op(5.0, 3.0)}`)

      // Consumer is pure — read-only access
      r1 <- Result("Steve", Integer())
      if r1?
        stdout.println(`ok: ${r1.ok()}`)

      // Built-in types use Consumer/Acceptor distinction
      opt <- Optional("Hello")
      if opt?
        stdout.println(`optional: ${opt.get()}`)

Java: Consumer<T> can mutate freely (no purity), Function<T,R> can have side effects, no distinction between pure and impure functional interfaces, @FunctionalInterface is just documentation. Python: no built-in abstract function types, no purity concept, any function can have side effects. JavaScript: no type system for functions, no purity enforcement, everything can mutate. Rust: Fn (immutable borrow), FnMut (mutable borrow), FnOnce (takes ownership) — similar concept but structural not nominal, no named types for closures. Go: no abstract function concept, no purity enforcement, all functions can have side effects. C#: Action<T> and Func<T,R> delegates but no purity enforcement, no Consumer/Acceptor distinction. Kotlin: (T) -> Unit for both pure and impure, no compile-time purity, no distinction. Swift: (T) -> Void for both, @Sendable for some safety but no purity enforcement. EK9: Consumer is PURE (read-only), Acceptor is NOT pure (can mutate), compiler enforces this distinction, complete set of built-in abstract function types with pure/non-pure variants, purity is inherited through implementations.

abstract acceptor assessor beginner comparator consumer effect enforce first function immutable intro migrate operator predicate pure purity side side-effect start supplier unary

Explanation: If the abstract parent is declared 'as pure', the implementation must also be 'as pure'. Omitting 'as pure' when the parent requires it triggers E05150 — purity mismatch. See ek9 -h E05150 for details.

Explanation: In a pure function, mutation operators like *= are forbidden because they modify the LHS in-place. This triggers E08120 — mutating variables not allowed in pure scope. Use reassignment (result: result * i) to create a new value instead. See ek9 -h E08120 for details.


Question: How do I pass functions as delegates in EK9?

How do I store a function in a variable in EK9?

How do I pass a function as a parameter in EK9?

Answer:
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.

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")}`)

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.

beginner call collection consumer delegate first function intro list parameter parentheses pass predicate reference start store variable

function delegate
pass function
callback

Explanation: 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.


Question: What are higher-order functions and why should I use them?

How do I create a function that returns a function in EK9?

How do I use the strategy pattern with EK9 functions?

Why are higher-order functions useful in EK9?

Answer:
A higher-order function is a function that either accepts a function as a parameter, returns a function as its result, or both. EK9's type system makes higher-order functions natural and type-safe because functions ARE types.

FUNCTION RETURNING A FUNCTION
A function can return a delegate based on runtime conditions:
  getProcessor()
    -> mode as String
    <- processor as Processor?
    if mode == "upper"
      processor := UpperProcessor
    else
      processor := LowerProcessor

The caller gets a function reference and calls it:
  transform <- getProcessor("upper")
  result <- transform("hello")

CONDITIONAL FUNCTION SELECTION (TERNARY)
EK9's ternary syntax is especially elegant for selecting between two functions:
  selectOperation()
    -> value as Float
    <- op as mathOperation: value < 0.0 <- addOp else subtractOp

This returns addOp when value is negative, subtractOp otherwise. One line, completely readable.

WHY USE HIGHER-ORDER FUNCTIONS?

1. STRATEGY PATTERN — Replace if/else chains with function selection:
  Instead of: if mode == "A" then doA() else if mode == "B" then doB()
  Use: processor <- getProcessor(mode) then processor(data)

2. CONFIGURABLE ALGORITHMS — Pass behaviour as a parameter:
  processList()
    -> items as List of String, transform as Processor
    <- results as List of String: List() of String
    for item in items
      results += transform(item)

3. COMPOSITION — Build complex behaviour from simple functions:
  Pipeline stages, each a function delegate, composed at runtime.

4. TESTABILITY — Mock behaviour by passing different function delegates.

In Java, the strategy pattern requires defining an interface, implementing it in separate classes, and wiring them together. In EK9, you just pass a function. In Python, you can pass functions but there is no type contract. In Rust, you need trait objects. EK9 gives you the safety of typed strategies with the simplicity of passing functions.

See Q51 for abstract function types. See Q54 for pure function constraints on strategy selection. See Q55 for function delegates. See Q57 for using higher-order functions to change class behaviour without subclassing. See Q58 for generic function templates. See Q59 for higher-order functions in stream pipelines. See Q89 for stream pipeline basics.

defines module qa.higherorderfunction

  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()

    getProcessor()
      -> mode as String
      <- processor as Processor?
      if mode == "upper"
        processor := UpperProcessor
      else
        processor := LowerProcessor

    processList()
      ->
        items as List of String
        transform as Processor
      <- results as List of String: List() of String
      for item in items
        results += transform(item)

  defines program

    HigherOrderDemo()
      stdout <- Stdout()

      // Function returning a function
      upper <- getProcessor("upper")
      stdout.println(`upper: ${upper("hello")}`)

      lower <- getProcessor("lower")
      stdout.println(`lower: ${lower("HELLO")}`)

      // Function accepting a function — configurable algorithm
      words <- ["Hello", "World", "EK9"]
      uppered <- processList(words, UpperProcessor)
      for word in uppered
        stdout.println(`processed: ${word}`)

      // Strategy selection — swap behaviour at runtime
      modes <- ["upper", "lower", "upper"]
      for mode in modes
        proc <- getProcessor(mode)
        stdout.println(`${mode}: ${proc("Test")}`)

Java: requires functional interfaces as strategy types, method references (::) or lambdas, verbose anonymous class pattern for pre-Java-8, no type hierarchy for strategies. Python: functions are first-class so higher-order works naturally, but no type contracts, no compile-time safety, duck typing means errors at runtime. JavaScript: functions are first-class, higher-order is common (callbacks, promises), but no type safety, callback hell, no quality enforcement. Rust: higher-order functions use Fn trait bounds or fn pointers, complex lifetime annotations for closures, cannot name return types without impl Trait, verbose Box<dyn Fn> for stored delegates. Go: higher-order functions work with function types, but no type hierarchies, no generics until 1.18, verbose type declarations. C#: Func<T,R> and Action<T> for higher-order patterns, delegate types, LINQ is built on higher-order functions. Kotlin: higher-order functions with structural types, inline keyword for performance, but no nominal function type hierarchies. Swift: higher-order functions with structural closure types, @escaping annotation complexity. EK9: higher-order functions are natural because functions ARE types, ternary syntax for elegant function selection, abstract function types provide compile-time contracts, no verbose interface boilerplate, complete type safety.

beginner compose conditional configure delegate first function higher intro migrate order pattern return select start strategy ternary

higher order function
function as parameter

Explanation: A pure function must have a body to be pure. Removing 'abstract' and adding 'pure' without a concrete body triggers E07110. See ek9 -h E07110 for details.

UpperProcessor() extends Processor as pure

Explanation: If the parent function is not pure, child functions must also not be pure. Adding 'as pure' on a child of a non-pure parent triggers E05160 — purity mismatch in type hierarchy. See ek9 -h E05160 for details.


Question: How can I change method behaviour on a class without subclassing?

How do I use the strategy pattern with function delegates in EK9?

How do I inject behaviour into a class using functions in EK9?

How do I customise class behaviour per instance in EK9?

Answer:
In EK9, you can change a class's method behaviour without subclassing by using function delegate fields. Supply a function as a constructor parameter, store it as a field, and call it from the method. Different instances of the same class can have different behaviour.

THE PATTERN
Define an abstract function for the behaviour contract:
  Processor() as abstract
    -> text as String
    <- result as String?

Define a class with a function delegate field:
  TextHandler
    processor as Processor?

    TextHandler()
      -> handler as Processor
      this.processor :=: handler

    process()
      -> text as String
      <- result as String: processor(text)

Now create instances with different behaviour:
  upper <- TextHandler(UpperProcessor)
  lower <- TextHandler(LowerProcessor)
  upper.process("hello")    //Returns "HELLO"
  lower.process("hello")    //Returns "hello"

Same class, same method signature, different behaviour per instance. No subclassing, no inheritance hierarchy, no override boilerplate.

WHY IS THIS BETTER THAN SUBCLASSING?
1. NO CLASS EXPLOSION — Instead of UpperTextHandler, LowerTextHandler, TrimTextHandler, you have one TextHandler class with different function delegates.
2. RUNTIME FLEXIBILITY — Change behaviour after construction by reassigning the delegate field.
3. COMPOSITION OVER INHERITANCE — Functions are composed, not inherited. This is the strategy pattern made trivial.
4. TESTABILITY — Pass a test function delegate in unit tests.

DYNAMIC FUNCTIONS AS DELEGATES
You can use dynamic functions with captures for custom behaviour:
  prefix <- ">>>"
  custom <- TextHandler((prefix) is Processor as function
    result:=? prefix + " " + text)
  custom.process("hello")    //Returns ">>> hello"

This creates a one-off behaviour with captured context. In Java, this requires an anonymous inner class or lambda with a functional interface. In EK9, it is natural.

In Java, the strategy pattern requires defining an interface, implementing multiple classes, and wiring dependencies. In Go, you use function fields but without type hierarchies. In Python, you monkey-patch or use first-class functions but without type safety. EK9 makes the strategy pattern a first-class, type-safe, compile-time-verified pattern.

See Q51 for abstract functions. See Q52 for dynamic functions. See Q53 for variable capture. See Q55 for function delegates. See Q106 for traits. See Q109 for composition over inheritance. See Q214 for strategy pattern.

defines module qa.strategypattern

  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()

  defines class

    TextHandler
      processor as Processor?

      default private TextHandler()

      TextHandler()
        -> handler as Processor
        this.processor: handler

      process()
        -> text as String
        <- result as String: processor(text)

      default operator ?

  defines program

    StrategyDemo()
      stdout <- Stdout()

      // Same class, different behaviour per instance
      upper <- TextHandler(UpperProcessor)
      lower <- TextHandler(LowerProcessor)

      stdout.println(`upper: ${upper.process("hello")}`)
      stdout.println(`lower: ${lower.process("HELLO")}`)

      // Dynamic function with capture as strategy
      prefix <- ">>>"
      customProcessor <- (prefix) is Processor as function
        result:=? `${prefix} ${text}`
      custom <- TextHandler(customProcessor)

      stdout.println(`custom: ${custom.process("hello")}`)

Java: strategy pattern requires defining an interface, implementing it in separate classes, injecting via constructor, verbose boilerplate, anonymous inner classes before Java 8, lambdas require functional interface. Python: duck typing allows passing any callable, no type safety, monkey-patching can change behaviour but is fragile, no compile-time verification. JavaScript: pass function as property, no type safety, prototype manipulation for behaviour changes, 'this' binding confusion. Rust: trait objects (dyn Trait) for runtime polymorphism, Box<dyn Fn> for stored closures, complex lifetime management, no simple field-level strategy pattern. Go: function fields on structs, simple but no type hierarchies, no interface guarantee on the function type. C#: delegate fields, strategy pattern well-supported but requires explicit delegate type declarations. Kotlin: function type fields ((String) -> String), SAM conversion, but structural not nominal typing. Swift: closure properties, @escaping required, but structural not nominal. EK9: function delegate field typed as abstract function gives compile-time contract, per-instance behaviour without subclassing, dynamic functions with captures for one-off strategies, natural strategy pattern with type safety.

beginner behaviour change class composition constructor delegate field first function inheritance inject instance intro migrate pattern start strategy subclass

TextHandler
      processor as Processor?

Explanation: TextHandler is not declared as 'as open' so it cannot be extended. Use composition with function delegate fields instead of creating subclasses. See ek9 -h E07090 for details.

Explanation: Class fields must be initialised at declaration. The '?' suffix creates the field in an uninitialised state that can be assigned later in the constructor. Without it, the field has no initial value. See ek9 -h E08180 for details.

processor()
        -> text as String
        <- result as String: processor(text)

Explanation: If TextHandler has a function delegate field 'processor' and also defines a method called 'processor()', the compiler reports E02080 DELEGATE_AND_METHOD_NAMES_CLASH. Calling processor() would be ambiguous — is it the method or the delegate invocation? Rename either the field or the method to avoid the clash. See ek9 -h E02080 for details.


Question: How do generic functions work in EK9?

How do I define a function with type parameters in EK9?

What are implicit super functions in EK9?

How does EK9 auto-create parameterised function supers?

Answer:
EK9 functions can have type parameters using 'of type T' for single-parameter or 'of type (S, T)' for multi-parameter generics. Generic functions serve as templates that create concrete function types when instantiated with specific types.

SINGLE TYPE PARAMETER
  transformer() of type T as open
    -> item as T
    <- result as T?

MULTI TYPE PARAMETER
  mapper() of type (S, T) as open
    -> item as S
    <- result as T := T()

The 'as open' modifier allows these to be extended with concrete types.

INSTANTIATING GENERIC FUNCTIONS
Create a concrete dynamic function from a generic template by specifying types:
  intDoubler <- () is transformer of Integer as function
    result:=? item * 2

  intToString <- () is mapper of (Integer, String) as function
    result: $item

IMPLICIT SUPER GENERATION
When you write '() is transformer of Integer as function', the compiler AUTOMATICALLY creates a concrete function type 'transformer of Integer' as the super type. You do not need to declare this type separately — it is generated implicitly from the generic template. This is unique to EK9.

The auto-generated super has the correct parameterised signature: 'transformer of Integer' has '-> item as Integer, <- result as Integer?'. This ensures type safety without boilerplate.

CONSTRAINING TYPE PARAMETERS
Use 'constrain by' to restrict which types can be used:
  shapeHandler() of type T constrain by Shape as open
    -> shape as T
    <- result as Boolean := false

With the constraint, you can call methods defined on Shape within the function body. Without constraints, only standard operators are available.

GENERIC FUNCTIONS WITH 'extends'
Both 'is' and 'extends' work:
  otherDoubler <- () extends transformer of Integer as function
    result:=? item + item

NAMED GENERIC FUNCTION IMPLEMENTATIONS
  doubleInteger() is transformer of Integer
    -> item as Integer
    <- result as Integer: item * 2

In Java, generics use type erasure and cannot create new types at runtime. In Rust, generics are monomorphised but closures cannot participate in generic hierarchies. In Go, generics (since 1.18) have no function type hierarchies. EK9's generic functions with implicit super generation provide type-safe, nominal, zero-boilerplate generic function hierarchies.

See Q51 for abstract functions. See Q52 for dynamic functions. See Q53 for variable capture in generic function instances. See Q54 for built-in generic function types (Predicate, Comparator, UnaryOperator). See Q55 for passing generic function instances as delegates. See Q89 for stream pipeline basics. See Q194 for generic classes. See Q195 for generic constraints. See Q235 for how generic function types are used as stream pipeline stages.

defines module qa.genericfunction

  defines function

    transformer() of type T as abstract
      -> item as T
      <- result as T?

    mapper() of type (S, T) as abstract
      -> item as S
      <- result as T?

  defines program

    GenericFunctionDemo()
      stdout <- Stdout()

      // Instantiate generic function with Integer
      intDoubler <- () is transformer of Integer as function
        result:=? item * 2

      stdout.println(`doubled: ${intDoubler(21)}`)

      // Multi-parameter generic — Integer to String
      intToString <- () is mapper of (Integer, String) as function
        result: $item

      stdout.println(`mapped: ${intToString(42)}`)

      // Using 'extends' instead of 'is'
      intTripler <- () extends transformer of Integer as function
        result:=? item * 3

      stdout.println(`tripled: ${intTripler(10)}`)

      // Store generic function instances in a list
      transforms <- [intDoubler, intTripler]
      for transform in transforms
        stdout.println(`transform(5): ${transform(5)}`)

Java: generics use type erasure (no runtime type information), functional interfaces can be generic but lambdas do not create named types, no implicit super generation, wildcard complexity (? extends T, ? super T). Python: no generics until 3.12, typing.Generic is runtime-invisible, no compile-time enforcement, no function type hierarchies. JavaScript: no generics, TypeScript has structural generics but no function type hierarchies. Rust: generics are monomorphised (good performance), impl Trait for generic returns, but closures cannot form generic type hierarchies, complex lifetime annotations with generics. Go: generics since 1.18, no function type hierarchies, limited type constraints. C#: generics with runtime type information, delegates can be generic, but no function type inheritance. Kotlin: generics similar to Java with reified keyword for some runtime info, no function type hierarchies, variance annotations (in/out). Swift: generics with associated types and protocols, no function type hierarchies. EK9: generic functions with 'of type T' or 'of type (S, T)', implicit super generation creates parameterised function types automatically, 'constrain by' for type bounds, 'as open' for extensibility, nominal typing throughout.

beginner constrain first function generation generic implicit instantiate intro migrate open parameter parameterised start super template type

intDoubler <- () is transformer as function

Explanation: Implementing a generic function without specifying the type parameter triggers E50100 — type is generic but no parameters were supplied. You must specify the concrete type when implementing a generic function. See ek9 -h E50100 for details.

intToString <- () is mapper of Integer as function

Explanation: If a generic function has two type parameters, providing only one triggers E06020 — incorrect number of parameters supplied. Match the number of type arguments to the generic declaration. See ek9 -h E06020 for details.


Question: How do I use functions in stream pipelines?

How do dynamic functions work with EK9 stream pipelines?

How do I capture variables for use in a pipeline stage?

How do functions integrate with cat, map, and filter in EK9?

Answer:
Dynamic functions with variable capture are essential for EK9 stream pipelines. They provide context to pipeline stages like 'map with' and 'filter by' without global state or side effects.

BASIC PIPELINE WITH NAMED FUNCTIONS
  cat items | map with transformer | filter by checker | collect as List of String

Each stage uses a function reference. 'map with' applies a transformation function. 'filter by' uses a predicate function.

CAPTURING CONTEXT FOR PIPELINE STAGES
The real power comes from dynamic functions that capture variables for use in pipeline stages:
  names <- Dict() of (Integer, String)
  names += DictEntry(1, "Alice")
  names += DictEntry(2, "Bob")

  idToName <- (names) is idMapper as function
    result: names.getOrDefault(id, "Unknown")

  resolved <- cat [1, 2, 3]
    | map with idToName
    | collect as List of String

The dynamic function 'idToName' captures the 'names' Dict and uses it inside the pipeline. Each pipeline element gets the captured context without global state.

STATEFUL PIPELINE FUNCTIONS
Dynamic functions can hold state across pipeline iterations — useful for accumulation, counting, or multi-element processing:
  count <- 0
  counter <- (count) is someAccumulator as function
    count++
    result:=? count

This is explicitly stated as a design feature: dynamic functions provide real power when building stream pipelines where you want to retain state as part of the pipeline process rather than depending on a single reduce at the end.

INLINE FUNCTIONS IN PIPELINES
Use inline syntax directly in the pipeline:
  results <- cat keys
    | map with (names) is idMapper (result: names.getOrDefault(id, "Unknown"))
    | collect as List of String

This is compact but still nominally typed — the compiler verifies the function matches the pipeline stage requirements.

FLATTEN FOR OPTIONAL RESULTS
When a pipeline stage returns Optional values, use 'flatten' to extract set values and discard unset ones:
  results <- cat keys
    | map with lookupFunction
    | flatten
    | collect as List of String

This eliminates null checking in pipeline code.

In Java, streams use lambdas but capture is hidden and by reference. In Python, generators and comprehensions have no type safety. In Rust, iterators with closures require complex lifetime annotations. EK9 pipelines with dynamic function captures are type-safe, explicit, and maintain quality enforcement.

See Q51 for abstract functions used as pipeline stage types. See Q52 for dynamic functions. See Q53 for variable capture. See Q54 for Consumer patterns in pipeline callbacks. See Q45 for List operations. See Q46 for Dict operations. See Q89 for stream pipeline basics. See Q122 for collect as and custom aggregators. See Q235 for complete stream operations reference.

defines module qa.functionpipeline

  defines function

    nameMapper() as abstract
      -> identifier as Integer
      <- result as String?

  defines program

    PipelineDemo()
      stdout <- Stdout()

      // Set up context data
      names <- Dict() of (Integer, String)
      names += DictEntry(1, "Alice")
      names += DictEntry(2, "Bob")
      names += DictEntry(3, "Charlie")

      // Dynamic function captures Dict for pipeline use
      lookup <- (names) is nameMapper as function
        result: names.getOrDefault(identifier, "Unknown")

      // Pipeline with captured context
      ids <- [1, 2, 4, 3]
      results <- cat ids
        | map with lookup
        | collect as List of String

      for name in results
        stdout.println(`resolved: ${name}`)

      // Direct function call — same function works outside pipelines
      stdout.println(`direct: ${lookup(2)}`)

Java: Stream API with lambdas, hidden capture by reference, map/filter/reduce pattern, no explicit capture list, lambda quality not enforced, flatMap for Optional unwrapping. Python: generators, list comprehensions, map/filter built-ins, no type safety, closures capture by reference. JavaScript: Array.map/filter/reduce with arrow functions, no type safety, promise chains, hidden capture. Rust: Iterator trait with map/filter/collect, closures with complex lifetime annotations, move semantics for capture, turbofish syntax for type annotation. Go: no built-in stream pipelines, manual loops, channels for pipeline patterns, verbose. Kotlin: sequences with lambda chains, structural typing, inline functions for performance, no explicit capture. Swift: lazy sequences with closures, structural typing, no explicit capture list in pipeline context. EK9: stream pipelines with cat/map/filter/collect, dynamic functions with EXPLICIT by-value capture provide pipeline context, stateful functions for accumulation, inline syntax for compact stages, flatten for Optional unwrapping, type-safe and quality-enforced.

anonymous beginner capture cat closure collect context dynamic filter first flatten function intro map migrate pipeline stage start stateful stream

stream pipeline
pipe
functional pipeline

Explanation: Using 'call' instead of 'map with' for a function that takes a parameter and returns a value triggers E07830 — stream pipeline type mismatch. Use 'map with' for transformation functions. See ek9 -h E07830 for details.

Explanation: EK9 Dict does not have a get() method. Use getOrDefault(key, default) for safe access. Triggers E50060 — method not resolved. See ek9 -h Dict for the full API.


Question: What is function dispatching in EK9?

How does the dispatcher keyword work in EK9?

How do I dispatch on multiple argument types in EK9?

Answer:
EK9 has a 'dispatcher' keyword that enables runtime type-based dispatch on method parameters. This is like method overloading but resolved at RUNTIME based on the actual types of the arguments, not just the declared types.

BASIC DISPATCHER
Mark a method as 'dispatcher' and provide overloaded implementations:
  Formatter
    format() as dispatcher
      -> item as Any
      <- result as String: $item

    format()
      -> item as Integer
      <- result as String: "Int: " + $item

    format()
      -> item as String
      <- result as String: "Str: " + item

When you call 'formatter.format(someValue)', EK9 dispatches to the most specific overload based on the runtime type of 'someValue'. If no specific overload matches, the dispatcher base method (with 'Any' parameter) handles it.

WHY IS THIS POWERFUL?
In Java, method overloading is resolved at COMPILE time based on declared types. If you have a variable typed as Object that holds an Integer, Java calls the Object overload. EK9 calls the Integer overload because it dispatches on the RUNTIME type.

This eliminates the visitor pattern. Instead of defining accept/visit methods across a class hierarchy, you write a single dispatcher method with overloads for each type.

DISPATCHER WITH MULTIPLE PARAMETERS
Dispatchers can dispatch on multiple parameters:
  intersect() as dispatcher
    -> s1 as Shape, s2 as Shape
    <- result as String: "generic"

  intersect()
    -> s1 as Circle, s2 as Circle
    <- result as String: "circle-circle"

  intersect()
    -> s1 as Circle, s2 as Rectangle
    <- result as String: "circle-rectangle"

This is true multiple dispatch — something only Julia and Common Lisp natively support among mainstream languages.

DISPATCHER WITH TRAITS
Dispatchers work with trait types, using 'allow only' to restrict which types the dispatcher accepts. This enables safe dispatching over trait hierarchies.

DISPATCHER IS PURE-COMPATIBLE
Dispatchers can be marked 'as pure dispatcher' for side-effect-free dispatching.

In Java, you need the visitor pattern (verbose, fragile). In Python, you use functools.singledispatch (limited). In Go, you use type switches. In Rust, you use match with enum variants. EK9's dispatcher is built into the language with compile-time verification of completeness.

See Q49 for defining functions. See Q51 for abstract functions. See Q56 for higher-order functions as an alternative dispatch mechanism. See Q105 for method dispatch in classes. See Q255 for cost-based method resolution in overloading. See Q600 for method overloading vs function dispatching comparison.

defines module qa.dispatching

  defines class

    Shape as abstract
      name() as abstract
        <- rtn as String?
      default operator ?

    Circle is Shape
      override name()
        <- rtn as String: "Circle"

    Square is Shape
      override name()
        <- rtn as String: "Square"

    Triangle is Shape
      override name()
        <- rtn as String: "Triangle"

    Renderer
      render() as dispatcher
        -> shape as Shape
        <- result as String: "Shape: " + shape.name()

      render()
        -> shape as Circle
        <- result as String: "Rendered Circle"

      render()
        -> shape as Square
        <- result as String: "Rendered Square"

  defines program

    DispatchDemo()
      stdout <- Stdout()

      renderer <- Renderer()

      // Dispatcher selects overload based on runtime type
      circle <- Circle()
      square <- Square()
      triangle <- Triangle()

      stdout.println(renderer.render(circle))
      stdout.println(renderer.render(square))
      // Triangle has no specific handler — falls through to base
      stdout.println(renderer.render(triangle))

Java: method overloading resolved at compile time (static dispatch), visitor pattern needed for runtime dispatch, verbose accept/visit boilerplate, instanceof checks are code smell. Python: functools.singledispatch for single-argument dispatch, limited to one parameter, no compile-time verification, runtime errors for missing handlers. JavaScript: no dispatch mechanism, manual typeof/instanceof checks, no type safety. Rust: match on enum variants, no multiple dispatch, trait methods for single dispatch, pattern matching is powerful but not multi-argument dispatch. Go: type switch for runtime dispatch, no multiple dispatch, no compile-time completeness check. C#: dynamic keyword for late binding, no multiple dispatch, visitor pattern needed. Kotlin: when with is checks for type matching, no multiple dispatch, smart casts help but are single-argument. Julia: built-in multiple dispatch as core language feature, most similar to EK9's dispatcher, but dynamically typed. Swift: no multiple dispatch, protocol-based single dispatch, switch with pattern matching. EK9: built-in 'dispatcher' keyword for runtime type-based dispatch, multiple dispatch on multiple parameters, compile-time verified, pure-compatible, eliminates visitor pattern, works with traits and class hierarchies.

beginner dispatch dispatcher first function handler immutable intro migrate multiple overload parameter pattern pure resolve runtime sealed side-effect start trait type visitor

Circle is Shape
      name()
        <- rtn as String: "Circle"

Explanation: When implementing an abstract method from a parent class, the 'override' keyword is required. Without it, the compiler treats it as a new method rather than an implementation. See ek9 -h E05120 for details.