Question: How do boolean and bitwise operators work in EK9?

What is the difference between boolean and bitwise operators in EK9?

How do shift operators work on Bits in EK9?

Does EK9 use the same syntax for boolean and bitwise operations?

Answer:
EK9 uses the SAME operator names for both Boolean logic and Bits manipulation. The compiler resolves which implementation to use based on the operand types.

SHARED OPERATORS
The 'and', 'or', 'xor' operators work on BOTH Boolean and Bits types. All are pure, take 1 argument, and return a value.

For Boolean:
  result <- true and false
  result <- true or false
  result <- true xor true

For Bits:
  mask <- 0b1100 and 0b1010
  combined <- 0b1100 or 0b1010
  toggled <- 0b1100 xor 0b1010

NEGATION (~)
The ~ operator works on Boolean (logical NOT) and Bits (bitwise NOT). Numbers use unary - for negation, not ~.
  notTrue <- ~ true
  flipped <- ~ 0b1100

BIT SHIFT OPERATORS (<< and >>)
The << and >> operators are Bits-only. They shift bits left or right. Both are pure, take 1 Integer argument, and return a new Bits value.
  shifted <- 0b0001 << 3
  result <- 0b1000 >> 2

Bits values grow as needed with left shift. Right shift discards bits.

BITS ARE NOT INTEGERS
Bits are ordered collections of individual bits. They are NOT integers. You cannot add Bits or use them in arithmetic. They support: and, or, xor, ~ (NOT), << (shift left), >> (shift right), length, ==, <>, $.

BITS LITERALS
Bits literals use the 0b prefix:
  flags <- 0b010011
  mask <- 0b111100

See Q30 for Boolean basics. See Q40 for Bits type. See Q238 for the complete operator set. See Q240 for arithmetic operators.

defines module qa.operators.booleanbitwise

  defines program

    BooleanBitwiseDemo()
      stdout <- Stdout()

      // === BOOLEAN LOGIC ===

      a <- true
      b <- false

      stdout.println(`and: ${a and b}`)
      stdout.println(`or: ${a or b}`)
      stdout.println(`xor: ${a xor b}`)
      stdout.println(`not: ${~ a}`)

      // === BITWISE OPERATIONS (same syntax!) ===

      mask1 <- 0b1100
      mask2 <- 0b1010

      stdout.println(`Bits and: ${mask1 and mask2}`)
      stdout.println(`Bits or: ${mask1 or mask2}`)
      stdout.println(`Bits xor: ${mask1 xor mask2}`)
      stdout.println(`Bits not: ${~ mask1}`)

      // === BIT SHIFT ===

      oneBit <- 0b0001
      shifted <- oneBit << 3
      stdout.println(`Shift left: ${shifted}`)

      wide <- 0b1000
      narrow <- wide >> 2
      stdout.println(`Shift right: ${narrow}`)

      // === BITS ARE NOT INTEGERS ===

      flags <- 0b010011
      stdout.println(`Flags length: ${length flags}`)
      stdout.println(`Flags equal: ${flags == 0b010011}`)

Java: && || for boolean, & | ^ for bitwise, different syntax for same concepts. Python: and or not for boolean, & | ^ ~ for bitwise, different syntax. Rust: && || for boolean, & | ^ for bitwise, ! for both not. Go: && || for boolean, & | ^ for bitwise, different syntax. Kotlin: && || for boolean, and or xor for bitwise on Int. JavaScript: && || for boolean, & | ^ for bitwise, different syntax. EK9: 'and' 'or' 'xor' '~' for BOTH Boolean and Bits (same syntax, type-based dispatch), << >> for Bits only.

and binary bits bitwise boolean expression flag left logic mask negate not operator or right shift xor

Explanation: Boolean negation in EK9 uses the ~ operator, not ! which is the factorial operator for Integer. The ! operator is not defined on Boolean. See ek9 -h E07620 for details.


Question: How do I implement all the operators my custom type needs?

What operators should I implement for a custom class in EK9?

How do I write a complete custom type with all operators?

What is the best practice for implementing operators on EK9 classes?

Answer:
Choose operators based on how your type will be used. Use 'default operator' for field-by-field behaviour, override for custom logic.

MINIMUM USEFUL SET
Most types need: == (equality), $ (string), ? (isSet).

COMPARISON (pure, 1 arg, return Boolean or Integer)
  operator == as pure -> other as T <- rtn as Boolean
  operator <=> as pure -> other as T <- rtn as Integer

ARITHMETIC (pure, 1 arg, return new value)
  operator + as pure -> other as T <- rtn as T
  operator - as pure -> other as T <- rtn as T

UNARY (pure, 0 args)
  operator - as pure <- rtn as T (negate)
  operator #^ as pure <- rtn as OtherType (promote)

MUTATION (NOT pure, no return)
  operator += -> other as T
  operator :=: -> source as T (copy)

CONVERSION (pure, 0 args, enforced return types)
  operator $ as pure <- rtn as String
  operator #? as pure <- rtn as Integer
  operator ? as pure <- rtn as Boolean

ERROR SCENARIOS
  E07500 - must be pure. E07510 - cannot be pure. E06280/E06290 - wrong param count. E07520/E07550/E07570/E07580 - wrong return type.

See Q96 for class operator syntax. See Q98 for record copy/merge/replace. See Q116 for default operator. See Q238 for complete operator set. See Q239 for comparison. See Q240 for arithmetic. See Q241 for mutation. See Q242 for conversion.

defines module qa.operators.customtype

  defines class

    Temperature
      celsius <- Float()

      default private Temperature() as pure

      Temperature() as pure
        -> celsius as Float
        this.celsius :=: celsius

      celsius() as pure
        <- rtn as Float: Float(celsius)

      // === COMPARISON (pure, 1 arg, return Boolean or Integer) ===

      operator == as pure
        -> other as Temperature
        <- rtn as Boolean: celsius == other.celsius

      operator <> as pure
        -> other as Temperature
        <- rtn as Boolean: celsius <> other.celsius

      operator <=> as pure
        -> other as Temperature
        <- rtn as Integer: celsius <=> other.celsius

      operator < as pure
        -> other as Temperature
        <- rtn as Boolean: celsius < other.celsius

      operator <= as pure
        -> other as Temperature
        <- rtn as Boolean: celsius <= other.celsius

      operator > as pure
        -> other as Temperature
        <- rtn as Boolean: celsius > other.celsius

      operator >= as pure
        -> other as Temperature
        <- rtn as Boolean: celsius >= other.celsius

      // === ARITHMETIC (pure, 1 arg, return new value) ===

      operator + as pure
        -> other as Temperature
        <- rtn as Temperature: Temperature(celsius + other.celsius)

      operator - as pure
        -> other as Temperature
        <- rtn as Temperature: Temperature(celsius - other.celsius)

      // === UNARY (pure, 0 args, return value) ===

      operator - as pure
        <- rtn as Temperature: Temperature(-celsius)

      operator #^ as pure
        <- rtn as Float: Float(celsius)

      // === CONVERSION (pure, 0 args, strictly typed returns) ===

      operator $ as pure
        <- rtn as String: `${celsius}C`

      operator #? as pure
        <- rtn as Integer: #?celsius

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

      // === MUTATION (NOT pure, no return) ===

      operator +=
        -> other as Temperature
        celsius += other.celsius

      operator :=:
        -> source as Temperature
        celsius :=: source.celsius

  defines program

    CustomTypeDemo()
      stdout <- Stdout()

      freezing <- Temperature(0.0)
      boiling <- Temperature(100.0)
      body <- Temperature(37.0)

      // === COMPARISON ===

      stdout.println(`Equal: ${freezing == boiling}`)
      stdout.println(`Less: ${freezing < boiling}`)
      stdout.println(`Compare: ${freezing <=> boiling}`)

      // === ARITHMETIC (creates new values) ===

      sum <- freezing + body
      stdout.println(`Sum: ${sum}`)
      diff <- boiling - body
      stdout.println(`Diff: ${diff}`)

      // === UNARY ===

      negated <- -body
      stdout.println(`Negated: ${negated}`)

      // === PROMOTE ===

      floatVal as Float: body
      stdout.println(`Promoted: ${floatVal}`)

      // === CONVERSION ===

      stdout.println(`String: ${body}`)
      stdout.println(`IsSet: ${body?}`)
      stdout.println(`Hash: ${#?body}`)

      // === MUTATION ===

      room <- Temperature(20.0)
      room += Temperature(5.0)
      stdout.println(`After +=: ${room}`)

      target <- Temperature(0.0)
      target :=: boiling
      stdout.println(`After copy: ${target}`)

Java: Comparable, manual equals/hashCode/toString. Python: __eq__/__lt__/__add__ etc. Rust: derive traits. Go: no operator overloading. EK9: operators with enforced signatures, 'default operator' for field-by-field, override for custom.

abstract class complete custom default example expression immutable implement mutation open operator override practice pure side-effect signature temperature type virtual

Explanation: Comparison operators must be marked 'as pure' because equality checks cannot have side effects. Omitting 'as pure' triggers E07500. See ek9 -h E07500 for details.

<- rtn as Boolean: celsius <=> other.celsius

Explanation: The <=> (spaceship) operator must return Integer (negative, zero, or positive for ordering). Returning Boolean instead triggers E07550. See ek9 -h E07550 for details.

<- rtn as Integer: celsius == other.celsius

Explanation: The == operator must return Boolean. Returning Integer instead of Boolean triggers E07520. See ek9 -h E07520 for details.

Explanation: A String literal cannot initialise a Temperature variable. EK9 is strongly typed and requires compatible types. Use the Temperature constructor with a Float argument. See ek9 -h E50030 for details.

Oracle can add operator declarations to an existing class with correct signatures and purity.


Question: How do I debug an EK9 program?

What debugging tools does EK9 provide?
How do I find bugs in my EK9 code?
What is the EK9 debugging workflow?

Answer:
EK9 provides several strategies for finding and preventing bugs, emphasising compile-time detection over runtime debugging. The compiler catches the majority of defects before your program ever runs.

COMPILE-TIME DEFECT PREVENTION
The compiler is your primary debugging tool. EK9's type system, flow analysis, and enforced guard patterns catch 80-90% of defects at compile time. Error messages include the file, line, column, and a specific error code. Use 'ek9 -E1' for visual error display with source snippets and carets pointing to the exact location. Use 'ek9 -h EXXXXX' to look up any error code for a detailed explanation.

REQUIRE AND ASSERT
The 'require' keyword validates preconditions at the start of methods and functions. It is always active and cannot be disabled:
  require value?
  require count > 0
If the condition fails, an exception is thrown with the source location. Use 'assert' for postconditions and invariants inside method bodies.

DIAGNOSTIC OUTPUT
Use stdout.println() to print values during development. The $ operator converts any value to a string, and string interpolation with backtick strings makes this easy:
  stdout.println(`Debug: value=${value}, count=${count}`)

DEBUG INSTRUMENTATION (-cg flag)
Compile with 'ek9 -cg' to enable debug instrumentation. This embeds source file, line, and column information into assertion messages and exception stack traces. Without -cg, assertion failures show generic messages. With -cg, they show exact source locations like './myfile.ek9:42:5'.

BLACK-BOX TESTING
EK9 has built-in black-box testing. Create an expected_output.txt file alongside your .ek9 file. Run with 'ek9 -t' and the test runner compares actual output against expected output line by line. This catches regressions without writing any test assertions.

TEST DIRECTIVE
Mark methods with @Test to create unit tests. Use assert, assertThrows, and assertDoesNotThrow inside test methods. Run all tests with 'ek9 -t myfile.ek9'.

LANGUAGE SERVER (LSP)
Run 'ek9 -ls' for real-time error detection in your editor. The LSP provides hover documentation, go-to-definition, and completions. Errors appear as you type, before you even save the file.

PLATFORM DEBUGGER
A platform debugger (edb) is planned but not yet available. The compiler generates JSR-45 SMAP data and LocalVariableTable entries so that standard JVM debuggers can step through EK9 source code. In the meantime, use diagnostic output and the testing framework.

See Q134 for try/catch error handling. See Q29 for tri-state (unset variable) semantics. See Q155 for writing unit tests. See Q247 for reading error messages. See Q252 for verbose compilation modes. See Q322 for profiling to find performance bottlenecks.

defines module qa.debugging.program

  defines function

    validateScore() as pure
      -> score as Integer
      <- rtn as Boolean: false

      require score?
      require score >= 0
      require score <= 100
      rtn: true

  defines class

    GameScore
      points <- Integer()

      GameScore()
        -> initialPoints as Integer
        require initialPoints?
        require initialPoints >= 0
        this.points :=: initialPoints

      addPoints()
        -> amount as Integer
        require amount?
        require amount > 0
        points += amount

      operator $ as pure
        <- rtn as String: `GameScore(${points})`

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

  defines program

    DebuggingDemo()
      stdout <- Stdout()

      // Diagnostic output with $ operator
      score <- GameScore(10)
      stdout.println(`Initial: ${score}`)

      score.addPoints(5)
      stdout.println(`After adding 5: ${score}`)

      // Validation with require
      valid <- validateScore(85)
      stdout.println(`Score 85 valid: ${valid}`)

      // Try/catch for error handling
      try
        badScore <- GameScore(-1)
        stdout.println(`Should not reach: ${badScore}`)
      catch
        -> ex as Exception
        stdout.println(`Caught: ${ex.reason()}`)

      // Guard pattern for safe access
      parsed <- Integer("42")
      if parsed?
        stdout.println(`Parsed value: ${parsed}`)

      badParsed <- Integer("not a number")
      if badParsed?
        stdout.println("Should not print")
      else
        stdout.println("Invalid input detected via ? check")

Java: IDE debuggers (IntelliJ, Eclipse), System.out.println, JUnit assertions, stack traces. Python: pdb debugger, print(), pytest, traceback. Rust: dbg!() macro, println!(), cargo test, LLDB/GDB. Go: fmt.Println, Delve debugger, go test. C++: GDB/LLDB, cerr, assert macro. Kotlin: IDE debuggers, println, JUnit. EK9: require/assert always active, stdout.println with $ operator, -cg debug instrumentation, built-in black-box testing, LSP real-time errors, platform debugger planned.

assert bug debug debugging diagnostic error error-message instrumentation lsp output println program require testing trace troubleshoot

Explanation: If a declared variable is never referenced after assignment, the compiler rejects it. Every variable must be used. See ek9 -h E50001 for details.


Question: How do I read EK9 compiler error messages?

How do I understand EK9 compiler output?

Answer:
EK9 error messages follow a consistent format and can be displayed at multiple verbosity levels. Understanding the format helps you fix issues quickly.

ERROR MESSAGE FORMAT
Every error follows this pattern:
  filename.ek9:line:column: EXXXXX description
For example:
  myfile.ek9:15:3: E50030 'unknownVar' is not resolved
The file path, line number, and column number tell you exactly where the error is. The EXXXXX code uniquely identifies the error type.

VERBOSITY LEVELS
EK9 supports five error verbosity levels, controlled by -E0 through -E4:

-E0 (Minimal): Single-line error messages. Just the location and error code. Best for experienced developers who know what the codes mean.

-E1 (Visual): Rust-style visual display with source code snippets and carets (^) pointing to the exact error location. Shows the surrounding code context. Recommended for daily development.

-E2 (Suggestions): Everything in -E1 plus 'Did you mean?' suggestions. When you misspell a variable or method name, the compiler uses fuzzy matching to suggest corrections.

-E3 (Full Explanations): Everything in -E2 plus detailed explanations suitable for beginners and AI tools. Includes diagnosis (what went wrong), rationale (why EK9 enforces this), and examples (how to fix it). Best for learning EK9.

-E4 (Developer): Internal compiler diagnostics. Shows phase-level detail. Only useful for compiler developers.

LOOKING UP ERROR CODES
Any error code can be looked up with:
  ek9 -h EXXXXX
This displays a detailed explanation of the error, what causes it, and how to fix it. Works offline, no internet needed.

MULTIPLE ERRORS
The compiler reports ALL errors it can find in a single pass, not just the first one. Fix errors from the top of the file downward, as later errors are sometimes caused by earlier ones.

See Q246 for debugging strategies. See Q248 for error code ranges and lookup. See Q252 for all compiler flags. See Q311 for the full catalog of quality error codes.

defines module qa.debugging.errormessages

  defines class

    Temperature
      degrees <- Float()
      unit <- String()

      Temperature()
        ->
          initialValue as Float
          initialUnit as String
        require initialValue?
        require initialUnit?
        this.degrees :=: initialValue
        this.unit :=: initialUnit

      celsius() as pure
        <- rtn as Float: Float()
        if unit == "C"
          rtn: degrees
        else
          rtn: (degrees - 32.0) * 5.0 / 9.0

      operator $ as pure
        <- rtn as String: `${degrees} ${unit}`

      override operator ? as pure
        <- rtn as Boolean: degrees? and unit?

  defines program

    ErrorMessageDemo()
      stdout <- Stdout()

      // Create valid temperature
      reading <- Temperature(100.0, "C")
      stdout.println(`Temperature: ${reading}`)
      stdout.println(`In Celsius: ${reading.celsius()}`)

      // Demonstrate correct patterns that avoid common errors
      count <- 42
      stdout.println(`Count: ${count}`)

      // Correct type usage
      name <- "EK9"
      nameLength <- length name
      stdout.println(`${name} has ${nameLength} characters`)

      // Correct operator usage
      a <- 10
      b <- 20
      sum <- a + b
      stdout.println(`Sum: ${sum}`)

Java: javac shows file:line: error with no visual display, IDEs provide rich error display. Python: SyntaxError with caret, traceback for runtime errors. Rust: famously rich error messages with source snippets, suggestions, and explanations. Go: terse single-line errors. C++: notoriously verbose template errors, varies by compiler. Kotlin: similar to Java with better type inference errors. EK9: five verbosity levels from minimal to full AI-friendly explanations, visual source snippets with carets, fuzzy suggestions, offline error code lookup.

E0 E1 E2 E3 code compile debug diagnostic error error-message format level lookup message migrate read troubleshoot understand verbosity

Explanation: EK9 uses 'length' as a prefix operator, not a method call. The method 'length()' does not exist on String. See ek9 -h E50060 for details.


Question: What does a specific EK9 error code mean?

How do I look up an EK9 error code?
What are the EK9 error code ranges?
How do I find what EXXXXX means in EK9?

Answer:
Every EK9 compiler error has a unique code in the format EXXXXX (E followed by five digits). Use 'ek9 -h EXXXXX' to look up any error code for a detailed explanation.

LOOKING UP ERROR CODES
Run the following command to see a detailed explanation:
  ek9 -h E50030
This works offline and shows what the error means, what causes it, and how to fix it.

ERROR CODE RANGES
Error codes are organised into ranges by category:

E01xxx - Names and Identifiers
  E01010: Invalid identifier name
  E01020: Reserved word used as identifier
  E01030: Excluded keyword (break, continue, return do not exist in EK9)

E02xxx - Duplicate Definitions
  E02010: Duplicate type definition
  E02020: Duplicate method or property name
  E02030: Duplicate module definition

E04xxx - Type Constraints
  E04010: Type must extend Exception (for throw/catch)
  E04020: Not a valid candidate for this context

E05xxx - Type Hierarchy
  E05020: Circular type hierarchy detected
  E05030: Type is not open for extension (closed by default)
  E05120: Method must use override keyword (overriding base method)

E06xxx - Parameters and Arguments
  E06260: Parameter type mismatch
  E06280: Too many parameters for this operator or method
  E06290: Too few parameters
  E06330: Function signature does not match

E07xxx - Operators and Methods
  E07500: Operator must be pure but is not declared pure
  E07510: Operator cannot be pure (mutation operators)
  E07520: Incorrect return type for operator
  E07550: Comparison operator must return Boolean
  E07570: Hash operator must return Integer
  E07580: String operator must return String
  E07620: Operator not defined on this type

E08xxx - Flow Analysis and DI
  E08020: Variable used before it is initialised
  E08030: Variable not checked with ? before access
  E08050: Return variable not initialised on all paths
  E08190: Circular dependency injection detected

E11xxx - Code Quality
  E11031: Variable name matches common convention violation
  E11032: Variable name is a reserved word
  E11040: Method complexity exceeds threshold

E50xxx - Resolution
  E50030: Symbol not resolved (variable, type, method, or function not found)

COMMON FIRST ERRORS
The most common errors for new EK9 developers are:
  E50030: Check spelling and ensure imports are correct
  E05030: Mark the base type 'as open' if you intend to extend it
  E08020: Initialise the variable before using it
  E06280/E06290: Check operator parameter counts

See Q247 for understanding error message format. See Q252 for verbose compilation modes. See Q238 for the complete operator set and enforcement rules. See Q290 for E11031 and E11032 naming errors. See Q291 for naming error troubleshooting. See Q311 for the complete quality checks catalog.

defines module qa.debugging.errorcodelookup

  defines class

    Account
      name <- String()
      balance <- Float()

      Account()
        ->
          accountName as String
          initialBalance as Float
        require accountName?
        require initialBalance?
        require initialBalance >= 0.0
        this.name :=: accountName
        this.balance :=: initialBalance

      deposit()
        -> amount as Float
        require amount?
        require amount > 0.0
        balance += amount

      getBalance() as pure
        <- rtn as Float: balance

      operator $ as pure
        <- rtn as String: `Account(${name}, ${balance})`

      operator == as pure
        -> other as Account
        <- rtn as Boolean: name == other.name

      operator <=> as pure
        -> other as Account
        <- rtn as Integer: balance <=> other.balance

      override operator ? as pure
        <- rtn as Boolean: name? and balance?

  defines program

    ErrorCodeDemo()
      stdout <- Stdout()

      // Correctly constructed class with operators
      acc <- Account("Alice", 100.0)
      stdout.println(`Account: ${acc}`)

      acc.deposit(50.0)
      stdout.println(`After deposit: ${acc}`)
      stdout.println(`Balance: ${acc.getBalance()}`)

      // Correct comparison usage
      acc2 <- Account("Bob", 200.0)
      stdout.println(`Equal: ${acc == acc2}`)
      stdout.println(`Compare: ${acc <=> acc2}`)

Java: javac error codes are not standardised, IDEs provide error databases. Python: no error code system, SyntaxError and named exceptions. Rust: E0001-E0999 error codes with 'rustc --explain E0308'. Go: no standardised error codes, terse messages. C++: compiler-specific error codes (GCC, Clang, MSVC all different). Kotlin: no standardised error codes, IDE-centric. EK9: unified EXXXXX error codes across all phases, offline lookup with 'ek9 -h EXXXXX', organised by category ranges.

E05030 E08020 E50030 EXXXXX category code debug error error-message explain help lookup meaning range resolve troubleshoot

Explanation: Calling a method that does not exist on the type produces a resolution error. The Account class defines 'deposit' but not 'withdraw'. See ek9 -h E50060 for details.

Explanation: If 'balance' is declared but never used in any expression or output, the compiler rejects it as unused. See ek9 -h E08090 for details.


Question: How do I use the language server for error detection?

How do I get real-time errors in my editor?

Answer:
EK9 includes a full Language Server Protocol (LSP) implementation that provides real-time error detection, hover documentation, go-to-definition, and completions in your editor.

STARTING THE LSP
Two modes are available:
  ek9 -ls     Full LSP mode for IDE integration
  ek9 -lsh    Hover-help-only mode for lightweight editors
The LSP communicates over stdio using the standard LSP JSON-RPC protocol. Any editor that supports LSP can use it (VSCode, Neovim, Emacs, Sublime Text, etc.).

VSCODE EXTENSION
A VSCode extension is available that provides:
  Syntax highlighting for .ek9 files
  Real-time error detection as you type
  Hover documentation showing type information and method signatures
  Go-to-definition for navigating to symbol declarations
  Code completions for types, methods, and operators

REAL-TIME ERROR DETECTION
The LSP compiles your code through the PRE_IR_CHECKS phase (phase 9 of 22). This means ALL type errors, flow analysis errors, duplicate detection, and resolution errors appear instantly in your editor. You see errors before you save the file.

HOVER DOCUMENTATION
Hovering over any symbol shows its type, documentation, and available methods. For built-in types, this includes the full API. For your own types, it shows the declared operators and methods.

PROGRESSIVE ERROR DETECTION
The multi-phase pipeline means errors are detected progressively:
  Phase 2-3: Duplicate names and definitions
  Phase 4-5: Unresolved references and types
  Phase 6-7: Type hierarchy and generic resolution errors
  Phase 8-9: Flow analysis (unset variables, uninitialised returns)
Each phase builds on the previous, so fixing early errors often resolves later ones.

COMMAND LINE ALTERNATIVE
If you prefer the command line, use:
  ek9 -c myfile.ek9          compile and check for errors
  ek9 -E1 -c myfile.ek9      visual errors with source snippets
  ek9 -E2 -c myfile.ek9      add fuzzy suggestions

See Q246 for debugging strategies. See Q252 for verbose compilation modes. See Q253 for understanding compiler phases.

defines module qa.debugging.languageserver

  defines trait

    Describable
      describe() as pure abstract
        <- rtn as String?

  defines class

    Point
      x <- Float()
      y <- Float()

      Point()
        ->
          px as Float
          py as Float
        this.x :=: px
        this.y :=: py

      distanceTo() as pure
        -> other as Point
        <- rtn as Float: Float()
        dx <- x - other.x
        dy <- y - other.y
        rtn: sqrt (dx * dx + dy * dy)

      operator $ as pure
        <- rtn as String: `(${x}, ${y})`

      override operator ? as pure
        <- rtn as Boolean: x? and y?

    Sensor with trait of Describable
      name <- String()
      reading <- Float()

      Sensor()
        ->
          sensorName as String
          sensorReading as Float
        this.name :=: sensorName
        this.reading :=: sensorReading

      override describe() as pure
        <- rtn as String: `Sensor ${name}: ${reading}`

      operator $ as pure
        <- rtn as String: `${name}=${reading}`

      override operator ? as pure
        <- rtn as Boolean: name? and reading?

  defines program

    LanguageServerDemo()
      stdout <- Stdout()

      // Types that the LSP would provide hover info for
      p1 <- Point(0.0, 0.0)
      p2 <- Point(3.0, 4.0)

      stdout.println(`Point 1: ${p1}`)
      stdout.println(`Point 2: ${p2}`)
      stdout.println(`Distance: ${p1.distanceTo(p2)}`)

      // Sensor with trait
      sensor <- Sensor("Temp", 22.5)
      stdout.println(`Sensor: ${sensor}`)
      stdout.println(`Description: ${sensor.describe()}`)

Java: IntelliJ/Eclipse provide rich IDE support, separate from compiler. Python: Pylance/Pyright LSP servers, type checking optional. Rust: rust-analyzer provides excellent LSP support. Go: gopls is the official LSP server. C++: clangd provides LSP, but C++ complexity limits accuracy. Kotlin: IntelliJ native support, kotlin-language-server for other editors. EK9: built-in LSP as part of the compiler itself, compiles through 10 phases for comprehensive error detection, hover documentation, go-to-definition.

completion debug definition detection editor error error-message hover ide integration language lsp realtime server troubleshoot vscode

Explanation: The method is named 'distanceTo' not 'distance'. Calling a method that does not exist on the type produces a resolution error. See ek9 -h E50060 for details.

Explanation: If the variable 'sensor' is declared but never used in any expression, the compiler rejects it. Every declared variable must be referenced. See ek9 -h E50050 for details.


Question: Why is the compiler reporting a type mismatch?

How do I fix type mismatch errors in EK9?

How do I convert between types correctly in EK9?

Answer:
Type mismatch errors occur when a value of one type is used where another is expected. EK9 has no implicit coercion; all conversions must be explicit.

COMMON FIXES

Integer to Float: #^ promote or assignment:
  floatVal <- #^ intVal
  floatVal as Float: intVal

Any to String: $ operator or interpolation:
  text <- $count
  message <- `Count is ${count}`

String to Integer/Float: constructors (unset if invalid):
  parsed <- Integer("42")

Parameter Mismatch: types must match exactly:
  WRONG:  processFloat(42)
  RIGHT:  processFloat(#^ 42) or processFloat(42.0)

Operator Return Types (enforced):
  == returns Boolean (E07550), $ returns String (E07580), #? returns Integer (E07570), <=> returns Integer (E07520). Mutation operators must NOT return (E07410).

DISPATCHER PATTERN
For type-specific processing, use dispatcher instead of casting. Runtime routes to the most specific overload.

See Q24 for type conversion and dispatcher. See Q25 for promote operator. See Q238 for operator rules. See Q242 for conversion operators.

defines module qa.debugging.typemismatch

  defines function

    processFloat() as pure
      -> floatInput as Float
      <- rtn as String: `Processed: ${floatInput}`

  defines class

    Measurement
      reading <- Float()
      label <- String()

      Measurement()
        ->
          initialReading as Float
          measureLabel as String
        this.reading :=: initialReading
        this.label :=: measureLabel

      operator $ as pure
        <- rtn as String: `${label}: ${reading}`

      operator == as pure
        -> other as Measurement
        <- rtn as Boolean: reading == other.reading

      operator <=> as pure
        -> other as Measurement
        <- rtn as Integer: reading <=> other.reading

      operator #? as pure
        <- rtn as Integer: #? reading

      override operator ? as pure
        <- rtn as Boolean: reading? and label?

  defines program

    TypeMismatchDemo()
      stdout <- Stdout()

      // Integer to Float promotion with #^
      intVal <- 42
      floatVal <- #^ intVal
      stdout.println(`Promoted: ${floatVal}`)

      // Direct Float assignment (compiler inserts promotion)
      widened as Float: intVal
      stdout.println(`Widened: ${widened}`)

      // Using promoted value in function call
      result <- processFloat(#^ intVal)
      stdout.println(result)

      // String conversion with $ operator
      count <- 100
      countText <- $count
      stdout.println(`As string: ${countText}`)

      // Constructor conversion
      parsed <- Integer("99")
      if parsed?
        stdout.println(`Parsed: ${parsed}`)

      // Invalid parse produces unset
      bad <- Integer("abc")
      if bad?
        stdout.println("Should not print")
      else
        stdout.println("Invalid parse detected")

      // Correct operator usage
      m1 <- Measurement(10.0, "Width")
      m2 <- Measurement(20.0, "Height")
      stdout.println(`${m1}`)
      stdout.println(`Equal: ${m1 == m2}`)
      stdout.println(`Compare: ${m1 <=> m2}`)

Java: implicit widening, ClassCastException. Python: dynamic typing, TypeError. Rust/Go: no implicit conversions. EK9: explicit #^ promotion, $ to string, constructor conversion, dispatcher for type-specific logic.

cast coerce compile conversion debug error error-message explicit fix implicit mismatch operator parameter promote return troubleshoot type

Explanation: A String cannot be passed where Float is expected. There is no automatic promotion from String to Float. Use a Float literal or the #^ operator to promote Integer to Float. See ek9 -h E06270 for details.


Question: Why does the compiler say my variable is unset?

How do I fix unset variable errors in EK9?

What does 'used before initialised' mean in EK9?

Answer:
The EK9 compiler uses flow analysis to track whether variables have been initialised. If you use a variable before giving it a value, or access a value without checking if it is set, the compiler reports an error.

COMMON ERRORS AND FIXES

E08020 - Used Before Initialised:
You declared a variable but used it before assigning a value:
  count <- Integer()     unset
  total <- count + 1     ERROR: count used before initialised
Fix: assign a value first, or use a guard:
  count <- Integer()
  count :=? 0
  total <- count + 1     now safe

E08030 - Not Checked Before Access:
You accessed a value that might be unset without a ? guard:
  parsed <- Integer(someString)
  stdout.println($parsed)     ERROR: parsed not checked
Fix: wrap in a guard:
  parsed <- Integer(someString)
  if parsed?
    stdout.println($parsed)   safe

E08050 - Return Not Always Initialised:
Your function has code paths where the return value is never set:
  calculateArea()
    -> shape as String
    <- rtn as Float: Float()
    if shape == "circle"
      rtn: 3.14
    // ERROR: rtn not set when shape is not circle
Fix: ensure ALL paths set the return value.

GUARD EXPRESSIONS
The preferred way to handle potentially unset values is with guard expressions. These combine assignment with a ? check:
  if value <- computeSomething()
    stdout.println(`Got: ${value}`)
The block only executes if value is set. This works identically in if, switch, for, while, and try.

GUARDED ASSIGNMENT (:=?)
The :=? operator only assigns if the target is currently unset:
  name <- String()
  name :=? "default"
This is safe because :=? checks first.

COALESCING OPERATORS
The ?? operator provides a default when a value is unset:
  safe <- maybeUnset ?? "fallback"
The ?: operator evaluates a function when unset:
  safe <- maybeUnset ?: getDefault

OPTIONAL AND RESULT
Optional requires a ? check before calling .get(). Result requires isOk() before .ok() and isError() before .error(). The compiler enforces these guards at compile time with no escape hatches.

DIVISION BY ZERO
Dividing by zero in EK9 does NOT throw an exception. It returns an unset value:
  result <- 10 / 0
  // result is unset, not an error
  if result?
    stdout.println($result)
  else
    stdout.println("Division produced unset")

See Q29 for tri-state semantics. See Q47 for Optional safe access. See Q48 for Result safe access. See Q75 for guard patterns in switch. See Q243 for coalescing operators. See Q632 for definition ordering. See Q633 for branch initialization. See Q634 for guard-based safe access.

defines module qa.debugging.variableunset

  defines function

    safeDivide() as pure
      ->
        a as Integer
        b as Integer
      <- rtn <- Integer()

      if b <> 0
        rtn: a / b

    findItem() as pure
      -> items as List of String
      <- rtn <- Optional() of String

      for item in items
        if item == "target"
          rtn: Optional(item)

  defines program

    VariableUnsetDemo()
      stdout <- Stdout()

      // Guard expression: only executes if set
      if result <- safeDivide(10, 3)
        stdout.println(`Division result: ${result}`)

      // Division by zero returns unset
      if zeroResult <- safeDivide(10, 0)
        stdout.println("Should not print")
      else
        stdout.println("Division by zero produced unset")

      // Guarded assignment with :=?
      name <- String()
      stdout.println(`Before guard: set=${name?}`)
      name :=? "default"
      stdout.println(`After guard: ${name}`)
      name :=? "other"
      stdout.println(`After second guard: ${name}`)

      // Coalescing operator ??
      unsetVal <- String()
      safe <- unsetVal ?? "fallback"
      stdout.println(`Coalesced: ${safe}`)

      // Optional with guard
      items <- ["apple", "target", "cherry"]
      if found <- findItem(items)
        foundItem <- found.get()
        stdout.println(`Found: ${foundItem}`)

      // Integer parsing with guard
      parsed <- Integer("42")
      if parsed?
        stdout.println(`Parsed: ${parsed}`)

      badParsed <- Integer("xyz")
      if badParsed?
        stdout.println("Should not print")
      else
        stdout.println("Invalid input: integer is unset")

Java: null by default, NullPointerException at runtime, no compile-time null tracking (unless using annotations). Python: NameError for undefined, None checks are convention not enforced. Rust: no null, Option/Result with compiler-enforced pattern matching. Go: zero values by default (0, "", nil), nil pointer panics at runtime. Kotlin: nullable types with compile-time null safety, but !! escape hatch exists. Swift: Optional with compile-time enforcement, ! force-unwrap crashes. EK9: tri-state model, compile-time flow analysis tracks initialisation, guard expressions, :=? conditional assignment, no escape hatches.

E08020 E08030 E08050 analysis before check compile debug error-message flow guard initialise initialize isset null-safe safe set troubleshoot unset used variable

Explanation: Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.


Question: How do I use verbose or debug compilation modes?

How do I get more detail from the EK9 compiler?

Answer:
EK9 provides a comprehensive set of command-line flags for controlling compilation verbosity, debugging, and output.

ERROR VERBOSITY (-E0 through -E4)
  -E0    Minimal: single-line error messages with location and code
  -E1    Visual: Rust-style display with source snippets and caret markers
  -E2    Suggestions: visual plus 'Did you mean?' fuzzy matching
  -E3    Full: detailed explanations with diagnosis, rationale, and examples
  -E4    Developer: internal compiler diagnostics for compiler developers
Default is -E1. Use -E3 when learning EK9 or when an error is confusing.

COMPILATION MODES
  -c     Incremental compile (only recompile changed files)
  -C     Full recompile (recompile everything from scratch)
  -r     Compile and run the program
  -t     Run tests (@Test methods and expected_output.txt)
Incremental compilation is faster for large projects. Use -C when you suspect stale cached output.

DEBUG AND VERBOSE FLAGS
  -v     Verbose output: shows which files are being compiled and which phases complete
  -dv    Debug verbose: more detail including symbol resolution and phase timing
  -cg    Debug instrumentation: embeds source location (file:line:column) in assertion messages and stack traces
  -cd    Combined development and debug mode
The -cg flag is essential for meaningful assertion failure messages. Without it, assertion failures show generic text. With it, they show exact source locations.

ANALYSIS FLAGS
  -di    DI/AOP analysis: outputs dependency injection wiring and aspect pointcut information
  -Cp N  Stop at phase N: compiles up to the specified phase number and stops. Not typical for end users, but valuable for AI tools learning EK9 (verify syntax and types compile correctly without generating runnable programs) and for compiler developers

HELP AND INFORMATION
  -h keyword    Help for a built-in type (e.g., 'ek9 -h String', 'ek9 -h List')
  -h EXXXXX     Explain a specific error code
  -H            List all available help keywords
  -V            Show compiler version

TEST OUTPUT FORMATS
  -t0    Plain text test output
  -t1    JUnit XML output
  -t2    TAP format
  -t3    JSON output
  -t4    Markdown output
  -t5    CSV output
  -t6    HTML dashboard
The default is -t0 (plain text). Use -t1 for CI/CD integration.

COVERAGE
  -tc SET      Statement coverage tracking
  -tc COUNT    Execution count per statement
  -tc ATOMIC   Thread-safe execution counting
Coverage data is written to a coverage report. The default threshold is 80%.

See Q246 for debugging strategies. See Q247 for reading error messages. See Q253 for understanding compiler phases. See Q323 for AI quality workflow. See Q622 for formatting flags. See Q627 for profiling flags.

defines module qa.debugging.verbosemodes

  defines function

    describeFeature()
      ->
        featureEnabled as Boolean
        featureName as String
      <- description as String: String()

      if featureEnabled
        description: `${featureName} is enabled`
      else
        description: `${featureName} is disabled`

  defines program

    VerboseModesDemo()
      stdout <- Stdout()

      // Simple program to demonstrate compilation
      stdout.println("Compilation modes demo")

      // Use string interpolation
      version <- "1.0"
      stdout.println(`Version: ${version}`)

      // Simple arithmetic
      a <- 10
      b <- 20
      stdout.println(`Sum: ${a + b}`)

      // Boolean check via function parameter
      stdout.println(describeFeature(true, "Logging"))

      // List operations
      items <- ["alpha", "beta", "gamma"]
      for item in items
        stdout.println(`Item: ${item}`)

Java: javac has minimal flags, IDEs provide most analysis. Python: python -v for verbose, -W for warnings, no compilation phases. Rust: RUST_LOG for verbose, cargo build --verbose, rustc --explain. Go: go build -v for verbose, go vet for analysis. C++: compiler-specific flags (-Wall, -Wextra, -g for debug info). Kotlin: kotlinc has minimal flags, relies on IDE. EK9: five error verbosity levels, debug instrumentation, phase-level control, built-in test formats, coverage modes, offline error code lookup.

E0 E1 E2 E3 cg compile coverage debug error-message flag instrumentation migrate mode output phase test troubleshoot verbose

featureDesc <- describeFeature(true, "Logging")

Explanation: If 'featureDesc' is declared but never used anywhere, the compiler rejects it as unused. Every variable must be referenced. See ek9 -h E08090 for details.


Question: What do the compiler phases mean?

How many compilation phases does EK9 have?

Why does EK9 use a multi-phase compiler?

Answer:
EK9 uses a 22-phase compilation pipeline. Each phase performs a specific task and builds on the results of previous phases. Understanding the phases helps you interpret error messages and know when different types of errors are detected.

FRONTEND PHASES (0-9)
These phases read, parse, and validate your source code:

Phase 0 - READING: Reads source files from disk.
Phase 1 - PARSING: Parses EK9 source into an abstract syntax tree (AST) using the ANTLR4 grammar. Syntax errors (missing colons, bad indentation) are caught here.
Phase 2 - SYMBOL_DEFINITION: Creates the symbol table with all defined types, classes, functions, and variables.
Phase 3 - DUPLICATION_CHECK: Detects duplicate type names, duplicate methods, and duplicate properties within the same scope.
Phase 4 - REFERENCE_CHECKS: Validates that all referenced symbols (types, variables, functions) exist somewhere in the codebase.
Phase 5 - EXPLICIT_TYPE_SYMBOL_DEFINITION: Second pass to define and resolve non-inferred template types (generic type parameters).
Phase 6 - TYPE_HIERARCHY_CHECKS: Validates inheritance hierarchies, checks for circular inheritance, verifies 'as open' for extension, and validates override correctness.
Phase 7 - FULL_RESOLUTION: Third pass to define and resolve inferred types and template types. Generics are fully resolved here.
Phase 8 - POST_RESOLUTION_CHECKS: Validates all symbols and template types are fully resolved and consistent.
Phase 9 - PRE_IR_CHECKS: Code flow analysis. Detects unset variables (E08020), missing guards (E08030), uninitialised returns (E08050), and validates DI wiring. This is the most important phase for catching logic errors.

MIDDLE PHASES (10-13)
Phase 10 - PLUGIN_RESOLUTION: Resolves external plugin points.
Phase 11 - IR_GENERATION: Generates the intermediate representation (IR) from the validated AST. The IR is the common format for all backends.
Phase 12 - IR_ANALYSIS: Analyses the IR for optimisation opportunities.
Phase 13 - IR_OPTIMISATION: Applies IR-level optimisations (currently stub implementation).

BACKEND PHASES (14-21)
Phase 14 - CODE_GENERATION_PREPARATION: Prepares for target code generation.
Phase 15 - CODE_GENERATION_CONSTANTS: Generates code for constant values.
Phase 16 - CODE_GENERATION_APPLICATIONS: Generates code for application entry points.
Phase 17 - CODE_GENERATION_AGGREGATES: Generates code for classes, records, traits, and other aggregates.
Phase 18 - CODE_OPTIMISATION: Applies target-specific code optimisations.
Phase 19 - PLUGIN_LINKAGE: Links external plugins into the compiled output.
Phase 20 - APPLICATION_PACKAGING: Packages the compiled application.
Phase 21 - PACKAGING_POST_PROCESSING: Final post-processing and cleanup.

WHERE ERRORS ARE CAUGHT
Most developer errors are caught in phases 2-9 (the frontend). These phases run quickly and provide immediate feedback. The LSP runs through phase 9, which is why you get comprehensive error detection in your editor.

PHASE CONTROL
The '-Cp N' flag stops compilation at phase N. This is not something end users typically need, but it is valuable in two scenarios: AI tools learning EK9 can verify that syntax and types compile correctly without generating runnable programs, and compiler developers can isolate specific phases for testing. For example, '-Cp 9' runs all validation without code generation.

WHY MULTI-PHASE?
A multi-phase approach allows each phase to assume that previous phases have validated their concerns. Phase 9 (flow analysis) does not need to check for duplicate names because phase 3 already did that. This makes each phase simpler, more focused, and more maintainable. It also enables the LSP to stop at phase 9 for fast feedback.

See Q246 for debugging strategies. See Q249 for language server integration. See Q252 for verbose compilation modes.

defines module qa.debugging.compilerphases

  defines class

    Shape as abstract
      name <- String()

      Shape()
        -> shapeName as String
        this.name :=: shapeName

      area() as pure abstract
        <- rtn as Float?

      operator $ as pure
        <- rtn as String: `${name}: area=${area()}`

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

    Circle extends Shape
      radius <- Float()

      Circle()
        -> r as Float
        super("Circle")
        this.radius :=: r

      override area() as pure
        <- rtn as Float: 3.14159 * radius * radius

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

    Rectangle extends Shape
      width <- Float()
      height <- Float()

      Rectangle()
        ->
          w as Float
          h as Float
        super("Rectangle")
        this.width :=: w
        this.height :=: h

      override area() as pure
        <- rtn as Float: width * height

      override operator ? as pure
        <- rtn as Boolean: width? and height?

  defines function

    describeShape() as pure
      -> shape as Shape
      <- rtn as String: `${shape}`

  defines program

    CompilerPhasesDemo()
      stdout <- Stdout()

      // Class hierarchy (validated in phase 6)
      circle <- Circle(5.0)
      rect <- Rectangle(3.0, 4.0)

      // Method dispatch (resolved in phase 7)
      stdout.println(describeShape(circle))
      stdout.println(describeShape(rect))

      // Flow analysis (checked in phase 9)
      shapes <- List() of Shape
      shapes += circle
      shapes += rect

      for shape in shapes
        stdout.println(`Shape: ${shape}`)

      // Guard pattern (flow analysis phase 9)
      parsed <- Float("2.5")
      if parsed?
        result <- parsed * parsed
        stdout.println(`Squared: ${result}`)

      // Try/catch (phase 9 validates exception flow)
      try
        stdout.println("Compilation phases demo complete")
      catch
        -> ex as Exception
        stdout.println(`Error: ${ex.reason()}`)

Java: javac is essentially single-pass with some deferred resolution, no phase control. Python: parsing then bytecode compilation, no intermediate phases exposed. Rust: multi-phase (parsing, name resolution, type checking, borrow checking, MIR, LLVM IR), some phase control with -Z flags. Go: fast single-pass compilation, no phase control. C++: preprocessing, compilation, assembly, linking as separate tools. Kotlin: multi-phase similar to Java, no phase control exposed. EK9: explicit 22-phase pipeline, phase-level control with -Cp flag, LSP stops at phase 9 for fast feedback, clear separation of concerns across phases.

analysis backend compilation compile compiler debug error-message flow frontend generation ir multi-phase parsing phase pipeline resolution symbol troubleshoot

Explanation: The method 'describe()' does not exist on Circle. Use the standalone function 'describeShape()' which accepts a Shape parameter. See ek9 -h E50060 for details.


Question: What is the Any type and when should I use it?

How does the universal base type work in EK9?

What is the root of the EK9 type hierarchy?

When should I use Any instead of a specific type?

Answer:
Any is the universal base interface in EK9. Every type implicitly inherits from Any, making it the root of the entire type hierarchy. This is similar to Java's Object or Python's object, but Any is an interface, not a class.

DEFAULT OPERATORS FROM ANY
All types inherit six default operators from Any:
  ? (isSet) - check if object has a meaningful value
  == (equality) - compare two objects for equality
  <=> (comparison) - three-way comparison for ordering
  $ (string) - convert to human-readable String representation
  $$ (json) - convert to JSON representation
  #? (hashcode) - compute a hash code for the object
These operators are always available on every type and can be overridden with 'override operator' in your own types.

ANY IN THE DISPATCHER PATTERN
The most common use of Any is as the fallback parameter in a dispatcher method. The dispatcher entry point takes an Any parameter, and specific overloads handle known types:
  describe() as dispatcher
    -> item as Any
    <- rtn as String: "Unknown"
  private describe()
    -> item as Integer
    <- rtn as String: "Integer"
The Any overload catches everything not handled by specific overloads.

COST-BASED MATCHING WITH ANY
When the compiler resolves which dispatcher overload to call, Any matches score HIGH_COST (20.0). This means Any is always the LEAST preferred match. Exact type matches score 0.0, superclass matches score 0.05 per level, trait matches score 0.10 per level, and promotion matches score 0.5. Any's 20.0 cost ensures specific overloads always win.

WHEN TO USE ANY
  Dispatcher entry points - the base method that catches unhandled types
  Generic programming - when you truly need to work with any type
  Polymorphic containers - when mixing types is genuinely required
  Framework-level code - infrastructure that must handle arbitrary types

WHEN NOT TO USE ANY
  Specific type handling - use the actual type for type safety
  API parameters - use specific types so callers know what to pass
  Return types - specific return types help callers avoid type checks
  When you know the type - Any loses compile-time type information

Using Any too broadly is a code smell. If you find yourself using Any everywhere and then dispatching on types, consider whether your design could use traits or abstract classes to express the relationship directly.

See Q24 for the dispatcher pattern and type conversion. See Q211 for double dispatch. See Q238 for the finite operator set. See Q255 for method resolution costs. See Q267 for anti-patterns including overusing Any.

defines module qa.advancedtypes.anytype

  defines class

    TypeDescriber

      describe() as dispatcher
        -> item as Any
        <- rtn as String: "Unknown type"

      private describe()
        -> item as Integer
        <- rtn as String: `Integer: ${item}`

      private describe()
        -> item as Float
        <- rtn as String: `Float: ${item}`

      private describe()
        -> item as String
        <- rtn as String: `String: ${item}`

      private describe()
        -> item as Boolean
        <- rtn as String: `Boolean: ${item}`

  defines program

    AnyTypeDemo()
      stdout <- Stdout()

      describer <- TypeDescriber()

      // Heterogeneous list creates List of Any
      items <- [42, 3.14, "hello", true]
      for listItem in items
        stdout.println(describer.describe(listItem))

      // Any fallback for types without specific overloads
      mixedItems <- [Date(), Duration()]
      for mixedItem in mixedItems
        stdout.println(describer.describe(mixedItem))

      // Default operators available on every type via Any
      greeting <- "Hello"
      stdout.println(`isSet: ${greeting?}`)
      stdout.println(`string: ${greeting}`)
      stdout.println(`hashcode: ${#?greeting}`)

Java: Object is the root class, all classes extend Object, has equals/hashCode/toString. Python: object is the root, all classes inherit from it, has __eq__/__hash__/__str__. C#: object/System.Object is universal base, has Equals/GetHashCode/ToString. Rust: no universal base type, uses trait objects (dyn Any) for type erasure. Go: interface{} (or 'any' in Go 1.18+) is the empty interface, no methods guaranteed. Kotlin: Any is the root type with equals/hashCode/toString. EK9: Any is a universal base interface (not class) with six default operators (?, ==, <=>, $, $$, #?), HIGH_COST (20.0) matching ensures specific types always preferred in dispatchers.

advanced any base cost default dispatcher fallback hierarchy inherit interface migrate object operator root type type-system universal

Explanation: TypeDescriber does not have a process() method. The correct method name is describe(). Using a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.


Question: How does method resolution work with overloading?

How does EK9 pick which overloaded method to call?

What is cost-based method matching in EK9?

How does the compiler resolve ambiguous method calls?

What are the cost levels in EK9 method resolution?

Answer:
EK9 uses a cost-based matching algorithm to resolve which overloaded method to call. Each potential match is assigned a numeric cost, and the method with the lowest total cost wins.

FIVE COST LEVELS
The compiler assigns costs based on how well each argument matches each parameter:
  ZERO_COST (0.0) - exact type match, always preferred
  SUPER_COST (0.05) - superclass match, 0.05 per inheritance level
  TRAIT_COST (0.10) - trait match, 0.10 per trait level
  COERCION_COST (0.5) - type promotion via #^ operator
  HIGH_COST (20.0) - Any type match, universal fallback

HOW MATCHING WORKS
For each candidate method, the compiler sums the costs of all parameter matches. The match percentage is calculated as 100.0 minus totalCost. Higher percentage means better match. The method with the highest percentage (lowest cost) wins.

Examples:
  Exact Integer match: cost 0.0, percentage 100.0%
  Superclass one level: cost 0.05, percentage 99.95%
  Trait match: cost 0.10, percentage 99.90%
  Promoted Integer to Float: cost 0.5, percentage 99.5%
  Any fallback: cost 20.0, percentage 80.0%

AMBIGUITY DETECTION
If two methods score within 0.001 of each other, the compiler reports an ambiguity error rather than guessing. This prevents subtle bugs where adding a new overload silently changes which method gets called.

DISPATCHER RESOLUTION ORDER
In a dispatcher, this cost system determines which overload handles each type:
  1. Exact type match scores 100% - always called first
  2. Parent class match scores near 100% - called for subtype
  3. Trait match scores near 100% - called for implementor
  4. Promotion match scores 99.5% - called after type widening
  5. Any fallback scores 80% - called only when nothing else matches

MULTI-PARAMETER COSTS
With multiple parameters, costs are summed. A method with two exact matches (cost 0.0 + 0.0 = 0.0) beats a method with one exact and one promoted match (cost 0.0 + 0.5 = 0.5).

See Q24 for the dispatcher pattern. See Q25 for the promote operator and coercion cost. See Q254 for Any type and HIGH_COST. See Q60 for function dispatching. See Q258 for type coercion details.

defines module qa.advancedtypes.methodresolution

  defines class

    // Three-level hierarchy for demonstrating SUPER_COST
    Shape as open
      default Shape() as pure

      override operator ? as pure
        <- rtn <- true

    Circle extends Shape as open
      default Circle() as pure

    SmallCircle extends Circle
      default SmallCircle() as pure

    // Class with two differently-named methods — no overloading
    Renderer

      // render accepts (Shape, Circle)
      render()
        ->
          first as Shape
          second as Circle
        require first? and second?

      // handleShape accepts (Circle, Shape) — different name, no ambiguity
      handleShape()
        ->
          first as Circle
          second as Shape
        require first? and second?

  defines program

    MethodResolutionDemo()
      stdout <- Stdout()

      shape <- Shape()
      circle <- Circle()
      small <- SmallCircle()

      renderer <- Renderer()

      // Exact match: render(Shape, Circle) cost 0.0 + 0.0 = 0.0
      renderer.render(shape, circle)

      // SmallCircle matches Circle at SUPER_COST: cost 0.0 + 0.05 = 0.05
      renderer.render(shape, small)

      // handleShape is a separate method — no overload ambiguity
      // circle matches Circle(0.0), small matches Shape at 0.10
      renderer.handleShape(circle, small)

      stdout.println("Method resolution completed")

Java: method overloading resolved at COMPILE time based on declared types, not runtime types. Most specific match wins per JLS rules. C++: overload resolution uses implicit conversion sequences ranked by category. Python: no method overloading, uses functools.singledispatch for single-argument dispatch. Rust: no method overloading at all, uses trait dispatch. Go: no method overloading. Kotlin: compile-time overload resolution similar to Java. Julia: multiple dispatch with specificity rules similar to EK9's cost model. EK9: cost-based RUNTIME resolution, five distinct cost levels, ambiguity detection within 0.001 tolerance, works with dispatchers for runtime type-based dispatch.

advanced ambiguity coercion cost dispatch handler matching method overload percentage promotion resolution sealed super trait type type-system visitor zero

Explanation: Renaming handleShape to render creates two crossed overloads: render(Shape,Circle) and render(Circle,Shape). Calling render(circle,small) then costs 0.10 for both overloads: circle to Shape(0.05)+small to Circle(0.05) vs circle to Circle(0)+small to Shape(0.10). Equal costs trigger E50210 METHOD_AMBIGUOUS. See ek9 -h E50210 for details.


Question: What is the Void type in EK9?

How do functions with no return value work in EK9?

What happens when a function does not declare a return?

Answer:
Void represents the concept of no return value in EK9. It is the implicit return type when a function or method has no return declaration (no <- line). You cannot instantiate Void, use it as a variable type, or use it as a generic type parameter.

IMPLICIT VOID
When a function or method omits the return declaration, its return type is Void:
  greet()
    -> name as String
    stdout.println(name)
This function returns Void because there is no <- declaration. You cannot assign its result to a variable.

FUNCTIONS WITH RETURN VALUES
A function with a <- declaration has an explicit return type:
  doubled()
    -> n as Integer
    <- rtn as Integer: n * 2
This function returns Integer, not Void.

VOID VS UNSET
These are fundamentally different concepts:
  Void: the concept of a value does not apply at all. The function produces no result.
  Unset: an object exists but has no meaningful value yet. Integer() is an unset Integer.
A Void function cannot produce a result. An unset variable can later receive a value.

VOID RESTRICTIONS
  Cannot instantiate: Void() is not valid
  Cannot declare variables: v as Void is not valid
  Cannot use as generic parameter: List of Void is not valid
  Cannot use in stream pipelines: mapping to Void is not valid
  Cannot assign result: result <- voidFunction() causes a compile error

Void's isSet always returns false because there is no value to be set.

WHY VOID EXISTS
Void gives the type system a way to represent the absence of a return type. Without Void, the compiler would need special-case handling for functions that return nothing. With Void as a type, the type system is uniform: every function has a return type, and Void is the type that means no result.

See Q29 for the unset concept and tri-state semantics. See Q49 for defining functions. See Q50 for function return values.

defines module qa.advancedtypes.voidtype

  defines function

    <?-
      A function that returns nothing (Void).
      No <- declaration means Void return type.
    -?>
    greet()
      -> name as String
      stdout <- Stdout()
      stdout.println(`Hello, ${name}!`)

    <?-
      A function with an explicit return value.
      The <- declaration specifies the return type.
    -?>
    doubled() as pure
      -> n as Integer
      <- rtn as Integer: n * 2

    <?-
      A pure function that formats a greeting.
    -?>
    formatGreeting() as pure
      -> name as String
      <- rtn as String: `Welcome, ${name}`

  defines program

    VoidTypeDemo()
      stdout <- Stdout()

      // Void function: just performs an action, no result
      greet("Steve")

      // Non-void function: returns a value
      result <- doubled(21)
      stdout.println(`Doubled: ${result}`)

      // Can use return value in expressions
      greeting <- formatGreeting("Alice")
      stdout.println(greeting)

      // The following would NOT compile:
      // badResult <- greet("Bob")
      // Error: cannot assign Void result to a variable

Java: void keyword (lowercase, not a type), Void class exists but rarely used directly. C#: void keyword, similar to Java. Python: functions without return implicitly return None (which IS a value). Rust: unit type () serves as void, IS a real type with one value. Go: functions without return type return nothing, no void keyword. Kotlin: Unit type (like Rust's unit), is a real singleton value. Swift: Void is a typealias for empty tuple (). EK9: Void is the implicit return type for functions without <- declaration, cannot be instantiated or used as variable type, distinct from unset.

advanced declare error function guard implicit method ok restriction result return type type-system unset void


Question: How do constrained types work in EK9?

What is the difference between constrain as and constrain by?

How do I create a type with validation constraints?

How do I restrict the values a type can hold?

Answer:
EK9 has two distinct constraint mechanisms with different semantics: value constraints that create disconnected types, and generic bounds that restrict type parameters.

VALUE CONSTRAINTS WITH CONSTRAIN AS
The 'constrain as' syntax creates a NEW type constrained by a pattern or condition:
  Name as String constrain as
    matches /^[a-zA-Z -]+$/
Name is a new type that validates its content at construction time. A bare constructor ASSERTS validity: it panics at runtime on a set invalid value (and a literal constant that provably violates is the compile error E08260). Use the fallible '.of(value)' factory for untrusted input — it returns an unset value on failure instead of panicking. Name shares operators with String (you can compare, convert to string, etc.) but it is NOT a String subtype. You cannot pass a Name where a String is expected. This is a LIKE-A relationship, not an IS-A relationship.

RANGE CONSTRAINTS WITH CONSTRAIN
The 'constrain' syntax restricts numeric values to a range:
  PositiveIndex as Integer constrain
    > 0
PositiveIndex only accepts values greater than zero. Like 'constrain as', this creates a disconnected type.

GENERIC BOUNDS WITH CONSTRAIN BY
The 'constrain by' syntax restricts generic type parameters:
  Handler of type T constrain by Comparable
This preserves IS-A relationships: T must be a subtype of the bound. Inside the generic, you can call methods from the constraining type.

WHY DISCONNECTED TYPES?
Value-constrained types are deliberately disconnected from their base type. If EmailAddress extended String, you could pass an EmailAddress anywhere a String is expected, bypassing validation. By making them disconnected, the compiler prevents accidental substitution:
  processEmail()
    -> addr as EmailAddress
    // Only accepts validated emails
  processString()
    -> text as String
    // EmailAddress cannot be passed here
This forces explicit conversion when crossing type boundaries.

CONSTRAINT VALIDATION
Constraints are checked at construction time. A bare constructor ASSERTS validity and panics at runtime if a set value violates the constraint (a violating literal constant is the compile error E08260). For untrusted input use the fallible '.of(value)' factory, which returns an unset value on failure instead of panicking:
  badName <- Name().of("123")
  if badName?
    // Will not enter: constraint failed
Use the ? operator to check if construction succeeded.

See Q195 for generic type constraints with constrain by. See Q223 for constrained enumerations. See Q27 for static typing benefits. See Q269 for input validation with constrained types.

defines module qa.advancedtypes.constrainedtypes

  defines class

    Person
      firstName as String?
      surname as String?

      default private Person() as pure

      Person() as pure
        ->
          first as String
          last as String
        firstName :=? String(first)
        surname :=? String(last)

      // Copy constructor: a constrained type (ValidPerson below) stores an INDEPENDENT copy of its
      // base, so the base needs a copy mechanism — either this public copy constructor Person(Person)
      // or a public no-arg constructor plus the ':=:' operator. Without one, ValidPerson is rejected
      // with E06131. Person keeps its no-arg constructor private, so the copy constructor is the fit.
      Person() as pure
        -> from as Person
        firstName :=? String(from.firstName)
        surname :=? String(from.surname)

      operator matches as pure
        -> pattern as RegEx
        <- rtn as Boolean: $this matches pattern

      operator $ as pure
        <- rtn as String: `${firstName} ${surname}`

      override operator ? as pure
        <- rtn as Boolean: firstName? and surname?

  defines type

    // Value constraint: Name must match letters and spaces only
    Name as String constrain as
      matches /^[a-zA-Z -]+$/

    // Range constraint: PositiveIndex must be greater than zero
    PositiveIndex as Integer constrain
      > 0

    // User-defined type constraint: Person must have first and last name
    ValidPerson as Person constrain as
      matches /^[a-zA-Z]+ [a-zA-Z]+$/

  defines program

    ConstrainedTypesDemo()
      stdout <- Stdout()

      // === VALUE CONSTRAINT: Name ===

      // Valid construction - fallible factory returns a set value
      goodName <- Name().of("Alice Smith")
      stdout.println(`Valid name set: ${goodName?}`)

      if goodName?
        stdout.println(`Name: ${goodName}`)

      // Invalid construction - fallible factory returns an unset value
      badName <- Name().of("123!@#")
      stdout.println(`Invalid name set: ${badName?}`)

      // === RANGE CONSTRAINT: PositiveIndex ===

      // Valid: greater than zero
      goodIndex <- PositiveIndex().of(5)
      stdout.println(`Valid index set: ${goodIndex?}`)

      if goodIndex?
        stdout.println(`Index: ${goodIndex}`)

      // Invalid: zero is not > 0 - fallible factory returns unset
      badIndex <- PositiveIndex().of(0)
      stdout.println(`Zero index set: ${badIndex?}`)

      // Invalid: negative - fallible factory returns unset
      negIndex <- PositiveIndex().of(-3)
      stdout.println(`Negative index set: ${negIndex?}`)

      // === USER-DEFINED TYPE CONSTRAINT: ValidPerson ===

      // Person with operator matches enables constraining
      goodPerson <- ValidPerson().of(Person("Alice", "Smith"))
      stdout.println(`Valid person set: ${goodPerson?}`)

      if goodPerson?
        stdout.println(`Person: ${goodPerson}`)

      // Person with numbers in name fails the regex constraint - fallible factory returns unset
      badPerson <- ValidPerson().of(Person("123", "456"))
      stdout.println(`Bad person set: ${badPerson?}`)

      // === GUARDED ASSIGNMENT WITH CONSTRAINTS ===

      // Guard pattern works naturally with constrained types
      if validName <- Name().of("Bob Jones")
        stdout.println(`Guarded name: ${validName}`)

      if invalidName <- Name().of("")
        stdout.println("Should not print")

Java: no built-in value constraints, uses Bean Validation annotations (@Pattern, @Min). Python: no type-level constraints, runtime checks with validators. Rust: no constrained types, uses newtype pattern with manual validation. Go: no type constraints, runtime validation. Kotlin: value classes with init validation, but still subtypes. Swift: no constrained types, uses property wrappers. C#: no type-level constraints, uses data annotations. EK9: built-in 'constrain as' for pattern constraints, 'constrain' for range constraints, creates disconnected types (LIKE-A not IS-A), validation at construction time: a bare constructor asserts validity (panics on a set invalid value, or compile error E08260 for a violating literal), while the fallible '.of(value)' factory returns unset on failure for untrusted input.

advanced bound constant constrain constraint disconnected generic like-a pattern range restrict type type-system validate validation

constrained type
type constraint
newtype

Explanation: Renaming 'operator matches' to method 'doesMatch' means Person no longer has the matches operator. The constrained type ValidPerson uses matches in its constraint expression, but the cloned operator no longer exists on the base type, triggering E50060. Constrained types inherit operators from their base — remove the operator and the constraint breaks. See ek9 -h E50060 for details.


Question: What is type coercion and promotion in method calls?

How does automatic type promotion work in EK9 method calls?

What happens when I pass an Integer where a Float is expected?

How does the #^ operator affect method resolution?

Why does EK9 only allow one level of type promotion?

Answer:
Type coercion in EK9 is the automatic widening of a value from one type to a compatible wider type. This happens through the promote operator (#^) and is tightly integrated with the cost-based method resolution system.

HOW PROMOTION WORKS
When you pass an Integer where a Float is expected, the compiler checks if Integer has a #^ operator that returns Float. It does, so the compiler automatically inserts the promotion call. The promoted value is then passed to the function.

BUILT-IN PROMOTIONS
  Integer to Float - safe numeric widening
  Character to String - single character to text
  Date to DateTime - date to full timestamp
  Millisecond to Duration - time unit widening

PROMOTION IN METHOD RESOLUTION
Promotion carries a COERCION_COST of 0.5 in the method matching system. This means:
  Exact match (0.0) always wins over a promoted match (0.5)
  Superclass match (0.05) wins over promotion
  Trait match (0.10) wins over promotion
  Promotion (0.5) wins over Any fallback (20.0)

Example: if a function has overloads for Float and Any, passing an Integer selects the Float overload (cost 0.5) over the Any overload (cost 20.0).

SINGLE PROMOTION ONLY
The compiler attempts exactly ONE promotion step. It does NOT chain promotions. If Integer promotes to Float and Float promoted to something else, the compiler would NOT go Integer to Float to that other type. It tries one step and stops.

This is a deliberate safety measure. Chained promotions in other languages are a major source of bugs:
  C++: user-defined conversions chain with standard conversions
  JavaScript: == operator coerces through multiple steps
  Scala 2: implicit conversions could chain unpredictably

USER-DEFINED PROMOTION
Define #^ on your own types for custom promotion:
  Measurement
    operator #^ as pure
      <- rtn as String: formatted text
A type can have only ONE #^ operator.

EXPLICIT PROMOTION
You can explicitly invoke promotion:
  intVal <- 42
  floatVal <- #^ intVal
This calls Integer's #^ operator directly.

See Q24 for type conversion overview. See Q25 for the promote operator in detail. See Q250 for fixing type mismatch errors. See Q255 for the full method resolution algorithm.

defines module qa.advancedtypes.typecoercion

  defines function

    <?-
      Accepts a Float parameter.
      Integer arguments are automatically promoted.
    -?>
    processFloat() as pure
      -> mainValue as Float
      <- rtn as String: `Float: ${mainValue}`

    <?-
      Accepts a String parameter.
      Character arguments are automatically promoted.
    -?>
    processString() as pure
      -> mainValue as String
      <- rtn as String: `String: ${mainValue}`

  defines class

    Temperature
      degrees <- Float()

      Temperature()
        -> initialDegrees as Float
        degrees :=: initialDegrees

      // User-defined promotion: Temperature to Float
      operator #^ as pure
        <- rtn as Float: Float(degrees)

      operator $ as pure
        <- rtn as String: `${degrees}C`

      default operator ?

  defines program

    TypeCoercionDemo()
      stdout <- Stdout()

      // === BUILT-IN PROMOTION: Integer to Float ===

      // Integer promoted to Float automatically (cost 0.5)
      floatResult <- processFloat(42)
      stdout.println(floatResult)

      // Float passed directly (cost 0.0, exact match)
      exactResult <- processFloat(3.14)
      stdout.println(exactResult)

      // === BUILT-IN PROMOTION: Character to String ===

      charResult <- processString('Z')
      stdout.println(charResult)

      directResult <- processString("hello")
      stdout.println(directResult)

      // === USER-DEFINED PROMOTION IN CALCULATION ===

      // Temperature promotes to Float via #^ for arithmetic
      boiling <- Temperature(100.0)
      freezing <- Temperature(0.0)

      // Automatic promotion: Temperature to Float in function call
      boilingResult <- processFloat(boiling)
      stdout.println(boilingResult)

      // Explicit promotion with #^
      boilingFloat <- #^ boiling
      freezingFloat <- #^ freezing
      range <- boilingFloat - freezingFloat
      stdout.println(`Temperature range: ${range}`)

Java: autoboxing + widening, Integer == Long compares identity not value, ternary widens unexpectedly. C++: implicit conversion sequences can chain standard + user-defined, explicit keyword to prevent. JavaScript: == coerces through multiple steps, === exists because == is unreliable. Python: mostly explicit, but __add__/NotImplemented/__radd__ chains exist. Rust: no implicit conversion at all, From/Into traits require explicit calls. Go: no implicit conversion, all conversions explicit. Kotlin: no implicit widening, explicit .toFloat() required. EK9: single-level promotion via #^ operator, COERCION_COST (0.5) in method resolution, no chaining, predictable and safe.

advanced automatic chain coercion conversion cost implicit method migrate promote promotion resolution single type type-system widening

Explanation: Renaming 'operator #^' to method 'asFloat' removes the promote operator from Temperature. The call processFloat(boiling) relied on automatic promotion from Temperature to Float via #^. Without the operator, the compiler finds processFloat(Float) but cannot match the Temperature argument, triggering E06270 parameter mismatch. For automatic type promotion to work, you MUST define 'operator #^', not a regular method. See ek9 -h E06270 for details.


Question: How does EK9's Result type differ from other languages?

How does Result's four-state model work?

What makes EK9's Result unique compared to Rust's Result?

Answer:
EK9's Result type has four states instead of the two states found in most languages. This is a deliberate type system design that handles real-world scenarios other languages cannot express.

FOUR STATES NOT TWO
Most languages (Rust, Swift, Kotlin) treat Result as strictly either success OR error. EK9's Result of (O, E) has four independent states:
  1. Neither: both ok and error are unset. Represents incomplete or pending.
  2. Ok only: success value present, no error.
  3. Error only: error value present, no success.
  4. Both ok AND error: success AND error both present simultaneously.

WHY FOUR STATES?
Real-world operations genuinely produce both success and error simultaneously:
  Configuration lookup fails but returns a default value plus an error code.
  Data migration converts a record but logs warnings.
  API returns partial results with error details about missing fields.
In these cases, the caller needs BOTH the usable result AND the error information. Rust's Result forces you to choose one or the other.

INDEPENDENT CHECKS
isOk() and isError() are independent boolean checks. Both can return true:
  r <- Result("Default", -1)
  r.isOk()    // true
  r.isError() // true
The ? operator checks isOk (biased toward success).

COMPILE-TIME GUARD ENFORCEMENT
The compiler enforces that you cannot access .ok() without an isOk() guard, and cannot access .error() without an isError() guard. There is no .unwrap() that panics at runtime. No escape hatches.
  if r.isOk()
    okData <- r.ok()
  if r.isError()
    errData <- r.error()

CONTRAST WITH OTHER LANGUAGES
  Rust: Result<T,E> is strictly Ok OR Err. .unwrap() panics at runtime. Forces choice between success and error.
  Go: returns (value, error) tuple. No compiler enforcement. Caller can ignore error. No type safety.
  Java: no Result type. Uses exceptions which are invisible in method signatures.
  Swift: Result<Success,Failure> is strictly success OR failure, like Rust.
  Kotlin: kotlin.Result exists but no compiler-enforced safe access.

EK9's four-state model with compile-time guards is unique. It eliminates the entire class of unwrap panics (Rust), ignored errors (Go), and unchecked exceptions (Java).

See Q48 for Result basics and creation. See Q86 for advanced guard patterns. See Q87 for Result operations. See Q139 for choosing between try/catch and Result.

defines module qa.advancedtypes.resulttypesystem

  defines function

    <?-
      Returns ok only: configuration found.
    -?>
    getConfig()
      <- rtn <- Result("production", Integer())

    <?-
      Returns error only: lookup failed.
    -?>
    getFailedConfig()
      <- rtn <- Result(String(), 404)

    <?-
      Returns BOTH ok and error: fallback used with warning.
    -?>
    getConfigWithFallback()
      <- rtn <- Result("default-value", -1)

    <?-
      Returns neither: operation not yet complete.
    -?>
    getPendingConfig()
      <- rtn <- Result() of (String, Integer)

  defines program

    ResultTypeSystemDemo()
      stdout <- Stdout()

      // === FOUR STATES ===

      okResult <- getConfig()
      errorResult <- getFailedConfig()
      bothResult <- getConfigWithFallback()
      neitherResult <- getPendingConfig()

      // isOk and isError are independent
      stdout.println(`Ok result - isOk: ${okResult.isOk()}, isError: ${okResult.isError()}`)
      stdout.println(`Error result - isOk: ${errorResult.isOk()}, isError: ${errorResult.isError()}`)
      stdout.println(`Both result - isOk: ${bothResult.isOk()}, isError: ${bothResult.isError()}`)
      stdout.println(`Neither result - empty: ${neitherResult is empty}`)

      // === COMPILE-TIME GUARDS ===

      // Guard required before accessing .ok()
      if okResult.isOk()
        stdout.println(`Config: ${okResult.ok()}`)

      // Guard required before accessing .error()
      if errorResult.isError()
        stdout.println(`Error code: ${errorResult.error()}`)

      // AND guard: both checks satisfied, both accesses safe
      if bothResult.isOk() and bothResult.isError()
        stdout.println(`Fallback value: ${bothResult.ok()}`)
        stdout.println(`Warning code: ${bothResult.error()}`)

      // === SAFE DEFAULTS ===

      // No guard needed with defaults
      safeConfig <- errorResult.okOrDefault("fallback")
      stdout.println(`Safe config: ${safeConfig}`)

      safeError <- okResult.errorOrDefault(0)
      stdout.println(`Safe error: ${safeError}`)

Rust: Result<T,E> is strictly Ok or Err (two states), .unwrap() panics at runtime, ? operator for propagation, forces Either semantics. Go: (value, error) return convention, no compiler enforcement, errors easily ignored, nil error means success. Java: no Result type, checked exceptions in signatures but unchecked exceptions invisible, try-catch is verbose. Swift: Result<Success,Failure> with associated values, strictly two states, get() throws on failure. Kotlin: kotlin.Result wrapping, no compiler-enforced guards, .getOrThrow() can fail. Haskell: Either monad is strictly Left or Right. EK9: Result of (O, E) has FOUR states (neither, ok-only, error-only, BOTH), compile-time guard enforcement, no unwrap, no escape hatches.

advanced both compile differ enforce error four guard independent isset migrate null-safe ok panic result safe safety state type type-system unique unwrap

Explanation: Guarding with isError() does not prove isOk() is true. Accessing .ok() requires an isOk() guard. The compiler tracks which guard was used and rejects mismatched access, triggering E08030. See ek9 -h E08030 for details.

Explanation: Changing 'and' to 'or' means neither isOk() nor isError() is individually guaranteed. With 'and', both guards are satisfied so both .ok() and .error() are safe. With 'or', either could be false, so the compiler rejects access to both, triggering E08030. See ek9 -h E08030 for details.


Question: What types can be used as Dict keys?

What are the requirements for Dict key types?

Can I use custom classes as Dict keys in EK9?

Do Dict keys need hashcode and comparison operators?

Answer:
Dict keys must support equality and hashing, giving O(1) lookups. Internally, Dict is backed by a Java LinkedHashMap whose key matching uses the key type's '==' (equality) and '#?' (hashcode) operators — the compiler bridges these to Java equals()/hashCode() for generated user types. Two keys match when '==' returns true and they share the same '#?' hash. The '<=>' (comparison) operator is NOT used for Dict key lookup at runtime, but it IS still required for the 'default operator' set (default '==' depends on '<=>' being defaulted first).

KEY REQUIREMENTS
A type used as a Dict key must implement:
  operator == (equality) - used for Dict key matching at runtime; required
  operator #? (hashcode) - used for Dict O(1) hashing at runtime; required
  operator <=> (comparison) - prerequisite for the 'default operator' set and for sorting; not used for Dict lookup itself, returns Integer
  operator $ (string) - for string representation
  operator ? (isSet) - for validity checking

BUILT-IN KEY TYPES
All these types work as Dict keys out of the box:
  String, Integer, Float, Character, Boolean
  Date, DateTime, Duration, Millisecond
  Enumerations (all enum values support comparison)

CUSTOM KEY TYPES
Use records with default operators for the simplest approach:
  PlayerName
    firstName as String: String()
    lastName as String: String()
    default PlayerName()
    PlayerName() ...
    default operator <=>
    default operator #?
    default operator ==
    default operator $
    default operator ?
The 'default' keyword auto-generates these operators based on field declarations.

You can also implement operators manually for custom comparison logic.

DICT INTERNALS
Dict uses LinkedHashMap internally, which means insertion order is preserved. When you iterate over a Dict, entries come out in the order they were added. Key equality is determined by the '==' operator (bridged to the generated equals()/hashCode()), and keys are hashed via '#?', giving O(1) contains/getOrDefault/remove via the native LinkedHashMap rather than a linear scan.

ENUMERATION KEYS
Enumerations make excellent Dict keys because they have built-in comparison and hashcode:
  scores <- Dict() of (Direction, Integer)
  scores += DictEntry(Direction.North, 10)

See Q46 for Dict basics. See Q90 for Dict operations. See Q129 for handling missing Dict keys. See Q167 for Dict iteration patterns.

defines module qa.advancedtypes.dictkeytype

  defines record

    PlayerName
      firstName as String: String()
      lastName as String: String()

      default PlayerName()

      PlayerName()
        ->
          fn as String
          ln as String
        firstName: fn
        lastName: ln

      default operator <=>
      default operator #?
      default operator ==
      default operator $
      default operator ?

  defines program

    DictKeyTypesDemo()
      stdout <- Stdout()

      // === BUILT-IN TYPES AS KEYS ===

      // String keys (most common)
      ages <- {"Alice": 30, "Bob": 25}
      stdout.println(`String keys: ${ages}`)

      // Integer keys
      labels <- {1: "first", 2: "second", 3: "third"}
      stdout.println(`Integer keys: ${labels}`)

      // === CUSTOM RECORD AS KEY ===

      scores <- Dict() of (PlayerName, Integer)
      scores += DictEntry(PlayerName("Alice", "Smith"), 95)
      scores += DictEntry(PlayerName("Bob", "Jones"), 87)
      scores += DictEntry(PlayerName("Charlie", "Brown"), 72)

      // Lookup by key (matched via == and #? hashing)
      aliceScore <- scores.getOrDefault(PlayerName("Alice", "Smith"), 0)
      stdout.println(`Alice score: ${aliceScore}`)

      // Missing key returns default
      unknownScore <- scores.getOrDefault(PlayerName("Dave", "Wilson"), 0)
      stdout.println(`Unknown score: ${unknownScore}`)

      // === ITERATION PRESERVES INSERTION ORDER ===

      for entry in scores
        stdout.println(`${entry.key()}: ${entry.value()}`)

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

      stdout.println(`Score count: ${length scores}`)
      stdout.println(`Scores empty: ${scores is empty}`)

Java: HashMap requires equals() and hashCode() on keys, LinkedHashMap preserves insertion order. Python: dict keys must be hashable (__hash__ and __eq__). Rust: HashMap keys need Hash + Eq traits. Go: map keys must be comparable (==). C#: Dictionary keys need GetHashCode() and Equals(). Kotlin: same as Java (equals/hashCode). EK9: Dict keys match by '==' (equality) and '#?' (hashcode) via a backing LinkedHashMap (insertion order preserved); records use 'default operator' to auto-generate the full set, which also includes '<=>' (still the default-operator prerequisite).

advanced comparison custom dict enumeration hashcode insertion key operator order record requirement type type-system

Explanation: Removing 'default operator <=>' from PlayerName means the record has no comparison operator. This cascades: 'default operator ==' depends on '<=>' and fails with E07180. Without comparison and equality, the type cannot function as a Dict key. The <=> operator is foundational — other operators depend on it and Dict key lookup requires it. See ek9 -h E07180 for details.


Question: What is idiomatic EK9 code?

What are EK9 best practices and conventions?

Answer:
Idiomatic EK9 uses the language's built-in features to write concise, safe, and expressive code. Four key idiom categories:

CONCISENESS: Backtick interpolation (Q113). Coalescing: ??, ?:, <?, >? (Q243). Guarded assignment :=? (Q74-Q78). Natural language operators: contains, matches (Q171). Control flow as expressions (Q68, Q80-Q83).

OPERATORS: Fixed set of ~50 operators (Q238). Default operators auto-generate ==, #?, $ (Q102). Pure vs mutation: + creates new, += mutates (Q241). Copy/merge/replace: :=:, :~:, :^: (Q87, Q90).

SAFETY: Purity via as pure (Q54). Universal guards (Q74-Q78). Tri-state: absent/unset/set, no null (Q29). No break/continue/return (Q148). Constrained types (Q257).

COMPOSITION: Closed types by default (Q102). Trait delegation with by (Q210). Dynamic functions/classes (Q115). Stream pipelines (Q235-Q237). Dispatcher pattern (Q211).

See Q29, Q48, Q54, Q55, Q68, Q69, Q74, Q80, Q87, Q90, Q102, Q113, Q115, Q148, Q171, Q210, Q211, Q215, Q235-Q238, Q241, Q243, Q254, Q257-Q259, Q268 for details on each pattern.

defines module qa.patterns.idiomatic

  defines function

    isNonEmpty() as pure
      -> item as String
      <- rtn as Boolean: item?

  defines program

    IdiomaticEk9Demo()
      stdout <- Stdout()

      // === GUARD EXPRESSION ===
      // Block only executes if the value is set

      greeting <- String("Hello EK9")
      if msg <- greeting
        stdout.println(msg)

      // === STRING INTERPOLATION ===

      lang <- "EK9"
      stdout.println(`Welcome to ${lang}`)

      // === COALESCING FOR DEFAULTS ===

      label <- String()
      displayLabel <- label ?: "default"
      stdout.println(`Label: ${displayLabel}`)

      // === GUARDED ASSIGNMENT ===
      // First-wins pattern

      config <- String()
      config :=? "first-wins"
      config :=? "ignored"
      stdout.println(`Config: ${config}`)

      // === STREAM PIPELINE ===

      words <- ["hello", "", "ek9", "", "code"]
      nonEmpty <- cat words | filter by isNonEmpty | collect as List of String
      stdout.println(`Non-empty words: ${length nonEmpty}`)

      // === NATURAL LANGUAGE OPERATOR ===

      text <- "Hello World"
      if text contains "World"
        stdout.println("Found World in text")

      stdout.println("Idiomatic EK9 in action")

Java: verbose, null-heavy, mutable/open by default. Python: dynamic typing, no compile-time safety. Rust: ownership complexity, similar Result pattern. EK9: combines safety (tri-state, guards, purity), expressiveness (operators, streams), and architecture (closed types, delegation) into a coherent idiom set.

best convention design good idiom idiomatic overview pattern philosophy practice proper right style way write

Explanation: Renaming the variable means later references to 'greeting' become unresolved, triggering E50001 — not resolved. Variable names must be consistent. See ek9 -h E50001 for details.

Explanation: Renaming the variable means later references to 'lang' become unresolved, triggering E50001 — not resolved. Variable names must be consistent. See ek9 -h E50001 for details.


Question: How do I implement the observer pattern in EK9?

How do I use events and listeners in EK9?

How do I implement publish-subscribe in EK9?

How do function delegates work as event listeners?

Answer:
EK9 implements the observer pattern using abstract functions as listener types and lists of function delegates as subscriber collections. This avoids the interface boilerplate of Java's Observer/Observable.

ABSTRACT FUNCTION AS LISTENER
Define the listener contract as an abstract function:
  Listener() as abstract
    -> event as String
This is the subscription interface. Any function matching this signature can be a listener.

EVENT EMITTER CLASS
Store listeners in a list and iterate to notify:
  EventEmitter
    listeners as List of Listener
    subscribe()
      -> listener as Listener
      listeners += listener
    emit()
      -> event as String
      for listener in listeners
        listener(event)

DYNAMIC FUNCTION SUBSCRIBERS
Create inline listeners with dynamic functions:
  printListener <- () is Listener as function
    Stdout().println(event)
The event parameter comes from the Listener abstract function signature.

CAPTURE FOR CONTEXT
Use explicit capture to add context to listeners:
  prefix <- "[AUDIT]"
  auditListener <- (prefix) is Listener as function
    Stdout().println(prefix + ": " + event)
The prefix is captured by value at creation time.

ADDING LISTENERS
Subscribe without modifying the emitter:
  emitter.subscribe(printListener)
  emitter.subscribe(auditListener)
  emitter.emit("user-login")
New listeners can be added at any time.

CONSUMER VS ACCEPTOR
Listeners that only read are consumers (pure). Listeners that cause side effects (printing, logging) are acceptors (impure). The abstract function's purity annotation controls this.

See Q54 for consumer and acceptor function types. See Q55 for function delegates. See Q115 for dynamic classes with capture. See Q214 for strategy pattern using similar function callbacks.

defines module qa.patterns.observer

  defines function

    Listener() as abstract
      -> event as String

  defines class

    EventEmitter
      listeners as List of Listener: List() of Listener

      subscribe()
        -> listener as Listener
        listeners += listener

      emit()
        -> event as String
        for listener in listeners
          listener(event)

      subscriberCount() as pure
        <- rtn as Integer: length listeners

      default operator ?

  defines program

    ObserverPatternDemo()
      stdout <- Stdout()

      emitter <- EventEmitter()

      // === DYNAMIC FUNCTION LISTENERS ===

      printListener <- () is Listener as function
        Stdout().println(`Event received: ${event}`)

      // Listener with captured context
      prefix <- "[AUDIT]"
      auditListener <- (prefix) is Listener as function
        Stdout().println(`${prefix} ${event}`)

      // === SUBSCRIBE ===

      emitter.subscribe(printListener)
      emitter.subscribe(auditListener)

      stdout.println(`Subscribers: ${emitter.subscriberCount()}`)

      // === EMIT EVENTS ===

      emitter.emit("user-login")
      emitter.emit("data-saved")

      // === ADD MORE LISTENERS LATER ===

      countListener <- () is Listener as function
        Stdout().println(`Counted: ${event}`)

      emitter.subscribe(countListener)
      emitter.emit("final-event")

Java: Observer/Observable (deprecated), listeners via interfaces, anonymous inner classes, lambda expressions since Java 8, EventListener pattern. Python: no built-in observer, callback functions or third-party libraries. JavaScript: EventEmitter (Node.js), addEventListener (DOM), callback-heavy. Rust: no built-in observer, channels or callback closures. Go: channels for pub/sub, callback functions. EK9: abstract functions as listener types, List of Listener for subscriber management, dynamic functions with capture for inline listeners.

callback delegate design emit event idiom listener migrate notify observer pattern publish subscribe

Explanation: Class fields must be initialized inline. Declaring a field without an initializer triggers E08180. Always provide an initializer, such as 'List() of Listener' for an empty list. See ek9 -h E08180 for details.

Explanation: Renaming the variable means later references to 'emitter' become unresolved, triggering E50001. Variable names must be consistent throughout the scope. See ek9 -h E50001 for details.