Question: How do I profile my EK9 tests?

How do I get JSON profiling data from EK9 tests?

Answer:
Every test format supports profiling by appending 'p'. The profiler instruments all code paths exercised by your tests.

PROFILE ALL TESTS
  ek9 -tp myproject.ek9
Runs all @Test functions with profiling. The human-readable output lists each function with call count and timing, sorted by self-time.

JSON OUTPUT FOR ANALYSIS
  ek9 -t2p myproject.ek9
Produces structured JSON that includes profiling data per function. This is ideal for:
- AI assistants analysing performance.
- CI pipelines checking latency thresholds.
- Trend analysis tools tracking performance over builds.

HTML DASHBOARD
  ek9 -t6p myproject.ek9
Generates an interactive HTML page with:
- Flame graph visualisation.
- Hot function table sorted by self-time.
- Call tree with timing annotations.
- Source views with timing badges per function.

FINDING SLOW TESTS
The profiler measures all code called during test execution. If a test is slow, the profiler shows which function under that test consumed the most time. This distinguishes between:
- Slow test setup (the test infrastructure).
- Slow application code (the code under test).
- Excessive assertions (too many checks per test).

FILTERING
Profile specific test groups:
  ek9 -tp -tg performance myproject.ek9
This profiles only tests in the 'performance' group.

See Q627 for profiling programs. See Q629 for reading flame graphs. See Q630 for identifying hot methods. See Q155 for writing unit tests. See Q157 for running tests. See Q207 for test output formats.

defines module qa.profilingdeep.tests

  defines function

    <?-
      Functions that tests would exercise.
      Profiling reveals which functions consume the most time.
    -?>
    isPrime()
      -> candidate as Integer
      <- prime as Boolean: false

      minimumPrime <- 2
      if candidate >= minimumPrime
        prime: true
        divisor <- 2
        squaredDivisor <- divisor * divisor
        while squaredDivisor <= candidate and prime
          if candidate mod divisor == 0
            prime: false
          divisor: divisor + 1
          squaredDivisor: divisor * divisor

    countPrimesBelow()
      -> limit as Integer
      <- count as Integer: 0

      for candidate in 2 ... limit
        if isPrime(candidate)
          count: count + 1

  defines program

    ProfileTestsDemo()
      stdout <- Stdout()

      hundredLimit <- 100
      primeCount <- countPrimesBelow(hundredLimit)
      stdout.println(`Primes below 100: ${primeCount}`)

Java: JUnit + JMH for microbenchmarks (separate framework), async-profiler for test profiling (external tool). Python: pytest-benchmark (plugin), cProfile with test runner (manual setup). Rust: criterion for benchmarks (external crate), no built-in test profiling. Go: go test -bench for benchmarks, -cpuprofile for profiling (separate flags). EK9: append 'p' to any test flag, profiling is built into the test runner, no separate framework needed.

HTML JSON benchmark dashboard flame flame-graph group performance profile slow t2p t6p test

Explanation: Integer has no toString() method in EK9; `primeCount.toString()` triggers E50060 - use the $ prefix operator or `${primeCount}` interpolation. See ek9 -h E50060 for details.

Explanation: Integer has no intValue() method in EK9. Integer values are used directly. See ek9 -h E50060 for details.


Question: How do I read an EK9 flame graph?

What do the colours in the flame graph mean?

How do I interpret the EK9 profiling dashboard?

What is a flame graph and how do I use it?

Answer:
The -t6p flag generates an interactive HTML flame graph. Understanding how to read it is the key skill for performance work.

GENERATING A FLAME GRAPH
  ek9 -t6p myproject.ek9
Output: .ek9/coverage/index.html (open in browser).

READING THE GRAPH
Each horizontal bar represents a function. The bars are stacked vertically to show the call chain: the bottom bar is the entry point, and each bar above it is a function called by the one below.

WIDTH = TIME
The width of each bar represents total time spent in that function (including its callees). A wide bar means that call path consumed a large fraction of execution time.

COLOUR MEANING
- Red frames: high self-time. These functions are doing the actual work. They are your primary optimisation targets.
- Blue frames: orchestrators. High total time but low self-time. They spend time calling other functions, not doing work themselves.
- Narrow frames: fast or rarely called. Usually not worth optimising.

INTERACTIVE FEATURES
- Click a frame to zoom into that subtree.
- Hover to see exact call count, self-time, and total time.
- Search to highlight all frames matching a function name.
- Reset to return to the full view.

ACTIONABLE STRATEGY
1. Look for the widest red frames. These are your bottlenecks.
2. Check call count. A fast function called millions of times can dominate.
3. Compare self-time vs total time. If total is high but self is low, the bottleneck is deeper in the call chain.
4. Click to zoom and find the deepest hot function.

See Q627 for profiling programs. See Q630 for identifying hot methods. See Q321 for the quality report dashboard. See Q322 for profiling overview.

defines module qa.profilingdeep.flamegraph

  defines function

    <?-
      A call chain that produces a clear flame graph:
      orchestrate calls processItems which calls transformItem.
      In the flame graph, orchestrate is at the bottom (wide),
      processItems in the middle, and transformItem at the top.
    -?>
    transformItem() as pure
      -> item as Integer
      <- result as Integer: item * item + 1

    processItems()
      -> items as List of Integer
      <- total as Integer: 0

      for item in items
        transformed <- transformItem(item)
        total: total + transformed

    orchestrate()
      -> size as Integer
      <- result as Integer: 0

      items <- List() of Integer
      for i in 1 ... size
        items += i
      result: processItems(items)

  defines program

    FlameGraphDemo()
      stdout <- Stdout()

      batchSize <- 50
      result <- orchestrate(batchSize)
      stdout.println(`Result: ${result}`)

Java: async-profiler generates flame graphs (SVG), JFR with JMC for interactive analysis. Python: py-spy generates flame graphs (SVG/HTML). Rust: flamegraph crate wraps perf into SVG output. Go: go tool pprof generates flame graphs. JavaScript: Chrome DevTools flame chart. EK9: built-in HTML flame graph from -t6p, interactive with zoom/search/hover, integrated with coverage and quality dashboard on same page.

blue dashboard flame flame-graph graph interpret performance profile read red self-time total-time width zoom

Explanation: Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details.


Question: How do I identify hot methods in EK9 profiling output?

How do I find the slowest functions in my EK9 program?

What is the hot function table in EK9 profiling?

How do I know which function to optimise first?

Answer:
The profiler output includes a hot function table that lists functions sorted by self-time. The top entries are your optimisation targets.

HOT FUNCTION TABLE
The -t6p HTML dashboard and -tp human-readable output both include a table with columns:
- Function name: fully qualified (module::type.method or module::function).
- Call count: how many times the function was invoked.
- Self time: time spent in this function only.
- Total time: time including all functions it calls.
- Avg time: average time per call.
- p95: 95th percentile call time.
- p99: 99th percentile call time.

SORTING STRATEGY
Sort by self-time to find functions doing the most work. Sort by call count to find functions called excessively. Sort by p99 to find functions with occasional slow calls (latency spikes).

THREE OPTIMISATION PATTERNS
1. High self-time, low call count: the function itself is slow. Optimise its algorithm.
2. Low self-time, very high call count: the function is fast but called too often. Reduce call frequency (cache results, batch operations).
3. High p99 with low average: occasional slow path. Check for conditional branches that hit slow code paths intermittently.

JSON FOR AUTOMATED ANALYSIS
  ek9 -t2p myproject.ek9
The JSON output can be consumed by AI assistants or CI scripts to:
- Flag functions where p99 exceeds a threshold.
- Track self-time trends across builds.
- Identify call count regressions.

See Q627 for profiling programs. See Q629 for reading flame graphs. See Q631 for benchmarking two approaches. See Q312 for complexity metrics. See Q322 for profiling overview.

defines module qa.profilingdeep.hotmethods

  defines function

    <?-
      Three functions with different profiling characteristics:
      - expensiveComputation: high self-time (does real work).
      - cheapHelper: low self-time, high call count.
      - orchestrator: high total time, low self-time.
    -?>
    expensiveComputation() as pure
      -> iterations as Integer
      <- accumulator as Float: 0.0

      for i in 1 ... iterations
        term <- 1.0 / Float(i)
        accumulator: accumulator + term

    cheapHelper() as pure
      -> number as Float
      <- doubled as Float: number * 2.0

    orchestrator()
      -> limit as Integer
      <- result as Float: 0.0

      thousand <- 1000
      raw <- expensiveComputation(thousand)
      for i in 1 ... limit
        result: cheapHelper(raw)

  defines program

    HotMethodsDemo()
      stdout <- Stdout()

      repetitions <- 50
      finalValue <- orchestrator(repetitions)
      stdout.println(`Result: ${finalValue}`)

Java: JFR hot methods view in JMC, async-profiler flat profile. Python: cProfile cumulative/tottime columns, py-spy top view. Rust: perf report sorted by overhead. Go: go tool pprof top command. JavaScript: Chrome DevTools Bottom-Up view. EK9: built-in hot function table in -tp (text) and -t6p (HTML), sorted by self-time, with p95/p99 percentiles, JSON export for CI.

call count flame-graph function hot method optimise p95 p99 performance profile self-time slow sort table

Explanation: Calling a pure function without capturing its return value is dead code. Pure functions have no side effects. See ek9 -h E11051 for details.


Question: How do I benchmark two approaches in EK9?

How do I compare the performance of two EK9 implementations?

Does EK9 have microbenchmarking support?

How do I benchmark execution time in EK9?

Answer:
EK9 provides two complementary approaches to benchmarking: the built-in profiler for real-world measurement, and Millisecond/SystemClock for manual timing.

APPROACH 1: PROFILER COMPARISON
Write two test functions that exercise the two approaches, then profile:
  ek9 -t2p myproject.ek9
The JSON output shows self-time and call count for each function. Compare the self-time values directly.

APPROACH 2: MANUAL TIMING
Use SystemClock and Millisecond for explicit before/after measurement:
  clock <- SystemClock()
  before <- clock.millisecond()
  // ... code to measure ...
  after <- clock.millisecond()
  elapsed <- after - before
This gives wall-clock elapsed time as a Millisecond value.

BEST PRACTICES
1. Warm up: run the code several times before measuring. JVM needs time to JIT-compile hot paths.
2. Repeat: measure multiple iterations and compute the average. Single measurements are noisy.
3. Isolate: benchmark one thing at a time. Do not mix the two approaches being compared in the same run.
4. Use profiler for real code: manual timing is good for quick checks but the profiler provides p50/p95/p99 distributions.
5. Avoid optimisation: profiling forces -O0 automatically. For manual timing, use ek9 -cd (debug mode, no optimisation) to prevent dead code elimination.

WHEN TO USE EACH
- Profiler (-tp/-t2p): for production-representative measurement with statistical detail.
- Manual timing: for quick A/B comparison during development.
- Both together: profile to find hot methods, then manually time the specific methods you want to optimise.

See Q627 for profiling programs. See Q628 for profiling tests. See Q630 for identifying hot methods. See Q544 for elapsed time and benchmarking with Millisecond. See Q41 for the Millisecond type.

defines module qa.profilingdeep.benchmark

  defines function

    <?-
      Two approaches to summing a range, to demonstrate benchmarking.
      Approach 1: iterative loop.
      Approach 2: mathematical formula.
    -?>
    sumIterative() as pure
      -> limit as Integer
      <- total as Integer: 0

      for i in 1 ... limit
        total: total + i

    sumFormula() as pure
      -> limit as Integer
      <- total as Integer: limit * (limit + 1) / 2

  defines program

    BenchmarkDemo()
      stdout <- Stdout()

      testSize <- 1000

      clock <- SystemClock()

      beforeIterative <- clock.millisecond()
      iterativeResult <- sumIterative(testSize)
      afterIterative <- clock.millisecond()
      iterativeElapsed <- afterIterative - beforeIterative

      beforeFormula <- clock.millisecond()
      formulaResult <- sumFormula(testSize)
      afterFormula <- clock.millisecond()
      formulaElapsed <- afterFormula - beforeFormula

      stdout.println(`Iterative: ${iterativeResult} (${iterativeElapsed})`)
      stdout.println(`Formula: ${formulaResult} (${formulaElapsed})`)

Java: JMH (Java Microbenchmark Harness) with @Benchmark annotations, System.nanoTime() for manual timing. Python: timeit module, time.perf_counter() for manual timing. Rust: criterion crate for statistical benchmarks, std::time::Instant for manual. Go: testing.B benchmark framework, time.Now() for manual. JavaScript: performance.now() in browser, benchmark.js (external). EK9: built-in profiler with p95/p99 statistics via -tp/-t2p, SystemClock + Millisecond for manual timing, no external framework needed.

Millisecond SystemClock approach benchmark compare elapsed flame-graph measure performance profile timing warm


Question: How does EK9 ensure variables are used only after they are defined?

What happens if I use a variable before declaring it?

Answer:
EK9 enforces sequential definition order within a scope. A variable must be declared before any reference to it. This prevents an entire category of bugs where code accidentally references a variable that does not yet exist.

DEFINITION ORDER RULE
Within a function, method, or program body, statements execute top-to-bottom. A variable declared on line 10 cannot be referenced on line 8. The compiler checks this and raises E08010 if the reference precedes the declaration.

WHY THIS MATTERS
Forward references to variables are a common copy-paste error. A developer moves a block of code above its variable declarations and the program silently reads uninitialized memory (in C) or gets undefined (in JavaScript). EK9 eliminates this by making definition order a compile-time guarantee.

EXCEPTIONS
Types and functions can be forward-referenced because EK9 resolves them in a separate phase. Only local variables, parameters, and fields follow strict sequential order.

CORRECT PATTERN
Declare each variable before using it. Group related declarations near their first use. The compiler verifies every reference has a preceding declaration in the same or enclosing scope.

See Q633 for initialization tracking across branches. See Q251 for debugging unset variable errors.
See Q688 for variable init order. See Q692 for field initialization.

defines module qa.dataflow.definitionorder

  defines function

    <?-
      Correct: each variable is declared before it is used.
      taxRate is declared before taxAmount references it.
    -?>
    computeTotal() as pure
      -> basePrice as Float
      <- total as Float: basePrice

      taxRate <- 0.15
      taxAmount <- basePrice * taxRate
      total: basePrice + taxAmount

    <?-
      Correct: intermediate results are computed in order.
      Each step uses only previously declared variables.
    -?>
    computeDiscount() as pure
      ->
        originalPrice as Float
        discountPercent as Float
      <- finalPrice as Float: originalPrice

      discountFraction <- discountPercent / 100.0
      discountAmount <- originalPrice * discountFraction
      finalPrice: originalPrice - discountAmount

    <?-
      Correct: variables defined in sequence, each referencing
      only previously declared values.
    -?>
    buildGreeting() as pure
      ->
        firstName as String
        lastName as String
      <- greeting as String: firstName

      fullName <- `${firstName} ${lastName}`
      greeting: `Hello ${fullName}, welcome`

  defines program

    DefineBeforeUseDemo()
      stdout <- Stdout()

      totalPrice <- computeTotal(100.0)
      stdout.println(`Total: ${totalPrice}`)

      discounted <- computeDiscount(200.0, 15.0)
      stdout.println(`Discounted: ${discounted}`)

      msg <- buildGreeting("Alice", "Smith")
      stdout.println(msg)

Java: variables must be declared before use (compiler error). JavaScript: var hoisting allows use before declaration (reads as undefined). Python: NameError at runtime if variable not yet assigned. C: undefined behavior if variable not initialized. Go: compile error for undeclared variable. Rust: compile error for uninitialized variable. EK9: compile error E08010 for any reference before declaration, enforced at Phase 3.

E08010 before data-flow declaration define forward initialize migrate order reference safety scope use variable

define before use
variable initialization

taxAmount <- basePrice * taxRate
      taxRate <- 0.15

Explanation: Referencing taxRate before it is declared is a forward reference error. Variables must be declared before use. See ek9 -h E08010 for details.


Question: How does EK9 track variable initialization across control flow branches?

What happens if a variable is only initialized in one branch?

How does EK9 check initialization in if-else?

Answer:
EK9 tracks the initialization state of every variable through all control flow paths. If a variable might be uninitialized on any reachable path where it is used, the compiler raises E08020.

BRANCH INITIALIZATION
When a variable is assigned in both the if and else branches, the compiler knows it is initialized after the if-else regardless of which branch executes. If only one branch initializes the variable, the compiler considers it potentially uninitialized after the conditional.

RETURN VARIABLE DEFAULTS
Return variables declared with a default value (e.g., '<- rtn as String: unknown') are always initialized. The default ensures the variable has a value even if no branch explicitly sets it.

SAFE PATTERN
Either provide a default value at declaration time, or ensure every branch of every conditional initializes the variable before it is used.

See Q632 for definition order. See Q634 for guard-based safe access. See Q251 for debugging unset variable errors.

defines module qa.dataflow.branchinit

  defines constant

    EXCELLENT_THRESHOLD <- 90

    GOOD_THRESHOLD <- 70

    POOR_THRESHOLD <- 50

  defines function

    <?-
      Correct: the return variable has a default value,
      so it is always initialized regardless of branch taken.
    -?>
    classifyScore() as pure
      -> score as Integer
      <- category as String: "average"

      if score >= EXCELLENT_THRESHOLD
        category: "excellent"
      else if score >= GOOD_THRESHOLD
        category: "good"
      else if score < POOR_THRESHOLD
        category: "poor"

    <?-
      Correct: both branches explicitly set the return variable.
      The compiler can verify both paths initialize it.
    -?>
    describeSign() as pure
      -> number as Integer
      <- description as String: "zero"

      if number > 0
        description: "positive"
      else if number < 0
        description: "negative"

    <?-
      Correct: intermediate variables are initialized at declaration
      and used after initialization in both branches.
    -?>
    formatAmount() as pure
      ->
        amount as Float
        showDecimals as Boolean
      <- formatted as String: `${amount}`

      rounded <- amount + 0.5
      label <- "Amount"

      if showDecimals
        formatted: `${label}: ${amount}`
      else
        formatted: `${label}: ${rounded}`

  defines program

    BranchInitDemo()
      stdout <- Stdout()

      cat1 <- classifyScore(95)
      stdout.println(`Score 95: ${cat1}`)

      cat2 <- classifyScore(42)
      stdout.println(`Score 42: ${cat2}`)

      sign1 <- describeSign(5)
      stdout.println(`5 is ${sign1}`)

      sign2 <- describeSign(-3)
      stdout.println(`-3 is ${sign2}`)

      fmt1 <- formatAmount(19.99, true)
      stdout.println(fmt1)

      fmt2 <- formatAmount(19.99, false)
      stdout.println(fmt2)

Java: definite assignment analysis requires variables to be assigned before use, similar concept. Rust: borrow checker ensures initialization on all paths. Go: zero-value initialization means variables are always initialized (but may have wrong value). Python: runtime NameError if variable not assigned on taken path. C: undefined behavior for uninitialized variables. EK9: compile error E08020 for potentially uninitialized variables on any reachable path.

E08020 branch control data-flow else flow if initialization initialize path safety uninitialized variable

category as String: String()
      if score >= EXCELLENT_THRESHOLD
        category: "excellent"
      stdout.println(category)

Explanation: If category is only initialized in one branch, it may be uninitialized when used later. Provide a default value at declaration or initialize on all paths. See ek9 -h E50001 for details.


Question: Why must I use a guard expression before accessing a returned value?

How do guard expressions prevent unsafe access?

What happens if I skip the guard on a function return?

Answer:
EK9 requires guard expressions to verify that a value is set before calling methods on it. Without the guard, the value might be unset, and calling a method on an unset value is unsafe.

GUARD EXPRESSION PATTERN
The guard pattern 'if value <- expression()' combines assignment with an isSet check. The variable is only in scope inside the guarded block, where it is guaranteed to be set. This eliminates unsafe access by construction.

WHY GUARDS ARE NEEDED
Functions can return unset values (e.g., an empty Optional, a failed lookup). Without a guard, calling methods on an unset value would be like calling methods on null in Java. EK9 prevents this at compile time rather than crashing at runtime.

MULTIPLE GUARD PATTERNS
Guards work identically in if, while, switch, for, and try statements. The same syntax 'var <- expr()' checks isSet and creates a scoped variable.

ALTERNATIVE: EXPLICIT isSet CHECK
You can also check with the ? operator: 'if someValue?' then use someValue inside the block. The compiler tracks that ? was checked.

See Q163 for Optional unwrapping patterns. See Q632 for definition ordering. See Q633 for branch initialization. See Q636 for chained guard access. See Q47 for Optional basics.
See Q687 for Result guard access. See Q688 for variable init order.

defines module qa.dataflow.guardaccess

  defines constant

    ALICE_ID <- 1

    BOB_ID <- 2

    STUDENT_ALPHA <- 100

    STUDENT_BETA <- 200

    UNKNOWN_ID <- 999

  defines function

    <?-
      Simulates a lookup that might return an unset value.
    -?>
    findUserName()
      -> userId as Integer
      <- userName as String: String()

      if userId == ALICE_ID
        userName: "Alice"
      else if userId == BOB_ID
        userName: "Bob"

    <?-
      Simulates a function that always returns a set value.
    -?>
    getDefaultGreeting()
      <- greeting as String: "Hello"

    <?-
      Simulates a numeric lookup that may return unset.
    -?>
    lookupScore()
      -> studentId as Integer
      <- score as Float: Float()

      if studentId == STUDENT_ALPHA
        score: 95.5
      else if studentId == STUDENT_BETA
        score: 87.3

  defines program

    GuardBeforeAccessDemo()
      stdout <- Stdout()

      //Guard pattern: variable only accessible when set
      if name <- findUserName(ALICE_ID)
        upperName <- name.upperCase()
        stdout.println(`Found user: ${upperName}`)

      //Guard on a function that may return unset
      if name2 <- findUserName(UNKNOWN_ID)
        stdout.println(`Found: ${name2}`)
      else
        stdout.println("User not found")

      //Multiple guards in sequence
      if score <- lookupScore(STUDENT_ALPHA)
        doubled <- score * 2.0
        stdout.println(`Score doubled: ${doubled}`)

      //Explicit isSet check pattern
      greeting <- getDefaultGreeting()
      if greeting?
        stdout.println(greeting)

      //Nested guard: both must be set
      if userName <- findUserName(BOB_ID)
        if userScore <- lookupScore(STUDENT_BETA)
          stdout.println(`${userName} scored ${userScore}`)

Java: no guard expressions, Optional.get() throws NoSuchElementException, nullable references crash with NPE. Kotlin: safe call ?. operator returns null on unset, Elvis ?: for defaults. Swift: if let/guard let unwraps optionals safely. Rust: if let Some(v) = expr pattern matching. Go: manual nil checks. EK9: guard expression 'if var <- expr()' enforced by compiler, variable only in scope when set.

E08030 E08040 absent access data-flow expression guard initialize isSet isset method migrate null null-safe optional safe safety unset

upperName <- name.upperCase()
      if name <- findUserName(ALICE_ID)

Explanation: Referencing name before its guard declaration is a forward reference error. The guard must come before any use. See ek9 -h E50001 for details.


Question: What operations are forbidden inside a pure method?

Why does EK9 reject mutation operators in pure methods?

What is E08120 mutation in pure context?

Answer:
Pure methods and functions are forbidden from using any mutation operator. This is enforced at compile time as E08120.

FORBIDDEN MUTATION OPERATORS IN PURE CONTEXT
The following operators are mutation operators and cannot appear inside a pure method or function: += (add-assign), -= (subtract-assign), *= (multiply-assign), /= (divide-assign), :=: (copy-into), :~: (merge-into), :^: (replace-with). Using any of these inside a pure method triggers E08120.

ALLOWED IN PURE CONTEXT
Reassignment with : (colon) is allowed because it creates a new binding. Non-mutating operators (+, -, *, /, ==, <>) are allowed. Creating new values and returning them is allowed. Reading fields and parameters is allowed.

WHY THIS RESTRICTION EXISTS
Pure methods guarantee no side effects. If a pure method could use +=, it would mutate the state of an existing object, breaking the purity contract. The compiler enforces this so callers can trust that pure methods do not change any state.

REASSIGNMENT VS MUTATION
Reassignment (variable: newValue) binds the variable to a new value. Mutation (variable += delta) modifies the existing object in place. Pure methods allow reassignment but forbid mutation.

See Q560 for pure method basics. See Q566 for pure call chain restrictions. See Q273 for purity as a security boundary.

defines module qa.dataflow.puremutation

  defines class

    Account
      balance <- 0.0
      accountName <- "unnamed"

      Account()
        ->
          accountName as String
          initialBalance as Float
        this.accountName: accountName
        this.balance: initialBalance

      <?-
        Pure: computes new balance without mutating the field.
        Uses + operator (non-mutating) and : reassignment (allowed).
      -?>
      projectedBalance() as pure
        -> deposit as Float
        <- projected as Float: balance + deposit

      <?-
        Pure: builds a formatted string from fields.
        No mutation operators used.
      -?>
      formatSummary() as pure
        <- summary as String: `${accountName}: ${balance}`

      <?-
        Non-pure: uses mutation operator to modify balance.
        This method correctly omits 'as pure'.
      -?>
      deposit()
        -> amount as Float
        balance += amount

      <?-
        Non-pure: uses mutation operator.
      -?>
      withdraw()
        -> amount as Float
        balance -= amount

      default operator ?

  defines program

    PureMutationDemo()
      stdout <- Stdout()

      acct <- Account("Savings", 1000.0)
      stdout.println(acct.formatSummary())

      //Pure call: safe, no mutation
      projected <- acct.projectedBalance(500.0)
      stdout.println(`Projected: ${projected}`)

      //Non-pure: mutates the account
      acct.deposit(500.0)
      stdout.println(acct.formatSummary())

      acct.withdraw(200.0)
      stdout.println(acct.formatSummary())

Java: no compile-time purity enforcement. Python: no immutability guarantees. Haskell: all functions pure by default. Rust: mutable borrows are explicit. Kotlin: val prevents reassignment but does not prevent method-level mutation. EK9: E08120 rejects mutation operators in pure methods at compile time.

E08120 assign copy data-flow forbidden function immutable initialize merge migrate mutation operator pure restriction safety side-effect

Explanation: String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.


Question: How do I chain multiple guard expressions for safe access?

How do I safely access multiple potentially-unset values?

What is the pattern for multiple optional lookups?

Answer:
When you need to access multiple values that might be unset, chain guard expressions by nesting if-guards or using sequential guards with explicit isSet checks.

NESTED GUARDS
Nest guard expressions to ensure all values are set:
  if name <- findName(id)
    if score <- findScore(id)
      result: formatResult(name, score)
Each level guarantees its variable is set before the inner block executes.

SEQUENTIAL GUARDS
Use separate guard expressions in sequence when values are independent:
  if name <- findName(id)
    stdout.println(name)
  if score <- findScore(id)
    stdout.println(score)
Each guard independently checks its value.

GUARD WITH ELSE
Provide fallback behavior when a guard fails:
  if name <- findName(id)
    greet(name)
  else
    greetStranger()

WHY CHAIN GUARDS
Chaining guards ensures that every value used in a computation is guaranteed set. The compiler verifies each guard protects its variable scope. Without the guard, accessing the value would be unsafe (E08030).

See Q634 for guard basics. See Q163 for Optional unwrapping. See Q47 for Optional type patterns.

defines module qa.dataflow.chainedguard

  defines constant

    USER_ALICE <- 1

    USER_CHARLIE <- 3

    UNKNOWN_USER <- 999

  defines function

    findFirstName()
      -> userId as Integer
      <- rtn as String: String()

      if userId == USER_ALICE
        rtn: "Alice"
      else if userId == USER_CHARLIE
        rtn: "Charlie"

    findLastName()
      -> userId as Integer
      <- rtn as String: String()

      if userId == USER_ALICE
        rtn: "Smith"
      else if userId == USER_CHARLIE
        rtn: "Brown"

    findAge()
      -> userId as Integer
      <- rtn as Integer: Integer()

      if userId == USER_ALICE
        rtn: 30
      else if userId == USER_CHARLIE
        rtn: 25

    findDepartment()
      -> userId as Integer
      <- rtn as String: String()

      if userId == USER_ALICE
        rtn: "Engineering"

  defines program

    ChainedGuardDemo()
      stdout <- Stdout()

      //Nested guards: all must be set to build full profile
      if first <- findFirstName(USER_ALICE)
        if last <- findLastName(USER_ALICE)
          if age <- findAge(USER_ALICE)
            stdout.println(`${first} ${last}, age ${age}`)

      //Sequential independent guards
      if nameA <- findFirstName(USER_ALICE)
        stdout.println(`Found: ${nameA}`)

      if nameB <- findFirstName(UNKNOWN_USER)
        stdout.println(`Found: ${nameB}`)
      else
        stdout.println("User not found")

      //Guard with additional nested guard
      if dept <- findDepartment(USER_ALICE)
        if userName <- findFirstName(USER_ALICE)
          stdout.println(`${userName} works in ${dept}`)

      //Explicit isSet check pattern
      ageResult <- findAge(USER_CHARLIE)
      if ageResult?
        stdout.println(`Age found: ${ageResult}`)

Java: nested if-present checks on Optional, pyramid of doom. Kotlin: safe call chaining ?. operator, scope functions let/run. Swift: if let chaining, optional binding. Rust: pattern matching with nested Some/None. Go: repeated nil checks. EK9: nested guard expressions 'if var <- expr()' with compiler-enforced scoping.

E08030 E08040 absent access chain data-flow guard initialize isset multiple nested null-safe optional safe safety scope

stdout.println(`${first} ${last}`)
      if first <- findFirstName(USER_ALICE)

Explanation: Using first before its guard declaration is a forward reference error. The guard must be evaluated before using the variable. See ek9 -h E50001 for details.


Question: How should I compare two similar variables to avoid self-comparison errors?

What is self-comparison and how does EK9 detect it?

Why does EK9 reject comparing a variable to itself?

Answer:
EK9 detects when both sides of a comparison operator resolve to the same variable and raises E08081. This is always a copy-paste error.

THE PROBLEM
When duplicating comparison code, it is easy to write 'priceA == priceA' instead of 'priceA == priceB'. The result is a constant (always true for ==, always false for <>) and the intended comparison never happens. This bug is silent in most languages.

FLAGGED OPERATORS
Self-comparison is detected for: ==, <>, <, >, <=, >=, <=>, <~>, contains, and matches. All produce constant results when both operands are the same variable.

CORRECT PATTERN
Always compare two DIFFERENT variables. Use distinct meaningful names that make the intent clear:
  if currentPrice < previousPrice
  if scoreA == scoreB
  if leftValue <=> rightValue

COMMON MISTAKE
Copy-pasting a line like 'if priceA < priceB' and accidentally getting 'if priceA < priceA' when modifying. EK9 catches this immediately.

See Q318 for self-assignment detection. See Q558 for tautological conditions. See Q638 for named constant patterns. See Q639 for Boolean usage without literals.

defines module qa.comparison.safecompare

  defines function

    <?-
      Correct: compares two different variables (priceA vs priceB).
      Renaming one operand to match the other would trigger E08081.
    -?>
    findCheaperItem() as pure
      ->
        priceA as Float
        priceB as Float
      <- cheaperPrice as Float: priceA

      if priceB < priceA
        cheaperPrice: priceB

    <?-
      Correct: compares two distinct score variables.
      Each operand is a different parameter.
    -?>
    scoresMatch() as pure
      ->
        scoreA as Integer
        scoreB as Integer
      <- matched as Boolean: scoreA == scoreB

    <?-
      Correct: compares left and right boundary values.
      Distinct names make the intent clear.
    -?>
    isOverlapping() as pure
      ->
        leftStart as Integer
        leftEnd as Integer
        rightStart as Integer
        rightEnd as Integer
      <- overlapping as Boolean: leftStart <= rightEnd

      if leftEnd < rightStart
        overlapping: false
      else if rightEnd < leftStart
        overlapping: false

    <?-
      Correct: three-way comparison between distinct amounts.
    -?>
    findMiddleValue() as pure
      ->
        firstAmount as Float
        secondAmount as Float
        thirdAmount as Float
      <- middleAmount as Float: firstAmount

      if firstAmount >= secondAmount and firstAmount <= thirdAmount
        middleAmount: firstAmount
      else if secondAmount >= firstAmount and secondAmount <= thirdAmount
        middleAmount: secondAmount
      else
        middleAmount: thirdAmount

  defines program

    SafeComparisonDemo()
      stdout <- Stdout()

      cheaper <- findCheaperItem(29.99, 19.99)
      stdout.println(`Cheaper: ${cheaper}`)

      matched <- scoresMatch(85, 85)
      stdout.println(`Scores match: ${matched}`)

      overlap <- isOverlapping(leftStart: 1, leftEnd: 5, rightStart: 3, rightEnd: 8)
      stdout.println(`Overlapping: ${overlap}`)

      mid <- findMiddleValue(3.0, 1.0, 2.0)
      stdout.println(`Middle: ${mid}`)

Java: no self-comparison detection in javac. SpotBugs has limited checks. SonarQube detects some patterns. Rust: clippy eq_op lint warns on self-comparison. Go: go vet detects some self-assignments. Python: no built-in detection. C++: -Wtautological-compare warns on some patterns. EK9: mandatory compiler error E08081 for all comparison and containment operators.

E08081 comparison copy debug detect different equal pair paste pattern self variable

Explanation: Comparing a variable to itself always produces a constant result. This is a copy-paste error where both operands resolve to the same variable. See ek9 -h E08081 for details.

Explanation: Self-comparison with == always produces true. Use two different variables to make a meaningful comparison. See ek9 -h E08081 for details.


Question: Why should I use named constants instead of literal values in comparisons?

Why does EK9 reject literal-vs-literal comparisons?

Why does EK9 reject comparing two literal values?

How do I fix a magic number comparison error?

Answer:
EK9 detects when both sides of a comparison are literal values and raises E08082. The result is always a compile-time constant, making the comparison pointless.

THE PROBLEM
Comparing two literals like '18 > 10' always produces the same result. This is dead code: the condition is predetermined. It typically indicates that the developer meant to compare a variable against a constant but accidentally used two constants.

CORRECT PATTERN
Use a named constant on one side and a variable on the other:
  if age >= MINIMUM_AGE
  if temperature > BOILING_POINT
  if count == EXPECTED_COUNT

NAMED CONSTANTS
Declare constants in a defines constant block:
  defines constant
    MINIMUM_AGE <- 18
    MAXIMUM_SPEED <- 120
This gives the literal a meaningful name and avoids magic numbers throughout the code.

WHY NAMED CONSTANTS MATTER
Self-documenting: 'MINIMUM_AGE' explains the intent. Single source of truth: change the value in one place. Avoids E08082: variable vs constant is a genuine comparison. Avoids E11065: magic literals in comparisons.

See Q637 for self-comparison detection. See Q558 for tautological conditions. See Q639 for Boolean comparison patterns.

defines module qa.comparison.namedconstants

  defines constant

    MINIMUM_AGE <- 18

    MAXIMUM_AGE <- 120

    PASSING_SCORE <- 60

    SPEED_LIMIT <- 65

    BOILING_POINT <- 100.0

    FREEZING_POINT <- 0.0

  defines function

    <?-
      Correct: compares a variable against a named constant.
      Replacing the variable with a literal would trigger E08082.
    -?>
    isAdult() as pure
      -> age as Integer
      <- adult as Boolean: age >= MINIMUM_AGE

    <?-
      Correct: variable compared against named constant.
    -?>
    isPassing() as pure
      -> score as Integer
      <- passing as Boolean: score >= PASSING_SCORE

    <?-
      Correct: variable compared against named constants for range check.
    -?>
    isValidAge() as pure
      -> age as Integer
      <- valid as Boolean: age >= MINIMUM_AGE

      if age > MAXIMUM_AGE
        valid: false

    <?-
      Correct: Float variable compared against named constant.
    -?>
    isBoiling() as pure
      -> temperature as Float
      <- boiling as Boolean: temperature >= BOILING_POINT

    <?-
      Correct: uses named constants for range classification.
    -?>
    classifyTemperature() as pure
      -> temperature as Float
      <- classification as String: "normal"

      if temperature >= BOILING_POINT
        classification: "boiling"
      else if temperature <= FREEZING_POINT
        classification: "freezing"

  defines program

    NamedConstantsDemo()
      stdout <- Stdout()

      stdout.println(`Is 21 adult? ${isAdult(21)}`)
      stdout.println(`Is 15 adult? ${isAdult(15)}`)

      stdout.println(`Score 75 passing? ${isPassing(75)}`)
      stdout.println(`Score 40 passing? ${isPassing(40)}`)

      stdout.println(`Age 25 valid? ${isValidAge(25)}`)
      stdout.println(`Age 150 valid? ${isValidAge(150)}`)

      stdout.println(`100C boiling? ${isBoiling(100.0)}`)
      stdout.println(`50C boiling? ${isBoiling(50.0)}`)

      cls <- classifyTemperature(105.0)
      stdout.println(`105C: ${cls}`)

Java: no compile-time constant comparison detection. SonarQube flags magic numbers as code smell (optional). Rust: clippy has absurd_extreme_comparisons lint. Go: no magic number detection. Python: pylint magic-value-comparison. C++: -Wtautological-compare for some literal comparisons. EK9: E08082 mandatory error for literal-vs-literal comparison, E11065 for magic literals.

E08082 E11065 comparison constant dead literal magic migrate named number pattern value

Explanation: Comparing two literal values produces a compile-time constant, making the comparison dead code. Compare a variable against a named constant instead. See ek9 -h E08082 for details.

Explanation: Using a raw literal 120 in a comparison is a magic literal. Use the named constant MAXIMUM_AGE instead. See ek9 -h E11064 for details.


Question: Should I compare Boolean values with == true or == false?

What is E08083 redundant Boolean comparison?

How should I use Boolean values in conditions?

Answer:
EK9 detects redundant Boolean comparisons and raises E08083. Comparing a Boolean to a literal (true or false) is always redundant because the Boolean IS the condition.

THE PROBLEM
'if ready == true' is equivalent to 'if ready'. The '== true' adds nothing. Similarly, 'if ready == false' is equivalent to 'if not ready' (using Boolean negation). The extra comparison is dead code.

CORRECT PATTERNS
Direct use: 'if isReady' instead of 'if isReady == true'
Negation: 'if not isReady' instead of 'if isReady == false'
Combined: 'if isReady and isValid' instead of 'if isReady == true and isValid == true'

WHY THIS IS FLAGGED
Redundant Boolean comparisons indicate unclear thinking about Boolean logic. They also create opportunities for copy-paste errors: 'if x == true and y == true' could accidentally become 'if x == true and x == true' (self-comparison plus redundant comparison).

BOOLEAN OPERATORS
Use Boolean operators directly: and, or, not, xor. These express intent clearly without comparing to literals.

See Q637 for self-comparison detection. See Q638 for named constant patterns. See Q558 for tautological conditions.

defines module qa.comparison.booleanuse

  defines function

    <?-
      Correct: Boolean value used directly in condition.
      Adding '== true' would trigger E08083.
    -?>
    checkEligibility() as pure
      ->
        isAdult as Boolean
        hasConsent as Boolean
      <- eligible as Boolean: isAdult and hasConsent

    <?-
      Correct: Boolean negation using 'not' operator.
      Writing 'isBlocked == false' would trigger E08083.
    -?>
    canProceed() as pure
      ->
        isBlocked as Boolean
        isReady as Boolean
      <- proceed as Boolean: not isBlocked and isReady

    <?-
      Correct: combines multiple Boolean values directly.
      No comparison to literals needed.
    -?>
    classifyAccess() as pure
      ->
        isAuthenticated as Boolean
        isAuthorized as Boolean
        isActive as Boolean
      <- accessLevel as String: "denied"

      if isAuthenticated and isAuthorized and isActive
        accessLevel: "full"
      else if isAuthenticated and isActive
        accessLevel: "limited"
      else if isAuthenticated
        accessLevel: "readonly"

    <?-
      Correct: Boolean return from comparison, used directly.
    -?>
    isInRange() as pure
      ->
        testValue as Integer
        minBound as Integer
        maxBound as Integer
      <- inRange as Boolean: testValue >= minBound and testValue <= maxBound

  defines program

    BooleanWithoutLiteralsDemo()
      stdout <- Stdout()

      //Use variables for Boolean arguments
      adult <- true
      consented <- true
      eligible <- checkEligibility(isAdult: adult, hasConsent: consented)
      stdout.println(`Eligible: ${eligible}`)

      blocked <- false
      ready <- true
      proceed <- canProceed(isBlocked: blocked, isReady: ready)
      stdout.println(`Can proceed: ${proceed}`)

      authenticated <- true
      authorized <- true
      active <- true
      lvl <- classifyAccess(isAuthenticated: authenticated, isAuthorized: authorized, isActive: active)
      stdout.println(`Access: ${lvl}`)

      inRange <- isInRange(testValue: 5, minBound: 1, maxBound: 10)
      stdout.println(`In range: ${inRange}`)

Java: no detection of redundant boolean comparison. SonarQube flags 'if (x == true)' as minor code smell. Rust: clippy has bool_comparison lint (warn). Go: no detection. Python: pylint has C0121 singleton-comparison. Kotlin: detects some redundant boolean comparisons. EK9: mandatory compile error E08083 for comparing Boolean to literal.

E08083 E08084 boolean comparison condition direct false literal pattern redundant true

if isAuthenticated == true and isAuthorized == true and isActive == true

Explanation: Comparing a Boolean to the literal true is redundant. The Boolean IS the condition. Use it directly. See ek9 -h E08084 for details.

proceed as Boolean: isBlocked == false and isReady == true

Explanation: Comparing Booleans to literal values is redundant. Use not for negation and the Boolean directly for truth. See ek9 -h E08084 for details.


Question: When is an isSet check on a collection genuinely needed?

Is checking a list with ? redundant after construction?

What is E08088 redundant isSet check on collection?

When should I check if a collection is set?

Answer:
Collections (List, Dict) are always set after construction, even when empty. Checking a newly constructed collection with ? is always redundant (E08088). However, when a collection comes from a function return, the check may be genuinely needed.

WHEN ISSET IS REDUNDANT (E08088)
After constructing a collection directly:
  items <- List() of String
  if items?    <- this is ALWAYS true (redundant)
The compiler knows the constructor always returns a set value.

WHEN ISSET IS NEEDED
When the collection comes from a function that might return unset:
  if results <- searchItems(query)
    process(results)
The function might return an unset List to signal 'no results found'.

EMPTY VS UNSET
An empty list (0 items) is SET. It exists, it is valid, it just has no elements. An unset list does not yet have a meaningful value. These are different states. Use 'empty' to check for no elements, use '?' to check for existence.

CORRECT PATTERN FOR EMPTY CHECK
Use the empty operator to check for no elements:
  if items empty
    stdout.println('No items')
Do NOT use '?' to check if a list has elements.

See Q559 for redundant isSet detection. See Q558 for tautological conditions. See Q641 for return value capture.

defines module qa.comparison.collectionisset

  defines function

    <?-
      Simulates a search that might return an unset list.
    -?>
    searchByKeyword()
      -> keyword as String
      <- results as List of String: List() of String

      if keyword == "ek9"
        results += "Introduction to EK9"
        results += "EK9 Best Practices"

    <?-
      Simulates a lookup that might return unset.
    -?>
    findScores()
      -> studentId as Integer
      <- scores as List of Integer: List() of Integer

      if studentId == 1
        scores += 85
        scores += 92
        scores += 78

    <?-
      Pure function that processes a list.
    -?>
    countAboveThreshold() as pure
      ->
        items as List of Integer
        threshold as Integer
      <- counted as Integer: 0

      for item in items
        if item > threshold
          counted: counted + 1

  defines program

    CollectionIsSetDemo()
      stdout <- Stdout()

      //Guard on function return: genuinely needed
      if results <- searchByKeyword("ek9")
        stdout.println(`Found ${length results} results`)
        for item in results
          stdout.println(`  ${item}`)

      //Guard on function that might return unset
      if scores <- findScores(1)
        above80 <- countAboveThreshold(scores, 80)
        stdout.println(`Scores above 80: ${above80}`)

      //No results case
      if noResults <- searchByKeyword("unknown")
        stdout.println("Should not print")
      else
        stdout.println("No results for 'unknown'")

      //Empty check: appropriate when list IS set but might have no items
      knownScores <- findScores(1)
      if knownScores?
        if knownScores empty
          stdout.println("Empty scores")
        else
          stdout.println(`Has ${length knownScores} scores`)

Java: no tracking of collection initialization state. Kotlin: nullable collection vs empty collection distinguished by type. Rust: Option<Vec<T>> vs empty Vec<T>. Go: nil slice vs empty slice. Python: None vs empty list. EK9: tri-state (absent, unset, set), collections are always set after construction, E08088 for redundant isSet check.

E08088 E08089 check collection compare comparison constructed dict empty isSet list pattern redundant


Question: Why must I capture the return value of a pure function call?

What is E11051 discarded pure function return?

Why does EK9 reject calling a function without capturing its result?

Answer:
EK9 detects when a function with a return value is called as a bare statement and the return value is discarded. For pure functions this is always an error (E11051) because a pure function has no side effects, so calling it without using the result is completely pointless.

DISCARDED RETURN VALUE (E11050)
Calling a function that returns a value without capturing the result:
  computeTotal(items)    <- discards the returned total
The function was called, but its result was thrown away. This usually means the developer forgot the assignment.

DISCARDED PURE RETURN (E11051)
Calling a pure function as a bare statement is especially bad because pure functions have NO side effects. The call does nothing observable. It is dead code.

DISCARDED CONSTRUCTOR (E11055)
Creating an object without capturing the reference:
  Sensor(98.6)    <- creates and immediately discards
This is always a bug. If you need side effects, use a function.

DISCARDED RESULT OR OPTIONAL (E11052)
Calling a function that returns Result or Optional and ignoring the result defeats the purpose of these types.

CORRECT PATTERN
Always capture the return value of functions that return values:
  total <- computeTotal(items)
  processed <- transform(input)
  newList <- List() of String

See Q316 for discarded return detection. See Q640 for collection patterns. See Q635 for pure mutation restrictions.

defines module qa.comparison.capturereturn

  defines constant

    SUM_THRESHOLD <- 6

  defines function

    <?-
      Pure function: its return value MUST be captured.
      Calling this without capturing triggers E11051.
    -?>
    computeSum() as pure
      ->
        valueA as Integer
        valueB as Integer
      <- total as Integer: valueA + valueB

    <?-
      Pure function: return value must be captured.
    -?>
    formatPrice() as pure
      -> price as Float
      <- formatted as String: `Price: ${price}`

    <?-
      Pure function with string processing.
    -?>
    buildLabel() as pure
      ->
        prefix as String
        suffix as String
      <- label as String: `${prefix}-${suffix}`

    <?-
      Non-pure function with return value.
      Return should still be captured (E11050).
    -?>
    fetchAndLog()
      -> itemId as Integer
      <- itemName as String: `item-${itemId}`

  defines program

    CaptureReturnDemo()
      stdout <- Stdout()

      //Correct: capture the return value
      total <- computeSum(10, 20)
      stdout.println(`Sum: ${total}`)

      //Correct: capture formatted string
      priceStr <- formatPrice(9.99)
      stdout.println(priceStr)

      //Correct: capture and use
      label <- buildLabel("order", "001")
      stdout.println(label)

      //Correct: capture non-pure return
      itemName <- fetchAndLog(42)
      stdout.println(`Fetched: ${itemName}`)

      //Correct: use return value directly in expression
      doubled <- computeSum(5, 5) * 2
      stdout.println(`Doubled: ${doubled}`)

      //Correct: use in condition
      if computeSum(3, 4) > SUM_THRESHOLD
        stdout.println("Sum exceeds threshold")

Java: return values can always be silently discarded, no warning by default. IntelliJ warns on discarded return from pure-annotated methods (optional). Rust: #[must_use] attribute warns when return value is discarded (opt-in). Go: error return values often silently discarded (common bug source). Kotlin: no enforcement. C++: [[nodiscard]] attribute (C++17, opt-in). EK9: mandatory compiler error E11050/E11051 for discarded return values, especially pure functions.

E11050 E11051 E11052 E11055 capture compare comparison constructor dead discard error function guard immutable ok pattern pure result return side-effect


Question: How does constructor-based type inference work for generic classes?

How does EK9 infer generic type parameters from constructor arguments?

What is E06030 generic constructor inappropriate?

Why must constructor parameter count match type parameter count for inference?

Answer:
EK9 infers generic type parameters from constructor arguments. For inference to work, the number of constructor parameters must match the number of type parameters, and each constructor parameter type must correspond to the type parameter in the same position.

INFERRENCE RULE
A generic class 'Box of type T' with constructor 'Box(val as T)' allows inference:
  box <- Box(42)
The compiler sees Integer argument, infers T = Integer.

MATCHING REQUIREMENT
For multi-parameter generics, parameter count must match:
  Pair of type (A, B)
    Pair(a as A, b as B)
Now 'Pair("hello", 42)' infers A=String, B=Integer.

EXPLICIT ALTERNATIVE
When inference cannot work, specify types explicitly:
  container <- Container() of String

See Q194 for generic class basics. See Q196 for multi-parameter generics. See Q643 for two-constructor requirement. See Q644 for constructor parameter type matching.
See Q681 for generic parameterization syntax. See Q682 for generic encapsulation. See Q683 for generic constructor rules.

defines module qa.genericsdeep.constructorinference

  defines class

    <?-
      Single type parameter with matching constructor.
      Enables type inference from constructor argument.
    -?>
    Box of type T
      content as T?

      Box() as pure
        content :=? T()

      Box() as pure
        -> initialValue as T
        content :=? initialValue

      getContent()
        <- rtn as T: T()
        rtn :=? T(content)

      default operator ?

    <?-
      Multi-parameter generic with matching constructor.
      Two type params, two constructor params, in order.
    -?>
    Mapping of type (K, V)
      theKey as K?
      theVal as V?

      Mapping() as pure
        theKey :=? K()
        theVal :=? V()

      Mapping() as pure
        ->
          k as K
          v as V
        theKey :=? k
        theVal :=? v

      key()
        <- rtn as K: K()
        rtn :=? K(theKey)

      val()
        <- rtn as V: V()
        rtn :=? V(theVal)

      default operator ?

  defines program

    GenericConstructorInferenceDemo()
      stdout <- Stdout()

      // === INFERENCE FROM SINGLE ARGUMENT ===

      intBox <- Box(42)
      stdout.println(`Integer box set: ${intBox?}`)

      strBox <- Box("hello")
      stdout.println(`String box set: ${strBox?}`)

      // === INFERENCE FROM MULTIPLE ARGUMENTS ===

      entry <- Mapping("name", 42)
      stdout.println(`Mapping set: ${entry?}`)

      // === EXPLICIT WHEN NO ARGS ===

      emptyBox <- Box() of String
      stdout.println(`Empty box set: ${emptyBox?}`)

Java: type inference via diamond operator (<>) and constructor args. Rust: turbofish syntax (::<Type>) when inference fails. Go: type inference from function arguments (Go 1.18+). Kotlin: inferred from constructor args or explicit <Type>. EK9: inference from constructor args when param count matches type param count, explicit 'of Type' otherwise.

E06030 class constant constructor generic inference instantiation match parameter type type-parameter

Box of type T
      content as T?

      default Box() as pure

      Box() as pure
        ->
          initialValue as T
          extra as Integer

Explanation: Generic constructor type inference requires the number of constructor parameters to match the number of type parameters. Box has one type parameter T, so the inferred constructor must have exactly one parameter of type T. Adding extra parameters breaks inference. See ek9 -h E06030 for details.


Question: Why must a generic class have both a default and an inferred-type constructor?

What is E06040 generic type requires two constructors?

Why do I need two constructors for a generic class in EK9?

What constructors does a generic type need in EK9?

Answer:
EK9 requires generic types to have exactly two constructors: a default (no-argument) constructor and an inferred-type constructor whose parameters match the type parameters.

TWO CONSTRUCTOR RULE
Generic types need:
1. A default constructor: 'default MyType()' for creating unset instances
2. An inferred constructor: 'MyType(val as T)' for type-inferred creation

WHY BOTH ARE NEEDED
The default constructor enables:
  container <- Container() of String
Creates an instance when no initial value is available.

The inferred constructor enables:
  container <- Container("hello")
Type parameter inferred from the argument.

DEFAULT KEYWORD
The 'default' keyword generates a no-argument constructor:
  default Container() as pure
This creates a Container where T is unset.

See Q642 for constructor inference. See Q644 for constructor argument type matching. See Q194 for generic class basics.

defines module qa.genericsdeep.twoconstructors

  defines class

    <?-
      Correct: generic class with both default and inferred constructors.
      Default constructor creates unset instance.
      Inferred constructor enables type inference.
    -?>
    Holder of type T
      stored as T?

      Holder() as pure
        stored :=? T()

      Holder() as pure
        -> initial as T
        stored :=? initial

      retrieve()
        <- rtn as T: T()
        rtn :=? T(stored)

      default operator ?

    <?-
      Another correct example with multi-parameter generic.
      Both default and inferred constructors present.
    -?>
    KeyVal of type (K, V)
      theKey as K?
      theValue as V?

      KeyVal() as pure
        theKey :=? K()
        theValue :=? V()

      KeyVal() as pure
        ->
          k as K
          v as V
        theKey :=? k
        theValue :=? v

      default operator ?

  defines program

    TwoConstructorDemo()
      stdout <- Stdout()

      // === DEFAULT CONSTRUCTION (needs explicit type) ===

      emptyHolder <- Holder() of Integer
      stdout.println(`Empty holder set: ${emptyHolder?}`)

      // === INFERRED CONSTRUCTION ===

      filledHolder <- Holder("data")
      stdout.println(`Filled holder set: ${filledHolder?}`)

      // === MULTI-PARAM DEFAULT ===

      emptyKV <- KeyVal() of (String, Float)
      stdout.println(`Empty KV set: ${emptyKV?}`)

      // === MULTI-PARAM INFERRED ===

      filledKV <- KeyVal("price", 9.99)
      stdout.println(`Filled KV set: ${filledKV?}`)

Java: no constructor requirements for generics, relies on 'new T()' (impossible due to erasure). C++: default-constructible via 'requires std::default_initializable<T>'. Rust: Default trait optional. Go: zero-value initialization built in. Kotlin: no mandatory constructor count. EK9: two constructors required, default + inferred, enabling both patterns.

E06040 class constant constructor default generic inferred instantiation requirement two type-parameter

      Holder() as pure
        -> initial as T

Explanation: A generic type requires both a default (no-argument) constructor and an inferred-type constructor; deleting the no-arg constructor leaves only the inferred one and fails. See ek9 -h E06040 for details.


Question: How must constructor argument types match generic type parameters?

What is E06050 constructor argument must match parametric types?

Why must constructor parameter order match type parameter order?

How does EK9 validate generic constructor signatures?

Answer:
When a generic type has an inferred constructor, the constructor parameter types must match the type parameters in both type and order. 'MyType of type (A, B)' must have constructor 'MyType(a as A, b as B)' with A first and B second.

ORDER MATTERS
For 'Pair of type (A, B)', the constructor must be:
  Pair(first as A, second as B)
Not 'Pair(first as B, second as A)' which reverses the types.

TYPE MATCHING
Each constructor parameter must use the corresponding type parameter:
  Position of type T
    Position(x as T)  // T matches the type parameter

WHY THIS RESTRICTION
The compiler uses the constructor to infer type arguments. If 'Pair("hello", 42)' is written, the compiler maps argument 1 to A (String) and argument 2 to B (Integer). The positional correspondence must be unambiguous.

See Q642 for constructor inference. See Q643 for two-constructor requirement. See Q645 for public constructor requirement.

defines module qa.genericsdeep.constructorargtypes

  defines class

    <?-
      Single-param: constructor argument type matches type parameter T.
    -?>
    Wrapper of type T
      wrapped as T?

      Wrapper() as pure
        wrapped :=? T()

      Wrapper() as pure
        -> item as T
        wrapped :=? item

      unwrap()
        <- rtn as T: T()
        rtn :=? T(wrapped)

      default operator ?

    <?-
      Multi-param: constructor argument order matches type parameter order.
      (A, B) maps to (first, second) positionally.
    -?>
    Entry of type (A, B)
      first as A?
      second as B?

      Entry() as pure
        first :=? A()
        second :=? B()

      Entry() as pure
        ->
          a as A
          b as B
        first :=? a
        second :=? b

      getFirst()
        <- rtn as A: A()
        rtn :=? A(first)

      getSecond()
        <- rtn as B: B()
        rtn :=? B(second)

      default operator ?

  defines program

    ConstructorArgTypeDemo()
      stdout <- Stdout()

      // === SINGLE PARAM INFERENCE ===

      intWrap <- Wrapper(100)
      stdout.println(`Int wrapper: ${intWrap?}`)

      strWrap <- Wrapper("text")
      stdout.println(`String wrapper: ${strWrap?}`)

      // === MULTI PARAM INFERENCE (positional) ===

      record <- Entry("id-001", 42)
      stdout.println(`Entry set: ${record?}`)

      first <- record.getFirst()
      if first?
        stdout.println(`First: ${first}`)

Java: no constructor-to-type-param mapping (erasure). C++: template argument deduction from constructor (C++17 CTAD). Rust: no constructor syntax, uses 'new' pattern. Go: no constructor, zero-value + explicit init. Kotlin: constructor infers from usage like Java. EK9: strict positional mapping between type params and constructor params.

E06050 argument constant constructor generic inference match order positional type type-parameter

Entry of type (A, B)
      first as A?
      second as B?

      default Entry() as pure

      Entry() as pure
        ->
          a as B
          b as A

Explanation: Constructor parameter types must match the type parameters in the same positional order. For 'Entry of type (A, B)', the first constructor parameter must be type A and the second must be type B. Reversing them breaks the positional correspondence the compiler uses for type inference. See ek9 -h E06050 for details.


Question: Can a generic type constructor be private?

What is E06060 generic constructors must be public?

Can I use private constructors in a generic class?

When is a private constructor allowed on a generic type?

Answer:
EK9 generic type constructors are public, with ONE exception: the no-argument DEFAULT constructor may be declared 'default private'. This is the escape hatch for a generic that has an uninitialised conceptual-T property it cannot give an unset value to - privacy then stops external code creating a partially-constructed (null-field) instance, while the inferred constructor still supplies a real value.

THE PUBLIC RULE
The inferred (parameterised) constructor must be public so the compiler can infer type arguments and instantiate from any context:
  Container of type T
    item as T?
    Container() as pure
      item :=? T()                  // give the optional field an unset T - never null
    Container(val as T) as pure       // inferred constructor: must be public
      item :=? val

THE PRIVATE NO-ARG ESCAPE HATCH
When a generic cannot give its field an unset 'T()' (T may be abstract, may hold a function, or may lack a public no-arg constructor), declare the default private:
  default private Container() as pure
External code then cannot create an empty Container; it must pass a value to the inferred constructor.

STILL NOT ALLOWED
1. A private or protected PARAMETERISED constructor - type inference needs it public.
2. A protected no-arg default - it would still allow subclass uninitialised construction.

See Q642 for constructor inference. See Q643 for two-constructor requirement. See Q194 for generic class basics.
See Q682 for generic encapsulation. See Q683 for generic constructor rules.

defines module qa.genericsdeep.publicconstructor

  defines class

    <?-
      Correct: both constructors are public (default access).
      Generic types require public constructors for inference.
    -?>
    Cache of type T
      stored as T?

      Cache() as pure
        stored :=? T()

      Cache() as pure
        -> initial as T
        stored :=? initial

      retrieve()
        <- rtn as T: T()
        rtn :=? T(stored)

      default operator ?

    <?-
      Correct: using 'default' keyword generates public constructor.
    -?>
    Slot of type T
      item as T?

      Slot() as pure
        item :=? T()

      Slot() as pure
        -> initialValue as T
        item :=? initialValue

      default operator ?

  defines program

    PublicConstructorDemo()
      stdout <- Stdout()

      // === PUBLIC CONSTRUCTORS ENABLE INFERENCE ===

      intCache <- Cache(42)
      stdout.println(`Cache set: ${intCache?}`)

      strSlot <- Slot("data")
      stdout.println(`Slot set: ${strSlot?}`)

      // === PUBLIC DEFAULT ENABLES EXPLICIT TYPING ===

      emptyCache <- Cache() of Float
      stdout.println(`Empty cache set: ${emptyCache?}`)

Java: generic classes can have private constructors (factory pattern). C++: templates can have private constructors. Rust: no constructor keyword, but 'new' can be private. Go: unexported functions serve as private constructors. EK9: generic constructors must be public for type inference and external instantiation.

E06060 access constant constructor generic instantiation private public type-parameter visibility

private Cache() as pure
        -> initial as T
        stored :=? initial

Explanation: Generic type constructors must be public. A private constructor would prevent type inference and instantiation from external code. The compiler needs unrestricted access to constructors for generic type instantiation. See ek9 -h E06060 for details.


Question: Where is type inference not supported in generic type and function bodies?

What is E06070 type inference not supported in generic context?

When must I use explicit types inside generic definitions?

Why cannot I use type inference inside a generic class body?

Answer:
Inside a generic type or function body, EK9 does not support type inference for local variables when the type would depend on the generic parameter. You must use explicit type declarations.

INFERENCE LIMITATION
Inside a generic definition, the compiler cannot infer types that depend on T because T is not yet known. Explicit typing is required when creating instances of T or using T-typed expressions.

EXPLICIT RETURN TYPES
Generic functions must declare return types explicitly:
  transform() of type (S, T) as open
    -> source as S
    <- target as T: T()

FIELD DECLARATIONS
Fields in generic classes must use the type parameter explicitly:
  Container of type T
    item as T?

WHEN INFERENCE WORKS
Type inference works at the CALL SITE, not inside the generic body:
  intContainer <- Container(42)  // inference works here
But inside Container's methods, T must be used explicitly.

See Q642 for constructor inference at call site. See Q648 for function constraints. See Q194 for generic class basics.

defines module qa.genericsdeep.inferencelimits

  defines function

    <?-
      Correct: explicit types used throughout the generic body.
      Return type explicitly declared as T.
    -?>
    identity() of type T as open
      -> item as T
      <- rtn as T: T(item)

    <?-
      Correct: multi-param generic with explicit return type.
      Both S and T used with explicit type annotations.
    -?>
    converter() of type (S, T) as open
      -> source as S
      <- target as T: T()
      require source?

  defines class

    <?-
      Correct: all fields and method return types use T explicitly.
      No reliance on type inference inside the generic body.
    -?>
    Stack of type T
      items as List of T?

      Stack()
        items :=? List() of T

      Stack()
        -> initial as T
        items: List() of T
        items += initial

      push()
        -> item as T
        if not items?
          items: List() of T
        items += item

      count() as pure
        <- rtn as Integer: 0
        if items?
          rtn: length items

      default operator ?

  defines program

    InferenceLimitsDemo()
      stdout <- Stdout()

      // === INFERENCE AT CALL SITE (works) ===

      result <- identity(42)
      stdout.println(`Identity: ${result}`)

      // === EXPLICIT TYPE AT CALL SITE ===

      stack <- Stack("first")
      stack.push("second")
      stack.push("third")
      stdout.println(`Stack count: ${stack.count()}`)

Java: type inference works inside generics (var keyword, diamond). C++: auto deduction inside templates. Rust: type inference inside generic functions with compiler assistance. Go: type inference limited inside generics too. Kotlin: type inference works inside generic bodies. EK9: no inference inside generic bodies, explicit T usage required.

E06070 body context explicit function generic inference limitation parameter type type-parameter

Explanation: Integer has no toString() method in EK9. Use the $ prefix operator or string interpolation. See ek9 -h E50060 for details.


Question: Why cannot I use a function type as a generic constraint?

What is E06080 constrained functions not supported?

Can I constrain a generic type parameter to a function type?

Why does EK9 prohibit function types in constrain by?

Answer:
EK9 does not allow function types as constraints in 'constrain by'. Only class types and traits can be used as constraints. Function types have a different dispatch model and cannot serve as type bounds.

PROHIBITED PATTERN
You cannot write:
  Handler of type T constrain by SomeFunction  // ERROR E06080
Function types cannot be used with 'constrain by'.

WHY PROHIBITED
Function types in EK9 define signatures (input types and return type). They are not class hierarchies with methods. Constraining T to a function would mean T must be callable, but EK9's generic system works with method access, not call-ability.

CORRECT ALTERNATIVES
Use a trait or abstract class as the constraint:
  Handler of type T constrain by Processable
Define a trait with the methods you need, then constrain by that trait.

FUNCTION PARAMETERS
If you need to pass functions to generics, use function-typed parameters:
  process()
    -> mapper as Mapper
Where Mapper is a defined function type, passed as a parameter rather than a constraint.

See Q195 for constrain by with classes. See Q646 for inference limits. See Q649 for generic function implementation.

defines module qa.genericsdeep.nofunctionconstraint

  defines trait

    <?-
      Correct approach: define a trait with the methods you need.
      Then constrain by the trait, not by a function type.
    -?>
    Describable
      description() as pure abstract
        <- rtn as String?

  defines class

    Planet with trait of Describable
      planetName as String?

      default private Planet() as pure

      Planet() as pure
        -> n as String
        planetName :=? String(n)

      override description() as pure
        <- rtn as String: String(planetName)

      override operator ? as pure
        <- rtn as Boolean: planetName?

    Star with trait of Describable
      starName as String?

      default private Star() as pure

      Star() as pure
        -> n as String
        starName :=? String(n)

      override description() as pure
        <- rtn as String: String(starName)

      override operator ? as pure
        <- rtn as Boolean: starName?

  defines function

    <?-
      A function type - cannot be used as a constraint.
    -?>
    CheckFunction()
      -> arg0 as String
      <- rtn as Boolean: arg0?

    <?-
      Correct: constrain by a trait, not a function.
      The trait provides the methods needed inside the generic.
    -?>
    describeItem() of type T constrain by Describable as open
      -> item as T
      <- rtn as String: item.description()

  defines program

    NoFunctionConstraintDemo()
      stdout <- Stdout()

      planet <- Planet("Mars")
      planetDesc <- describeItem(planet)
      stdout.println(`Planet: ${planetDesc}`)

      star <- Star("Polaris")
      starDesc <- describeItem(star)
      stdout.println(`Star: ${starDesc}`)

Java: can use functional interfaces as bounds '<T extends Function<S,R>>'. C++: concepts can constrain to callable. Rust: trait bounds with Fn/FnMut/FnOnce. Go: interfaces can include method signatures (callable). Kotlin: can bound by function types. EK9: function types excluded from 'constrain by', use traits instead.

E06080 bound constant constrain constraint function generic prohibited trait type-parameter

Explanation: The constraining type must be a resolvable class or trait. Using a name that does not exist triggers a type-not-resolved error. See ek9 -h E50010 for details.

describeItem() of type T constrain by CheckFunction as open

Explanation: Generic types cannot be constrained by function types. Use a trait or class as the constraint. See ek9 -h E06080 for details.


Question: Why cannot I use named dynamic classes inside a generic type definition?

What is E06090 named dynamic class in generic?

Can I create dynamic classes inside a generic class body?

Why are named dynamic classes prohibited in generic definitions?

Answer:
EK9 does not allow named dynamic classes inside generic type or function definitions. Dynamic classes created inside generics must be anonymous (unnamed).

PROHIBITED PATTERN
Inside a generic body, you cannot define a named dynamic class because the name would need to be monomorphised for each type instantiation, creating ambiguous class identities.

CORRECT PATTERN
Use anonymous dynamic classes with traits:
  handler <- () with trait of EventHandler as class
    override handle()
      <- rtn as Boolean: true
No name means no monomorphisation conflict.

ALTERNATIVE: DEFINE OUTSIDE
Define the class outside the generic, then use it inside:
  ConcreteHandler with trait of EventHandler
    ...
Then reference ConcreteHandler from within the generic body.

See Q647 for function constraint limits. See Q646 for type inference limits. See Q194 for generic class basics.

defines module qa.genericsdeep.nonameddynamic

  defines trait

    <?-
      Trait used as contract for dynamic classes.
    -?>
    Renderable
      render() as pure abstract
        <- rtn as String?

  defines class

    <?-
      Named class defined OUTSIDE the generic (correct approach).
      Can be referenced from inside generic bodies.
    -?>
    TextRenderer with trait of Renderable
      content as String?

      default private TextRenderer() as pure

      TextRenderer() as pure
        -> txt as String
        content :=? String(txt)

      override render() as pure
        <- rtn as String: String(content)

      override operator ? as pure
        <- rtn as Boolean: content?

    <?-
      Generic class that uses the trait, not named dynamic classes.
    -?>
    Displayer of type T constrain by Renderable
      item as T?

      default private Displayer() as pure

      Displayer() as pure
        -> inputItem as T
        item :=? inputItem

      show()
        <- rtn as String: "empty"
        if item?
          rtn: item.render()

      default operator ?

  defines program

    NoDynamicInGenericDemo()
      stdout <- Stdout()

      renderer <- TextRenderer("EK9 generics")
      displayer <- Displayer(renderer)
      output <- displayer.show()
      stdout.println(`Display: ${output}`)

Java: anonymous inner classes freely used inside generics. C++: lambdas and local classes inside templates. Rust: closures inside generic functions. Go: anonymous structs (limited). Kotlin: anonymous objects inside generics. EK9: named dynamic classes prohibited in generic bodies, use anonymous or define outside.

E06090 anonymous capture class closure dynamic generic monomorphisation named prohibited type-parameter

named as Renderable: MyRenderer() with trait of Renderable as class
          override render()
            <- rtn as String: "dynamic"

Explanation: Named dynamic classes like 'MyRenderer' are not allowed inside generic type definitions. The generic body is monomorphised for each type parameter, so a named class would be created multiple times. Use anonymous dynamic classes instead. See ek9 -h E06090 for details.


Question: Why must generic functions provide an implementation?

What is E06100 generic function implementation required?

Can I declare a generic function without a body?

How do generic function templates work in EK9?

Answer:
EK9 requires generic functions to provide an implementation. Unlike abstract methods in classes, generic functions are templates that must contain code to execute when instantiated.

IMPLEMENTATION REQUIRED
A generic function must have a body:
  transform() of type T as open
    -> item as T
    <- rtn as T: T(item)
The body provides the default behavior.

AS OPEN FOR EXTENSION
Mark a generic function 'as open' to allow specialised implementations:
  compare() of type T as open
    -> a as T, b as T
    <- rtn as Integer: a <=> b
Concreted implementations can override this default.

DYNAMIC INSTANTIATION
Generic functions create concrete instances:
  intCompare <- () is compare of Integer as function
This uses the template's default implementation.

CUSTOM IMPLEMENTATION
Dynamic functions can provide custom bodies:
  customCompare <- () is compare of Integer as function
    rtn: b <=> a  // reversed

See Q197 for extending generics. See Q647 for function constraint rules. See Q194 for generic class basics.

defines module qa.genericsdeep.functionimplementation

  defines function

    <?-
      Correct: generic function with implementation body.
      'as open' allows extension when parameterised.
    -?>
    doubleValue() of type T as open
      -> item as T
      <- rtn as T: item + item

    <?-
      Correct: multi-param generic function with body.
    -?>
    selectFirst() of type (A, B) as open
      ->
        first as A
        second as B
      <- rtn as A: A(first)
      require second?

    <?-
      Correct: constrained generic function with implementation.
    -?>
    stringify() of type T as open
      -> item as T
      <- rtn as String: $item

  defines program

    GenericFunctionImplDemo()
      stdout <- Stdout()

      // === USING DEFAULT IMPLEMENTATIONS ===

      doubled <- doubleValue(21)
      stdout.println(`Doubled: ${doubled}`)

      strDoubled <- doubleValue("ab")
      stdout.println(`String doubled: ${strDoubled}`)

      // === MULTI-PARAM GENERIC FUNCTION ===

      selected <- selectFirst("chosen", 99)
      stdout.println(`Selected: ${selected}`)

      // === STRINGIFY GENERIC ===

      asStr <- stringify(3.14)
      stdout.println(`Stringified: ${asStr}`)

Java: abstract methods exist but generic methods always have bodies. C++: templates always have bodies (no separate declaration for function templates). Rust: generic functions always have bodies. Go: generic functions always have bodies. Kotlin: generic functions always have bodies. EK9: same requirement, enforced with E06100 if body missing.

E06100 body function generic implementation open required template type-parameter

doubleValue() of type T as open
      -> item as T
      <- rtn as T?

Explanation: Generic functions must provide an implementation body. Unlike abstract methods in classes, generic functions are templates that generate concrete code when instantiated with specific types. A generic function without a body has nothing to instantiate. See ek9 -h E07110 for details.


Question: What operators can be used with function-typed type parameters in generics?

What is E06120 function used in generic operator restriction?

Why can I only use the ? operator on function type parameters?

What limitations exist for function types in generic contexts?

Answer:
When a generic type parameter happens to be filled with a function type, only the '?' (isSet) operator is supported. Other operators like '+', '-', ':=:', ':^:', ':~:' are not applicable to function types.

ONLY QUESTION MARK
Function types support only '?' to check if they are set:
  if callback?
    // function is assigned and ready to call

WHY LIMITED
Function types represent callable code, not data values. Arithmetic operators (+, -, *) make no sense on functions. Copy/merge/replace operators (:=:, :~:, :^:) would have unclear semantics for closures with captured state.

CORRECT USAGE
Check if a function parameter is set, then call it:
  if handler?
    result <- handler(inputData)

DESIGN PRINCIPLE
Functions are called, not manipulated. EK9 treats functions as first-class values for passing and storing, but arithmetic and mutation operators are reserved for data types.

See Q647 for function constraint prohibition. See Q649 for generic function implementation. See Q58 for generic functions.

defines module qa.genericsdeep.functioningeneric

  defines function

    <?-
      A simple function type that can be used as a type parameter.
    -?>
    formatter() as open
      -> item as String
      <- rtn as String: item

  defines class

    <?-
      Generic class that stores a value of type T.
      When T is a function type, only ? operator works.
      This example uses T with standard types, showing
      the ? operator that works for all types including functions.
    -?>
    Holder of type T
      stored as T?

      //A generic that may hold a FUNCTION cannot give its field an unset 'T()' (you cannot construct a
      //function - E06110), so the no-arg default is 'default private': you cannot externally create an
      //empty one, you must use the inferred constructor with a value.
      default private Holder() as pure

      Holder() as pure
        -> initialItem as T
        stored :=? initialItem

      isPresent() as pure
        <- rtn as Boolean: stored?

      default operator ?

  defines program

    FunctionInGenericDemo()
      stdout <- Stdout()

      // === STANDARD TYPE: all operators work ===

      intHolder <- Holder(42)
      stdout.println(`Int holder present: ${intHolder.isPresent()}`)

      strHolder <- Holder("hello")
      stdout.println(`String holder present: ${strHolder.isPresent()}`)

      // === FUNCTION TYPE: only ? works ===

      upperFormatter <- () is formatter as function
        rtn: item.upperCase()

      funcHolder <- Holder(upperFormatter)
      stdout.println(`Function holder present: ${funcHolder.isPresent()}`)

      // === LIMIT: a function-holding generic cannot be empty-constructed ===
      // 'Holder() of Integer' is NOT available: the no-arg default is private because 'T()' is unavailable
      // for a generic that may hold a function. Construct with a value via the inferred constructor instead.

Java: function interfaces support only apply/invoke. C++: std::function supports only call operator. Rust: Fn traits define only call semantics. Go: functions only support calling. Kotlin: function types support only invoke. EK9: function type parameters in generics limited to '?' operator only.

E06120 callable function generic isSet limitation operator question type-parameter

combine() as pure
        -> other as T
        <- rtn as T: stored + other

Explanation: When a generic type parameter is filled with a function type, only the '?' (isSet) operator is supported. Operators like '+', '-', ':=:', ':^:', ':~:' are not applicable to function types. Functions are called, not manipulated with arithmetic or mutation operators. See ek9 -h E50060 for details.


Question: How must constrained type constructors match the parameterising type?

What is E06130 constrained type constructor missing?

Why must constrained generic subtypes have matching constructors?

How do constructor requirements flow from constraints to subtypes?

Answer:
When a generic type is constrained with 'constrain by', the constraining type's constructors must be present on any type used to parameterise the generic. If the constraining type has a String constructor, any actual type argument must also have a String constructor.

CONSTRUCTOR MATCHING
If Shape has 'Shape(name as String)', then any T used in 'of type T constrain by Shape' must also have a String constructor.

WHY REQUIRED
Generic code may create instances of T using constructors from the constraining type. If the constructor does not exist, the code would fail at instantiation.

CORRECT PATTERN
Ensure subtypes mirror the constructor signatures:
  Shape as abstract
    Shape(n as String)
  Circle is Shape
    Circle(n as String)  // matches Shape's constructor
      super(n)

See Q195 for constrain by basics. See Q642 for constructor inference. See Q647 for function constraint limits.

defines module qa.genericsdeep.constraintconstructormatch

  defines class

    <?-
      Abstract base class used as constraint.
      Has both default and String constructors.
    -?>
    Vehicle as abstract
      vehicleName as String: String()

      default Vehicle() as pure

      Vehicle() as pure
        -> n as String
        vehicleName :=? String(n)

      name() as pure
        <- rtn as String: String(vehicleName)

      default operator ?

    <?-
      Concrete subtype with matching constructors.
      Both default and String constructors present (matching Vehicle).
    -?>
    Truck is Vehicle
      payload <- Integer()

      default Truck() as pure

      Truck() as pure
        -> n as String
        super(n)

      Truck() as pure
        ->
          n as String
          cap as Integer
        super(n)
        payload :=? cap

      override operator ? as pure
        <- rtn as Boolean: name()?

    <?-
      Subtype missing String constructor (for E06130 mutation testing).
    -?>
    Bicycle is Vehicle

      default Bicycle() as pure

      override operator ? as pure
        <- rtn as Boolean: name()?

    <?-
      Another concrete subtype with matching constructors.
    -?>
    Sedan is Vehicle
      doors as Integer: 4

      default Sedan() as pure

      Sedan() as pure
        -> n as String
        super(n)

      override operator ? as pure
        <- rtn as Boolean: name()?

  defines function

    <?-
      Constrained generic function.
      T must be a Vehicle, so T has name() method available.
    -?>
    describeVehicle() of type T constrain by Vehicle as open
      -> item as T
      <- rtn as String: item.name()

  defines program

    ConstraintConstructorDemo()
      stdout <- Stdout()

      truck <- Truck("Big Rig")
      truckDesc <- describeVehicle(truck)
      stdout.println(`Truck: ${truckDesc}`)

      sedan <- Sedan("Family Car")
      sedanDesc <- describeVehicle(sedan)
      stdout.println(`Sedan: ${sedanDesc}`)

Java: no constructor inheritance or matching requirement (erasure). C++: concepts can require constructibility. Rust: trait bounds define method requirements, not constructors. Go: no constructor concept. Kotlin: no constructor matching for generics. EK9: constraining type constructors must exist on parameterising type.

E06130 constant constraint constructor generic match missing parameter subtype type-parameter

Truck() as pure
        -> n as String
        super(n)

Truck() as pure
        -> cap as Integer
        payload :=? cap

Explanation: When a generic type is constrained by Vehicle (which has a String constructor), any type used to parameterise it must also have a matching String constructor. Removing the String constructor from Truck while keeping only a non-matching constructor triggers E50060. See ek9 -h E50060 for details.

bicycle <- Bicycle()
      bicycleDesc <- describeVehicle(bicycle)
      stdout.println(`Bicycle: ${bicycleDesc}`)

Explanation: Bicycle extends Vehicle but is missing the String constructor that Vehicle defines. When describeVehicle (constrained by Vehicle) is parameterised with Bicycle, the compiler detects the missing constructor match and triggers E06130. See ek9 -h E06130 for details.


Question: Why must Result be used with two different types?

What is E06190 Result must have different types?

Can I use Result with the same type for success and error?

How does EK9 prevent Result type ambiguity?

Answer:
EK9 requires Result to be parameterised with two DIFFERENT types. Using 'Result of (String, String)' is an error because the compiler cannot distinguish success from failure when both have the same type.

DIFFERENT TYPES REQUIRED
The ok type and error type must differ:
  Result of (Integer, String)  // correct: Integer ok, String error
  Result of (String, String)   // ERROR E06190: same types

WHY REQUIRED
Result's purpose is to carry EITHER a success value OR an error value. If both are the same type, there is no way to distinguish which state the Result is in. The type system loses its ability to guarantee safety.

CORRECT PATTERNS
Use distinct types for success and error:
  Result of (UserRecord, String)     // success: record, error: message
  Result of (Integer, ErrorCode)     // success: count, error: enum
  Result of (Float, Exception)       // success: value, error: exception

CREATING RESULTS
Use the ok or error constructor:
  okResult <- Result(42, String())   // ok result
  errResult <- Result(Integer(), "failed")  // error result

See Q48 for Result basics. See Q198 for built-in generics. See Q104 for error handling patterns.

defines module qa.genericsdeep.resultdifferenttypes

  defines function

    <?-
      Correct: Result with different ok and error types.
      Integer for success value, String for error message.
    -?>
    safeDivide() as pure
      ->
        numerator as Integer
        denominator as Integer
      <- rtn as Result of (Integer, String): Result(Integer(), "not calculated")

      if denominator == 0
        rtn: Result(Integer(), "division by zero")
      else
        rtn: Result(numerator / denominator, String())

    <?-
      Correct: Result with custom type and String error.
    -?>
    parseScore() as pure
      -> input as String
      <- rtn as Result of (Float, String): Result(Float(), "not parsed")

      if input?
        rtn: Result(Float(input), String())

  defines program

    ResultDifferentTypesDemo()
      stdout <- Stdout()

      // === SUCCESSFUL DIVISION ===

      divResult <- safeDivide(numerator: 10, denominator: 3)
      stdout.println(`Division result set: ${divResult?}`)

      // === DIVISION BY ZERO ===

      zeroResult <- safeDivide(numerator: 10, denominator: 0)
      stdout.println(`Zero division set: ${zeroResult?}`)

      // === PARSE RESULT ===

      scoreResult <- parseScore("95.5")
      stdout.println(`Parse result set: ${scoreResult?}`)

Java: no built-in Result type. Rust: Result<T, E> allows same types (Result<String, String> is valid). Go: multiple return values (value, error). Kotlin: Result<T> wraps only success, uses exceptions for errors. EK9: Result of (OK, ERR) requires OK and ERR to be different types for unambiguous state.

E06190 ambiguity different error generic guard ok result success type-parameter types

rtn as Result of (String, String): Result(String(), "not calculated")

Explanation: Result must be parameterised with two different types so the compiler can distinguish success from error. Using the same type for both (e.g., Result of (String, String)) triggers E06190. See ek9 -h E06190 for details.


Question: Why is List of Circle not compatible with List of Shape?

How does type independence work for parameterised types in EK9?

Can I pass List of Circle where List of Shape is expected?

Answer:
EK9 creates truly independent types for each generic parameterisation. 'List of Circle' and 'List of Shape' are completely separate types with no relationship, even if Circle extends Shape.

TYPE INDEPENDENCE
Each parameterisation is a distinct type:
  List of Circle   // one type
  List of Shape    // completely different type
  List of Integer  // yet another different type

NO ERASURE
Unlike Java where 'List<Circle>' and 'List<Shape>' become the same 'List' at runtime (erasure), EK9 maintains full type identity. They are separate types that happen to have similar structures.

NO COVARIANCE
You cannot pass 'List of Circle' where 'List of Shape' is expected. This prevents the type safety violation where adding a Square to a 'List of Shape' that is actually a 'List of Circle'.

METHOD OVERLOADING
Because types are independent, you can overload methods:
  process(circles as List of Circle)
  process(shapes as List of Shape)
Java cannot do this due to erasure.

See Q198 for built-in generics. See Q195 for constrain by. See Q196 for multi-parameter generics. See Q655 for method overloading with generics. See Q642 for constructor inference.

defines module qa.genericsdeep.typeindependence

  defines class

    Colour as abstract
      colourName as String: String()

      default Colour() as pure

      Colour() as pure
        -> n as String
        colourName :=? String(n)

      name() as pure
        <- rtn as String: String(colourName)

      default operator ?

    Red is Colour
      default Red() as pure
      Red() as pure
        -> n as String
        super(n)

    Blue is Colour
      default Blue() as pure
      Blue() as pure
        -> n as String
        super(n)

  defines program

    TypeIndependenceDemo()
      stdout <- Stdout()

      // === SEPARATE LISTS OF DIFFERENT TYPES ===

      reds <- List() of Red
      reds += Red("Crimson")
      reds += Red("Scarlet")

      blues <- List() of Blue
      blues += Blue("Navy")

      // === EACH LIST IS AN INDEPENDENT TYPE ===

      redCount <- length reds
      stdout.println(`Red count: ${redCount}`)

      blueCount <- length blues
      stdout.println(`Blue count: ${blueCount}`)

      // === CANNOT MIX: List of Red != List of Blue ===
      // reds += Blue("Teal")  // ERROR: Blue is not Red

      // === INDEPENDENT CONTAINERS ===

      redOpt <- Optional(Red("Vermillion"))
      blueOpt <- Optional(Blue("Azure"))
      stdout.println(`Red optional set: ${redOpt?}`)
      stdout.println(`Blue optional set: ${blueOpt?}`)

      // Optional of Red and Optional of Blue are different types

      stdout.println("Each parameterised type is a separate independent type")

Java: type erasure makes List<Circle> and List<Shape> the same at runtime, cannot overload. C++: templates create independent types (full monomorphisation). Rust: generics create independent types. Go: generics create independent types (Go 1.18+). Kotlin: same erasure as Java. EK9: fully independent types, no erasure, safe method overloading.

covariance erasure generic independence list migrate overload parameterised separate type type-parameter

reds <- List() of Red
      reds += Blue("Navy")

Explanation: Each generic parameterisation creates a fully independent type. List of Red and List of Blue are completely separate types with no relationship, so adding a Blue to a List of Red is a type error. See ek9 -h E50060 for details.