Question: How does method overloading work compared to function dispatching?

What is the difference between method overloading and dispatcher in EK9?

When should I use dispatcher instead of overloaded methods?

How does EK9 resolve overloaded method calls?

Answer:
EK9 has two distinct mechanisms for calling different code based on types: method overloading (compile-time resolution) and dispatching (runtime resolution).

METHOD OVERLOADING
Classes can have multiple methods with the same name but different parameter types. The compiler resolves which overload to call at COMPILE time using cost-based matching: exact type match (cost 0), subtype (cost 1), promotion like Integer to Float (cost 2), and Any match (cost 3). The lowest cost wins. If two overloads tie, the compiler reports an ambiguity error.

DISPATCHER KEYWORD
The 'dispatcher' keyword enables RUNTIME type-based dispatch. Mark a method as dispatcher and provide overloaded implementations:
  format() as dispatcher
    -> item as Any
    <- rtn as String: $item
  format()
    -> item as Integer
    <- rtn as String: "Int: " + $item
When called, the runtime checks the actual type of the argument and dispatches to the most specific overload.

KEY DIFFERENCE
With overloading, if you have a variable typed as 'Any' that holds an Integer, the compiler selects the 'Any' overload. With dispatcher, the runtime selects the 'Integer' overload because it examines the actual type.

FUNCTION DISPATCHING
Standalone functions cannot share names in the same module. For function-level dispatch, use the 'as dispatcher' modifier. Dispatchers are limited to one or two parameters, must have the same parameter count across overloads, and must share the same purity.

WHEN TO CHOOSE
Use method overloading when the caller knows the exact type at compile time. Use dispatcher when processing heterogeneous collections or when the runtime type matters. Dispatcher eliminates the need for the visitor pattern.

See Q60 for function dispatching basics. See Q105 for method dispatch in class hierarchies. See Q255 for cost-based method resolution details. See Q596 for function vs method distinction.

defines module qa.functionsAndMethods.overloadingVsDispatch

  defines class

    //Class with overloaded methods (compile-time resolution)
    Formatter
      formatItem() as pure
        -> item as String
        <- rtn as String: "String: " + item

      formatItem() as pure
        -> item as Integer
        <- rtn as String: "Integer: " + $item

      formatItem() as pure
        -> item as Float
        <- rtn as String: "Float: " + $item

    //Class using dispatcher (runtime resolution)
    RuntimeFormatter
      describe() as dispatcher
        -> item as Any
        <- rtn as String: "Any type"

      describe()
        -> item as Integer
        <- rtn as String: "Int: " + $item

      describe()
        -> item as String
        <- rtn as String: "Str: " + item

  defines program

    OverloadingVsDispatchDemo()
      stdout <- Stdout()

      formatter <- Formatter()

      // === COMPILE-TIME OVERLOADING ===
      // Compiler selects overload based on declared type
      stdout.println(formatter.formatItem("hello"))
      stdout.println(formatter.formatItem(42))
      stdout.println(formatter.formatItem(3.14))

      // === RUNTIME DISPATCHING ===
      // Dispatcher selects overload based on runtime type
      runtimeFmt <- RuntimeFormatter()

      //Direct calls: same as overloading
      stdout.println(runtimeFmt.describe(42))
      stdout.println(runtimeFmt.describe("hello"))

      //Heterogeneous list: runtime dispatch shines here
      items <- [1, 2.5, "text"]
      for item in items
        stdout.println(runtimeFmt.describe(item))

Java: method overloading resolved at compile time, visitor pattern for runtime dispatch, instanceof checks as workaround, no multiple dispatch. Python: no method overloading (last definition wins), functools.singledispatch for single parameter. JavaScript: no overloading, manual type checking with typeof. Rust: no method overloading, trait dispatch for polymorphism, match on enums. Go: no overloading, type switch for runtime dispatch. Kotlin: method overloading like Java, when with is for runtime checks, no dispatcher. C#: method overloading at compile time, dynamic keyword for runtime. Julia: multiple dispatch built into language, most similar to EK9 dispatcher. EK9: method overloading with cost-based compile-time resolution, 'dispatcher' keyword for runtime type-based dispatch, eliminates visitor pattern.

compile-time cost dispatch dispatcher handler matching method overloading parameter resolution runtime sealed type visitor

formatItem() as pure
        -> item as String
        <- rtn as String: "String: " + item
        stdout.println(item)

Explanation: A pure method cannot call non-pure methods like stdout.println(). Pure methods must have no side effects. See ek9 -h E50001 for details.


Question: How do dynamic functions replace nested functions in EK9?

Can I define a function inside another function in EK9?

What replaces nested functions or local functions in EK9?

How do I create helper functions scoped to another function?

Answer:
EK9 does not support nested function definitions. You cannot define a named function inside another function. Dynamic functions with closures serve the same purpose: they are created inline, capture variables from the enclosing scope, and exist only within that scope.

WHY NO NESTED FUNCTIONS
Nested function definitions complicate scope analysis and name resolution. They create hidden coupling between the inner function and the outer function's variables. EK9 keeps function definitions flat (at module level) to make dependencies explicit.

DYNAMIC FUNCTIONS AS LOCAL HELPERS
Define an abstract function type at module level, then create dynamic implementations inline where needed:
  Validator as pure abstract
    -> text as String
    <- valid as Boolean?

Inside a program or function body, create a dynamic function:
  checker <- (minLen) is Validator as pure function
    valid:=? length text >= minLen

The dynamic function captures 'minLen' from the enclosing scope and implements the Validator contract. It behaves exactly like a nested function: scoped locally, accessing outer variables, callable only within the enclosing scope.

INLINE SINGLE-EXPRESSION FORM
For simple helpers, use inline syntax:
  notEmpty <- () is Validator as pure (valid:=? length text > 0)

MULTIPLE LOCAL HELPERS
Create several dynamic functions in the same scope, each capturing different values:
  short <- (shortLen: 3) is Validator as pure (valid:=? length text >= shortLen)
  long <- (longLen: 10) is Validator as pure (valid:=? length text >= longLen)

POLYMORPHIC USE
Because all dynamic functions implement the same abstract type, they can be stored in lists and iterated polymorphically.

See Q52 for dynamic function syntax. See Q53 for closure capture mechanics. See Q115 for dynamic classes. See Q590 for dynamic function extension. See Q596 for function vs method distinction.

defines module qa.functionsAndMethods.dynamicAsNested

  defines function

    //Abstract function type: the contract for validators
    Validator as pure abstract
      -> text as String
      <- valid as Boolean?

    //Named implementation for comparison
    NotBlankValidator is Validator as pure
      -> text as String
      <- valid as Boolean: length text > 0

  defines program

    DynamicAsNestedDemo()
      stdout <- Stdout()

      // === DYNAMIC FUNCTION AS LOCAL HELPER ===

      //Captures minLen from enclosing scope
      minLen <- 5
      lengthChecker <- (minLen) is Validator as pure function
        valid:=? length text >= minLen

      stdout.println(`"hello" valid (min 5): ${lengthChecker("hello")}`)
      stdout.println(`"hi" valid (min 5): ${lengthChecker("hi")}`)

      // === INLINE SINGLE-EXPRESSION FORM ===

      notEmpty <- () is Validator as pure (valid:=? length text > 0)
      stdout.println(`"" not empty: ${notEmpty("")}`)
      stdout.println(`"x" not empty: ${notEmpty("x")}`)

      // === MULTIPLE LOCAL HELPERS WITH DIFFERENT CAPTURES ===

      shortLen <- 3
      longLen <- 10
      shortChecker <- (shortLen) is Validator as pure (valid:=? length text >= shortLen)
      longChecker <- (longLen) is Validator as pure (valid:=? length text >= longLen)

      testWord <- "medium"
      stdout.println(`"${testWord}" short enough: ${shortChecker(testWord)}`)
      stdout.println(`"${testWord}" long enough: ${longChecker(testWord)}`)

      // === POLYMORPHIC: all are Validator type ===

      validators <- [NotBlankValidator, notEmpty, lengthChecker, shortChecker, longChecker]
      for validator in validators
        stdout.println(`validates "test": ${validator("test")}`)

Java: no nested methods (until Java 22 unnamed classes), inner classes as workaround, lambdas for inline behaviour. Python: def inside def creates nested functions, closures capture by reference (late-binding bug). JavaScript: function inside function creates closures, common pattern but 'this' binding is error-prone. Rust: closures defined inline with |args| body, cannot define named fn inside fn (unstable), closures capture by reference or move. Go: anonymous functions inline, func() {} closures, capture by reference. Kotlin: local functions (fun inside fun) supported, closures capture mutable state. Swift: nested functions supported, closures for inline behaviour. EK9: no nested function definitions, dynamic functions with explicit capture serve the same purpose, scoped to enclosing function, polymorphic through abstract function types.

abstract anonymous capture closure dynamic function helper inline local method nested parameter scope

NotBlankValidator is Validator
      -> text as String
      <- valid as Boolean: length text > 0

Explanation: When the abstract function is declared as pure, all implementations must also be pure. Omitting 'as pure' triggers E05150 because 'pure' in super requires 'pure' for this definition. See ek9 -h E05150 for details.

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


Question: What is a circular type hierarchy and why does EK9 ban it?

What happens with circular inheritance in EK9?

How does EK9 prevent circular class hierarchies?

Answer:
A circular type hierarchy occurs when a class directly or indirectly extends itself, creating an impossible loop. EK9 detects this at compile time and raises E05020.

DIRECT CIRCULAR REFERENCE
The simplest case is a class extending itself:
  A extends A  //ERROR: E05020 - A depends on itself
This is logically impossible because A cannot inherit from something that hasn't been defined yet.

INDIRECT CIRCULAR CHAIN
More subtle cycles involve multiple classes:
  A extends B
  B extends A  //ERROR: E05020 - cycle A -> B -> A
The compiler traverses the full hierarchy chain and detects cycles at any depth.

WHY BANNED
Circular inheritance is logically impossible: if A inherits from B and B inherits from A, neither type can be fully defined. The method resolution order becomes undefined, field initialization creates infinite loops, and the type system becomes unsound.

WHAT TO DO INSTEAD
Use composition over inheritance. If two types need each other's features, extract the shared functionality into a common trait or base class.

See Q603 for multi-level chain detection. See Q604 for circular hierarchy checks on traits, records, and components. See Q610 for refactoring circular dependencies using composition. See Q101 for closed-by-default types.
See Q677 for abstract implementation chain. See Q679 for sealed allow-only. See Q697 for inheritance depth limit.
See Q729 for inheritance depth boundary example.

defines module qa.typehierarchy.circular

  defines class

    //CORRECT: Linear hierarchy with no cycles
    Vehicle as open
      kind()
        <- rtn as String: "vehicle"
      default operator ?

    Car extends Vehicle as open
      override kind()
        <- rtn as String: "car"
      default operator ?

    ElectricCar extends Car
      override kind()
        <- rtn as String: "electric car"
      default operator ?

  defines function

    testLinearHierarchy()
      vehicle <- Vehicle()
      car <- Car()
      electric <- ElectricCar()

      require vehicle?
      require car?
      require electric?

      //Each type in a clean linear chain: ElectricCar -> Car -> Vehicle
      require vehicle.kind() == "vehicle"
      require car.kind() == "car"
      require electric.kind() == "electric car"

Java: circular inheritance detected at compile time (same error). Python: MRO computation fails for circular hierarchies. C++: compilation error for circular inheritance. Rust: no class inheritance, traits cannot self-reference. Kotlin: compile error for circular inheritance. EK9: E05020 detects cycles at any depth in the hierarchy chain.

E05020 chain circular composition cycle extends hierarchy inheritance self-reference type

Explanation: If Car extended ElectricCar which extends Car, a circular inheritance cycle would be created. The compiler traverses the full hierarchy chain and detects cycles at any depth. See ek9 -h E05030 for details.

Explanation: When a child class has a method matching a parent method, the override keyword is required. Omitting it causes method shadowing detection. See ek9 -h E05120 for details.


Question: How does EK9 detect multi-level circular inheritance chains?

Can EK9 detect indirect circular inheritance?

What about three-way circular class hierarchies?

Does EK9 check deep circular inheritance chains?

Answer:
EK9 traverses the entire inheritance chain during type hierarchy checks and detects circular references at any depth, not just direct self-references.

TWO-LEVEL CYCLE
A extends B, B extends A creates a cycle of length 2. The compiler walks A's parent chain and finds it leads back to A.

THREE-LEVEL CYCLE
A extends B, B extends C, C extends A creates a cycle of length 3. Even though no class directly extends itself, the transitive chain A -> B -> C -> A is detected.

DEEP CHAINS
The compiler checks arbitrarily deep chains. A cycle buried five levels deep is caught just as reliably as a direct self-reference. The traversal is O(n) in the depth of the hierarchy.

HOW DETECTION WORKS
During Phase 5 (TYPE_HIERARCHY_CHECKS), the compiler walks each type's supertype chain. If it encounters the starting type again, it reports E05020. This is a standard cycle detection algorithm applied to the inheritance graph.

WHAT TO DO
Draw out the inheritance relationships to visualize the cycle. Identify which class should be the root. Break the cycle by removing one extends clause. Consider composition instead.

See Q602 for circular hierarchy basics. See Q610 for composition-based refactoring. See Q315 for inheritance depth quality checks.

defines module qa.typehierarchy.chaindetection

  defines class

    //CORRECT: Five-level linear hierarchy (no cycles)
    Animal as open
      describe()
        <- rtn as String: "animal"
      default operator ?

    Mammal extends Animal as open
      override describe()
        <- rtn as String: "mammal"
      default operator ?

    Carnivore extends Mammal as open
      override describe()
        <- rtn as String: "carnivore"
      default operator ?

    Canine extends Carnivore as open
      override describe()
        <- rtn as String: "canine"
      default operator ?

    Wolf extends Canine
      override describe()
        <- rtn as String: "wolf"
      default operator ?

  defines function

    testDeepLinearChain()
      //Five levels deep: Wolf -> Canine -> Carnivore -> Mammal -> Animal
      wolf <- Wolf()
      require wolf?
      require wolf.describe() == "wolf"

      //Polymorphic use through the hierarchy
      animals <- List() of Animal
      animals += Animal()
      animals += Mammal()
      animals += Carnivore()
      animals += Canine()
      animals += Wolf()

      for creature in animals
        require creature?

Java: detects circular inheritance at all depths (identical behavior). C#: detects cycles in class hierarchy. C++: compilation error for circular inheritance at any depth. Kotlin: compile error, same as Java. Python: raises TypeError when MRO cannot be computed. EK9: E05020 fires at any cycle depth, detected during TYPE_HIERARCHY_CHECKS phase.

E05020 chain circular cycle deep detection hierarchy inherit multi-level transitive type

require wolf.describe().toUpperCase() == "wolf"

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

Explanation: At every level of the five-level hierarchy, the override keyword is required when replacing the parent describe method. Omitting it triggers shadowing detection. See ek9 -h E05120 for details.


Question: Do traits, records, and components also check for circular hierarchies?

Does EK9 check circular inheritance on traits?

Can records have circular hierarchies in EK9?

Is E05020 only for classes or all construct types?

Answer:
E05020 applies to ALL EK9 construct types that support inheritance or implementation relationships, not just classes.

TRAITS
Traits can extend other traits, forming hierarchies. Circular trait chains (TraitA extends TraitB extends TraitA) are detected and rejected with E05020.

RECORDS
Records that extend other records are subject to the same cycle detection. A record extending another record that extends the first is a compile error.

COMPONENTS
Components participate in hierarchies through trait implementation. Circular dependencies between components are detected.

CLASSES WITH TRAITS
Even when cycles involve a mix of classes and traits (a class implementing a trait that requires extending a class that circularly depends on the original), the compiler detects the cycle.

UNIFORM ENFORCEMENT
The type hierarchy checker runs the same cycle detection algorithm across all construct types. This means you get the same error (E05020) regardless of whether the cycle involves classes, traits, records, or components.

See Q602 for circular hierarchy basics. See Q106 for trait hierarchies. See Q97 for records vs classes.

defines module qa.typehierarchy.allconstructs

  defines trait

    //CORRECT: Linear trait hierarchy
    Describable
      describe() as abstract
        <- rtn as String?

    Displayable with trait of Describable
      display() as abstract
        <- rtn as String?

  defines record

    //CORRECT: Simple record (records are closed by default)
    Point
      xPos as Float: 0.0
      yPos as Float: 0.0

      Point()
        ->
          xPos as Float
          yPos as Float
        this.xPos: xPos
        this.yPos: yPos

      operator $ as pure
        <- rtn as String: `(${xPos}, ${yPos})`

      default operator ?

  defines class

    //Class implementing trait hierarchy correctly
    Widget with trait of Displayable
      label <- "widget"

      Widget()
        -> label as String
        this.label: label

      override describe()
        <- rtn as String: label

      override display()
        <- rtn as String: `[${label}]`

      default operator ?

  defines function

    testCorrectHierarchies()
      point <- Point(3.0, 4.0)
      require point?
      require $point == "(3.0, 4.0)"

      widget <- Widget("button")
      require widget?
      require widget.describe() == "button"
      require widget.display() == "[button]"

Java: circular check on classes and interfaces. C#: circular check on classes, interfaces, and structs. Kotlin: circular check on all types including sealed. Rust: no class inheritance; trait coherence rules prevent cycles. Go: interfaces are structural, no circular hierarchy concept. EK9: uniform E05020 check across classes, traits, records, and components.

E05020 circular component construct cycle hierarchy inherit record trait type uniform

require widget.describe().toUpperCase() == "button"

Explanation: When implementing abstract trait methods like describe from Describable, the override keyword is required to show explicit intent. See ek9 -h E05120 for details.


Question: Why can't I use 'this' inside a standalone function?

Why does using 'this' in a function cause an error?

Answer:
The 'this' keyword refers to the current object instance, which only exists inside class methods, constructors, and operators. Standalone functions have no instance, so 'this' is meaningless. Using it triggers E05070.

WHERE THIS IS VALID
'this' is valid in instance methods of classes and records, constructors (accessing and initializing fields), operators (accessing the current object), and dynamic class bodies.

WHERE THIS IS INVALID
'this' is invalid in standalone functions (not bound to any instance), module-level code, and class-level constant expressions. These contexts have no object instance, so 'this' has nothing to refer to.

COMMON MISTAKE
Developers coming from Java or C# sometimes write standalone functions and use 'this' as if they were static methods with access to instance state. In EK9, functions are truly standalone: they only have access to their parameters and closure captures.

WHAT TO DO INSTEAD
If you need instance state, make it a method on a class. If the function just needs data, pass it as a parameter. If you accidentally wrote 'defines function' instead of a method inside a class, restructure the code.

See Q49 for standalone functions. See Q596 for function vs method distinction. See Q580 for using 'this' correctly in constructor delegation.

defines module qa.typehierarchy.thiscontext

  defines class

    Counter
      count <- 0

      Counter()
        -> initialCount as Integer
        //this is valid in constructors
        this.count: initialCount

      increment()
        //this is valid in methods
        this.count: this.count + 1

      currentCount() as pure
        <- rtn as Integer: count

      operator $ as pure
        <- rtn as String: `Counter(${count})`

      default operator ?

  defines function

    //Standalone function: no 'this' - only parameters and locals
    doubleValue() as pure
      -> inputValue as Integer
      <- result as Integer: inputValue * 2

    testThisContext()
      //Functions work with parameters, not 'this'
      doubled <- doubleValue(21)
      require doubled == 42

      //Classes use 'this' internally
      counter <- Counter(10)
      counter.increment()
      require counter.currentCount() == 11

Java: 'this' only in instance methods (static methods cannot use it). Python: 'self' must be explicit parameter, not available in module functions. C#: 'this' only in instance methods. Kotlin: 'this' in member functions only, extension functions use receiver. JavaScript: 'this' binding is complex, arrow functions inherit outer 'this'. EK9: 'this' strictly limited to class/record methods, constructors, and operators. E05070 if used elsewhere.

E05070 circular class context function hierarchy inherit instance method migrate scope standalone this type

doubleValue() as pure
      -> inputValue as Integer
      <- result as Integer: this.count * 2

Explanation: Standalone functions have no instance, so the this keyword is meaningless. Using this in a defines function context triggers E50001. See ek9 -h E50001 for details.

require counter.currentCount().intValue() == 11

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


Question: What is method shadowing and how does EK9 prevent it?

Why can't I have a method with the same name but different signature in a child class?

Answer:
Method shadowing occurs when a child class declares a method with the same name as a parent method but a different signature (different parameter types or count). This creates confusing behavior where calling through a parent reference invokes the parent method, but calling through a child reference invokes the child method.

WHY SHADOWING IS DANGEROUS
Shadowing violates the substitution principle. If child.process() does something completely different from parent.process(), code expecting parent behavior through a parent-typed reference will behave differently from code using a child-typed reference. This is a major source of bugs.

E05120: SHADOWING DETECTION
EK9 detects shadowing and raises E05120. If a child method has the same name as a parent method but a different signature, it's not a valid override (signatures don't match) and it's not a new method (name collision). The compiler rejects it.

WHAT TO DO INSTEAD
If replacing parent behavior: use 'override' with the EXACT same signature. If creating a new method: rename it to avoid collision with the parent method name. If you need both behaviors: keep both methods but give them different, descriptive names.

OVERRIDE VS SHADOW
A proper override uses the 'override' keyword with an identical signature. A shadow accidentally matches the name without matching the signature. EK9 requires explicit intent via 'override', preventing accidental shadowing.

See Q570 for override basics. See Q571 for missing override keyword. See Q572 for false override claims.

defines module qa.typehierarchy.methodshadow

  defines class

    Formatter as open
      format() as pure
        -> text as String
        <- rtn as String: text

      describe() as pure
        <- rtn as String: "base formatter"

      default operator ?

    UpperFormatter extends Formatter
      //CORRECT: override with exact same signature
      override format() as pure
        -> text as String
        <- rtn as String: text.upperCase()

      override describe() as pure
        <- rtn as String: "upper formatter"

      //CORRECT: new method with a different name (no collision)
      formatWithPrefix() as pure
        -> text as String
        <- rtn as String: ">> " + text.upperCase()

      default operator ?

  defines function

    testProperOverride()
      base <- Formatter()
      upper <- UpperFormatter()

      require base.format("hello") == "hello"
      require upper.format("hello") == "HELLO"
      require upper.formatWithPrefix("hello") == ">> HELLO"

      //Polymorphic: calling through parent type still dispatches correctly
      formatters <- List() of Formatter
      formatters += Formatter()
      formatters += UpperFormatter()

      for formatter in formatters
        result <- formatter.format("test")
        require result?

Java: shadowing is allowed (with @Override catching some cases, but not all). C++: hiding is allowed by default, 'override' keyword optional. C#: 'new' keyword can explicitly shadow (EK9 bans this entirely). Python: no override concept, last definition wins. Kotlin: 'override' required, similar to EK9. Swift: 'override' required, similar to EK9. EK9: shadowing is a compile error (E05120), override keyword required for intentional replacement.

E05120 abstract child circular collision function hierarchy inherit name open override parent shadow shadowing signature substitution type virtual

Explanation: When the child UpperFormatter has a method matching parent Formatter.format, the override keyword is required. Without it, the compiler detects method shadowing. See ek9 -h E05120 for details.

Explanation: Adding override to formatWithPrefix would be a false claim since Formatter has no method named formatWithPrefix. The compiler rejects override when no parent method matches. See ek9 -h E05110 for details.


Question: Must a class with 'allow only' be declared as open?

Why does 'allow only' require 'as open'?

What happens if a sealed class is not open?

Answer:
Yes. A class that uses 'allow only' to restrict its permitted subclasses MUST be declared 'as open'. If a class has 'allow only' but is not 'as open', the compiler raises E05270.

WHY OPEN IS REQUIRED
The 'allow only' clause specifies which subclasses may extend this class. But if the class is not 'as open', no class can extend it at all. Having 'allow only' on a closed class is contradictory: you're listing permitted subclasses for a class that permits no subclasses.

CORRECT PATTERN
Always combine 'allow only' with 'as open':
  Shape allow only Circle, Square as open
    area() as abstract
This declares Shape as extensible but only by Circle and Square.

TRAITS ARE DIFFERENT
Traits are inherently open (they must be implementable). The E05270 check applies specifically to classes, which are closed by default in EK9.

ABSTRACT CLASSES
Abstract classes are implicitly open (they must be extensible to be useful). So 'abstract' classes with 'allow only' don't need an explicit 'as open' modifier.

See Q298 for allow only basics. See Q300 for abstract exemption in allow only lists. See Q301 for sealed class basics. See Q608 for sealed classes vs sealed traits. See Q101 for closed-by-default philosophy.

defines module qa.typehierarchy.sealedopen

  defines class

    //CORRECT: 'allow only' combined with 'as open'
    Shape allow only Circle, Square as open
      name()
        <- rtn as String: "shape"

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

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

  defines class

    //CORRECT: abstract class with 'allow only' (abstract is implicitly open)
    Event allow only ClickEvent, KeyEvent as abstract
      eventType() as abstract
        <- rtn as String?
      default operator ?

    ClickEvent extends Event
      override eventType()
        <- rtn as String: "click"
      default operator ?

    KeyEvent extends Event
      override eventType()
        <- rtn as String: "key"
      default operator ?

  defines function

    testSealedOpenClasses()
      circle <- Circle()
      square <- Square()
      require circle.name() == "circle"
      require square.name() == "square"

    testSealedAbstractClasses()
      click <- ClickEvent()
      key <- KeyEvent()
      require click.eventType() == "click"
      require key.eventType() == "key"

Java: sealed classes are implicitly extensible by permitted types (no separate 'open' keyword needed). Kotlin: sealed classes are abstract by default. C#: no equivalent. Rust: no class sealing. EK9: sealed classes with 'allow only' must explicitly be 'as open' (E05270), because classes are closed by default.

E05270 allow circular class closed define exhaustive extend hierarchy only open sealed subclass type

Explanation: A class with allow only must be declared as open. Without as open, the class is closed by default and no subclasses can extend it, making the allow only list contradictory. See ek9 -h E05270 for details.

Explanation: When overriding the name method from the sealed parent Shape, the override keyword is mandatory. Omitting it triggers shadowing detection. See ek9 -h E05120 for details.


Question: How do sealed classes differ from sealed traits in EK9?

Should I use a sealed class or a sealed trait?

What is the difference between allow only on classes and traits?

Answer:
Both classes and traits support 'allow only' to create sealed hierarchies, but they differ in requirements and use cases.

SEALED CLASSES
Sealed classes require 'as open' (or 'as abstract') because classes are closed by default. Sealed classes use single inheritance: each permitted subclass extends exactly one sealed parent. This creates a clean, linear hierarchy well-suited for data types.

SEALED TRAITS
Traits are inherently open (they must be implementable), so they don't need 'as open'. Sealed traits allow multiple implementation: a class can implement multiple sealed traits. This creates richer type relationships suited for behavioral contracts.

E05240 AND E05270
Both sealed classes and traits enforce E05240 (unpermitted concrete type tries to implement/extend). Sealed classes additionally enforce E05270 if they forget 'as open'. Traits never get E05270 because traits are always implementable.

WHEN TO USE EACH
Use sealed classes when you want a fixed set of data variants (like Result, Optional). Use sealed traits when you want a fixed set of behavioral capabilities that classes can mix.

DISPATCHER EXHAUSTIVENESS
Both sealed classes and sealed traits trigger exhaustive dispatch checking (E05260). A dispatcher over either type must handle all permitted types.

See Q298 for sealed traits. See Q301 for sealed classes. See Q607 for E05270. See Q302 for dispatchers with sealed classes.

defines module qa.typehierarchy.sealedcomparison

  defines trait

    //Sealed trait: no 'as open' needed (traits are inherently open)
    Printable allow only Document, Spreadsheet, Slide
      render() as abstract
        <- rtn as String?

  defines class

    //Sealed class: requires 'as open' explicitly
    Vehicle allow only Car, Truck as open
      describe()
        <- rtn as String: "vehicle"

    Car extends Vehicle
      override describe()
        <- rtn as String: "car"

    Truck extends Vehicle
      override describe()
        <- rtn as String: "truck"

    //Classes implementing the sealed trait
    Document with trait of Printable
      override render()
        <- rtn as String: "document"

    Spreadsheet with trait of Printable
      override render()
        <- rtn as String: "spreadsheet"

    Slide with trait of Printable
      override render()
        <- rtn as String: "slide"

  defines function

    testSealedClass()
      car <- Car()
      truck <- Truck()
      require car.describe() == "car"
      require truck.describe() == "truck"

    testSealedTrait()
      doc <- Document()
      sheet <- Spreadsheet()
      slide <- Slide()
      require doc.render() == "document"
      require sheet.render() == "spreadsheet"
      require slide.render() == "slide"

Java: sealed interfaces and sealed classes have similar distinction (Java 17+). Kotlin: sealed classes and sealed interfaces with permits. Scala: sealed traits and sealed abstract classes. Rust: enums (similar to sealed classes), no sealed traits. EK9: sealed classes require 'as open', sealed traits inherently open, both support exhaustive dispatch.

E05240 E05270 allow circular class closed difference exhaustive hierarchy inherit multiple only sealed single trait type

Explanation: Sealed classes require as open because classes are closed by default. Without it, the allow only list is contradictory since no class can extend a closed type. See ek9 -h E05270 for details.

Explanation: When overriding the describe method from the sealed parent Vehicle, the override keyword is mandatory. Omitting it triggers shadowing detection. See ek9 -h E05120 for details.


Question: Can sealed enforcement work across module boundaries?

Does allow only work across EK9 modules?

How does EK9 enforce sealed types in different modules?

Are sealed constraints checked across packages?

Answer:
Yes, sealed enforcement (E05240) works across module boundaries. The 'allow only' list names specific concrete types, and the compiler checks every concrete class that attempts to extend or implement the sealed type, regardless of which module it is in.

TRANSITIVE CHECKING
The check is transitive. If Module A defines a sealed trait and Module B defines a class implementing it, the compiler still verifies that Module B's class is in the 'allow only' list. If not, E05240 fires.

WHY CROSS-MODULE WORKS
EK9 compiles all referenced modules together. The full type hierarchy is available during TYPE_HIERARCHY_CHECKS, so the compiler has complete visibility across module boundaries.

LIMITATIONS
The permitted types must be resolvable from the sealed type's module. This means they must be in the same module or in a module that is a dependency. You cannot list a type from a module that the sealed type's module doesn't know about.

ABSTRACT INTERMEDIARIES
Abstract classes can implement a sealed trait from any module without being in the 'allow only' list. Only concrete subclasses need to be listed.

See Q298 for sealed trait basics. See Q300 for abstract exemptions. See Q607 for sealed class requirements. See Q6 for module organization.

defines module qa.typehierarchy.crossmodule

  defines trait

    //Sealed trait with all permitted types defined in same module
    //In practice, permitted types could be in different modules
    Serializable allow only JsonFormat, XmlFormat, CsvFormat
      serialize() as abstract
        <- rtn as String?

  defines class

    JsonFormat with trait of Serializable
      override serialize()
        <- rtn as String: "json"

    XmlFormat with trait of Serializable
      override serialize()
        <- rtn as String: "xml"

    CsvFormat with trait of Serializable
      override serialize()
        <- rtn as String: "csv"

  defines function

    testSealedCrossModule()
      formats <- List() of Serializable
      formats += JsonFormat()
      formats += XmlFormat()
      formats += CsvFormat()

      for fmt in formats
        result <- fmt.serialize()
        require result?

Java: sealed interfaces work across packages but within the same module. Kotlin: sealed hierarchy restricted to same package/module. Rust: orphan rule restricts trait implementation to same crate. EK9: sealed enforcement works across modules with full transitive checking.

E05240 boundary circular closed cross enforcement exhaustive hierarchy inherit module package sealed transitive type visibility

Explanation: When implementing the abstract serialize method from the sealed Serializable trait, the override keyword is required. Omitting it triggers shadowing detection. See ek9 -h E05120 for details.


Question: How do I refactor circular dependencies using composition?

How to fix circular hierarchy errors in EK9?

What is the composition pattern for breaking cycles?

How do I restructure code that has circular inheritance?

Answer:
When you encounter E05020 (circular hierarchy), the root cause is usually two types that each depend on the other's features. The solution is composition: instead of A extending B and B extending A, extract the shared behavior and have both types delegate to it.

EXTRACT SHARED BEHAVIOR
Identify what A needs from B and what B needs from A. Create a separate type (trait or class) for the shared behavior. Both A and B depend on the extracted type instead of on each other.

USE TRAITS FOR CONTRACTS
If A and B need to call methods on each other, define traits that describe the required interface. Each class implements the trait the other needs, breaking the direct dependency.

DELEGATE INSTEAD OF INHERIT
Instead of inheriting methods, hold a reference to the other object and call methods through delegation. This is the classic 'composition over inheritance' principle.

PRACTICAL EXAMPLE
If Engine needs Car features and Car needs Engine features, extract a Powertrain trait. Engine implements its contract, Car implements its contract, and they communicate through the trait interface rather than inheritance.

See Q602 for circular hierarchy basics. See Q109 for composition patterns. See Q212 for composition over inheritance. See Q210 for trait delegation.

defines module qa.typehierarchy.composition

  defines trait

    //Shared behavior extracted into traits
    Reportable
      report() as abstract
        <- rtn as String?

    Trackable
      tracking() as abstract
        <- rtn as String?

  defines class

    //Instead of Order extending Shipment extending Order (circular),
    //both implement shared traits and use composition

    OrderItem with trait of Reportable
      itemName <- "item"
      shipmentRef <- String()

      OrderItem()
        ->
          itemName as String
          shipmentRef as String
        this.itemName: itemName
        this.shipmentRef: shipmentRef

      override report()
        <- rtn as String: `Order: ${itemName}`

      shipmentReference() as pure
        <- rtn as String: shipmentRef

      default operator ?

    Shipment with trait of Trackable
      shipmentId <- "shipment"
      orderRef <- String()

      Shipment()
        ->
          shipmentId as String
          orderRef as String
        this.shipmentId: shipmentId
        this.orderRef: orderRef

      override tracking()
        <- rtn as String: `Shipment: ${shipmentId}`

      orderReference() as pure
        <- rtn as String: orderRef

      default operator ?

  defines function

    testCompositionOverCycles()
      //OrderItem and Shipment reference each other by ID, not by inheritance
      item <- OrderItem("Widget", "SHIP-001")
      shipment <- Shipment("SHIP-001", "ORD-100")

      require item.report() == "Order: Widget"
      require shipment.tracking() == "Shipment: SHIP-001"
      require item.shipmentReference() == "SHIP-001"
      require shipment.orderReference() == "ORD-100"

Java: same refactoring pattern applies (extract interface, use composition). Python: mixins or composition to break cycles. Rust: traits for shared behavior, no inheritance. Go: interfaces and embedding for composition. Kotlin: delegation pattern with 'by' keyword. EK9: use traits and composition to replace circular inheritance.

E05020 circular composition delegate dependency extract hierarchy inheritance refactor trait type

Explanation: If OrderItem extended Shipment and Shipment extended OrderItem, a circular hierarchy would be created. Using composition with traits avoids this cycle. See ek9 -h E05030 for details.

Explanation: When implementing the abstract report method from the Reportable trait, the override keyword is required. Omitting it triggers shadowing detection. See ek9 -h E05120 for details.


Question: What is the full set of type hierarchy checks EK9 performs?

What are all the E05xxx hierarchy error codes?

Answer:
EK9 performs comprehensive type hierarchy validation during compilation, catching structural errors that would cause runtime failures in less strict languages.

CIRCULAR HIERARCHIES (E05020)
Detects cycles in inheritance chains at any depth. A class that directly or indirectly extends itself is rejected. Applies to classes, traits, records, and components.

CLOSED TYPE EXTENSION (E05030)
EK9 types are closed by default. Extending a type that is not 'as open' or 'as abstract' produces E05030.

THIS IN WRONG CONTEXT (E05070)
Using 'this' in a standalone function or other context without an instance is rejected with E05070.

OVERRIDE VALIDATION (E05100-E05120)
E05100: claiming override when nothing to override exists. E05110: matching a parent method without 'override' keyword. E05120: method name matches parent but signature differs (shadowing).

PURITY HIERARCHY (E05150-E05160)
E05150: overriding a pure method without marking the override as pure. E05160: super call from a pure method to a non-pure parent.

SEALED TYPE ENFORCEMENT (E05240-E05270)
E05240: concrete type not in 'allow only' list. E05250: abstract type in 'allow only' list. E05260: dispatcher missing handler for permitted type. E05270: sealed class not declared 'as open'.

See Q602 for circular hierarchies. See Q605 for 'this' context. See Q606 for method shadowing. See Q607 for sealed class requirements. See Q570 for override mechanics.
See Q679 for sealed allow-only. See Q680 for diamond trait resolution. See Q697 for inheritance depth limit.
See Q729 for inheritance depth boundary example.

defines module qa.typehierarchy.summary

  defines trait

    //Trait hierarchy: clean and valid
    Loggable
      logEntry() as abstract
        <- rtn as String?

  defines class

    //Open class: can be extended
    BaseEntity as open
      entityId()
        <- rtn as String: "entity"
      default operator ?

    //Extends open class: valid
    UserEntity extends BaseEntity with trait of Loggable
      override entityId()
        <- rtn as String: "user"
      override logEntry()
        <- rtn as String: "User entity"
      default operator ?

    //Sealed class: open + allow only
    Status allow only Active, Inactive as open
      label()
        <- rtn as String: "status"

    Active extends Status
      override label()
        <- rtn as String: "active"

    Inactive extends Status
      override label()
        <- rtn as String: "inactive"

  defines function

    testHierarchySummary()
      user <- UserEntity()
      require user.entityId() == "user"
      require user.logEntry() == "User entity"

      active <- Active()
      inactive <- Inactive()
      require active.label() == "active"
      require inactive.label() == "inactive"

Java: some checks at compile time (circular, override), others at runtime. Python: MRO failures at class creation time. C++: compile-time hierarchy checks but no sealed types. Kotlin: comprehensive compile-time checks similar to EK9. Rust: no inheritance, trait coherence rules instead. EK9: all hierarchy checks at compile time, no runtime surprises.

Explanation: A sealed class with allow only must be declared as open. Without as open, the class is closed by default and no subclasses can extend it. See ek9 -h E05270 for details.

Explanation: When overriding entityId from the parent BaseEntity, the override keyword is mandatory. Without it, the compiler detects method shadowing. See ek9 -h E05120 for details.

require user.entityId().toUpperCase() == "user"


Question: What happens when a dispatcher entry and handler have different purity?

Must dispatcher handlers match purity of the entry method?

Can a pure dispatcher have non-pure handlers?

Answer:
All dispatcher handlers must match the purity of the dispatcher entry method. If the entry is 'as pure', every handler must also be 'as pure'. If the entry is non-pure, handlers must be non-pure. A mismatch raises E05170.

WHY PURITY MUST MATCH
Callers of the dispatcher expect consistent behavior. If a dispatcher is declared pure, the caller relies on that guarantee for all possible dispatch targets. A non-pure handler would violate that contract at runtime, depending on which type is passed.

PURE DISPATCHER PATTERN
Mark both the entry and all handlers as pure:
  describe() as pure dispatcher
    -> item as Item
    <- rtn as String: $item
  describe() as pure
    -> item as Book
    <- rtn as String: "Book: " + item.title()

NON-PURE DISPATCHER PATTERN
Omit 'as pure' from both entry and handlers:
  process() as dispatcher
    -> item as Item
  process()
    -> item as Book
    stdout.println(item.title())

MIXING IS FORBIDDEN
You cannot have a pure entry with a non-pure handler, or vice versa. The compiler checks every handler against the entry's purity.

See Q560 for purity basics. See Q566 for pure method restrictions. See Q60 for dispatcher fundamentals. See Q620 for dispatcher hierarchy rules.

defines module qa.dispatchervalidation.purity

  defines class

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

    Book extends Item
      title <- "untitled"

      Book()
        -> title as String
        this.title: title

      override name() as pure
        <- rtn as String: title

      default operator ?

    Magazine extends Item
      issue <- "unknown"

      Magazine()
        -> issue as String
        this.issue: issue

      override name() as pure
        <- rtn as String: issue

      default operator ?

    //CORRECT: pure dispatcher with all pure handlers
    PureCatalog
      describe() as pure dispatcher
        -> item as Item
        <- rtn as String: item.name()

      describe() as pure
        -> item as Book
        <- rtn as String: "Book: " + item.name()

      describe() as pure
        -> item as Magazine
        <- rtn as String: "Magazine: " + item.name()

  defines function

    testPureDispatcher()
      catalog <- PureCatalog()
      book <- Book("EK9 Guide")
      mag <- Magazine("Tech Monthly")

      bookResult <- catalog.describe(book)
      magResult <- catalog.describe(mag)

      require bookResult == "Book: EK9 Guide"
      require magResult == "Magazine: Tech Monthly"

Java: no purity concept, no dispatch purity checking. Kotlin: no purity enforcement on dispatchers. Haskell: all functions pure by default, dispatch via type classes. Rust: trait methods can be effectful or pure, but no dispatch purity contract. EK9: E05170 enforces consistent purity across all dispatcher handlers.

E05170 ambiguity consistent contract dispatch dispatcher entry handler immutable match pure purity side-effect validate

describe() as pure
        -> item as Book

Explanation: The dispatcher entry describe is declared as pure. Every handler must also be pure. Omitting as pure on a handler creates a purity mismatch. See ek9 -h E05170 for details.

describe() as pure
        -> item as Magazine

describe() as pure
        -> vehicle as Vehicle

Explanation: Each handler parameter type must be a subtype of the dispatcher entry parameter type Item. A handler for an unrelated type like Vehicle is outside the hierarchy. See ek9 -h E50010 for details.


Question: Can a derived class dispatcher target a private handler in the parent?

Why can't a dispatcher call private methods from a parent?

What access level do dispatcher handlers need?

Answer:
No. A dispatcher cannot target a private method from a parent class. Private methods are not visible to child classes, so dispatch to them would fail. The compiler catches this with E05180.

PRIVATE METHODS ARE INVISIBLE
When a child class defines a dispatcher, the dispatch targets must be accessible. Private methods in the parent are not inherited by children and cannot be dispatched to.

CORRECT ACCESS LEVELS
Dispatcher handlers should be protected (visible to children) or public (visible to all). If the handler is in the same class as the dispatcher, private is fine because the dispatcher and handler are in the same scope.

SAME-CLASS DISPATCHERS
When both dispatcher entry and handlers are in the same class, private handlers are perfectly valid. The E05180 check only fires when the dispatcher in a child class tries to reach a private method in a parent.

WHAT TO DO
If a parent method needs to be a dispatch target from child classes, change it from private to protected. If the method must remain private, move the dispatcher into the parent class.

See Q60 for dispatcher fundamentals. See Q95 for field visibility. See Q573 for access modifier rules on overrides. See Q614 for dispatcher type hierarchy validation.

defines module qa.dispatchervalidation.access

  defines class

    Message as abstract
      content() as pure abstract
        <- rtn as String?
      default operator ?

    TextMessage extends Message
      body <- "text"

      TextMessage()
        -> body as String
        this.body: body

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

      default operator ?

    AlertMessage extends Message
      alert <- "alert"

      AlertMessage()
        -> alert as String
        this.alert: alert

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

      default operator ?

    //CORRECT: Dispatcher and all handlers in same class
    MessageProcessor

      process() as dispatcher
        -> msg as Message
        <- rtn as String: msg.content()

      process()
        -> msg as TextMessage
        <- rtn as String: "Text: " + msg.content()

      process()
        -> msg as AlertMessage
        <- rtn as String: "Alert: " + msg.content()

  defines function

    testSameClassDispatcher()
      processor <- MessageProcessor()

      textMsg <- TextMessage("hello")
      alertMsg <- AlertMessage("warning")

      require processor.process(textMsg) == "Text: hello"
      require processor.process(alertMsg) == "Alert: warning"

Java: method overloading resolution doesn't access private methods from child. C++: virtual dispatch doesn't apply to private methods. Python: no private methods (name mangling is convention). Kotlin: private methods invisible to subclasses. EK9: E05180 prevents dispatcher from targeting private parent methods.

E05180 access ambiguity child class dispatch dispatcher handler parent private protected validate visible

Explanation: The dispatcher entry parameter is Message. Every handler parameter must be a subtype of Message. String is not in the Message hierarchy. See ek9 -h E50060 for details.

process()
        -> msg as AlertMessage
        <- rtn as Integer: 42

Explanation: All dispatcher handlers must return the same type as the entry method. The entry returns String, so a handler returning Integer is incompatible. See ek9 -h E05220 for details.

      process() as dispatcher
        -> msg as TextMessage
        <- rtn as String: "Text: " + msg.content()

Explanation: Only one method with a given name can be marked 'as dispatcher'. The entry point process() is already marked as dispatcher. Marking a handler method as dispatcher too triggers E07820. See ek9 -h E07820 for details.


Question: What happens if a dispatcher handler's parameter type is outside the hierarchy?

Why must dispatcher handler types be in the same hierarchy?

Can a dispatcher handle unrelated types?

Answer:
Every dispatcher handler's parameter type must be a subtype of the dispatcher entry's parameter type. If a handler specifies a type that is not in the hierarchy, the compiler raises E05210.

WHY TYPE HIERARCHY MATTERS
Dispatchers route calls based on runtime type. The entry method declares a base type (e.g., Animal). Each handler declares a specific subtype (e.g., Dog, Cat). At runtime, the dispatcher checks the actual type and routes to the matching handler. If a handler type is not in the hierarchy, it can never match.

EXAMPLE OF THE ERROR
If the dispatcher entry takes Animal and a handler takes Vehicle, no Animal can ever be a Vehicle. The handler is unreachable and indicates a design error.

CORRECT PATTERN
All handlers must use subtypes of the entry's parameter type:
  handle() as dispatcher
    -> a as Animal
  handle()
    -> a as Dog        //OK: Dog extends Animal
  handle()
    -> a as Cat         //OK: Cat extends Animal

SEPARATE DISPATCHERS FOR SEPARATE HIERARCHIES
If you need to dispatch on multiple unrelated type hierarchies, create separate dispatchers. Each dispatcher handles one hierarchy.

See Q60 for dispatcher fundamentals. See Q615 for return type matching. See Q619 for two-parameter dispatchers.

defines module qa.dispatchervalidation.hierarchy

  defines class

    Animal as abstract
      speak() as pure abstract
        <- rtn as String?
      default operator ?

    Dog extends Animal
      override speak() as pure
        <- rtn as String: "woof"
      default operator ?

    Cat extends Animal
      override speak() as pure
        <- rtn as String: "meow"
      default operator ?

    Bird extends Animal
      override speak() as pure
        <- rtn as String: "tweet"
      default operator ?

    //CORRECT: all handler types are in the Animal hierarchy
    AnimalHandler
      handle() as dispatcher
        -> animal as Animal
        <- rtn as String: "Animal: " + animal.speak()

      handle()
        -> animal as Dog
        <- rtn as String: "Dog says: " + animal.speak()

      handle()
        -> animal as Cat
        <- rtn as String: "Cat says: " + animal.speak()

      handle()
        -> animal as Bird
        <- rtn as String: "Bird says: " + animal.speak()

  defines function

    testHierarchyDispatch()
      handler <- AnimalHandler()

      dog <- Dog()
      cat <- Cat()
      bird <- Bird()

      require handler.handle(dog) == "Dog says: woof"
      require handler.handle(cat) == "Cat says: meow"
      require handler.handle(bird) == "Bird says: tweet"

Java: method overloading doesn't check hierarchy, resolved at compile time. Kotlin: no runtime dispatcher, uses when + is checks. Rust: match on enum variants (closed hierarchy). Go: type switch operates on interfaces. EK9: E05210 enforces that all dispatcher handler parameter types are within the dispatch hierarchy.

E05210 ambiguity dispatch dispatcher extends handler hierarchy parameter subtype type unrelated validate

Explanation: The dispatcher entry takes Animal. A handler parameter type must be a subtype of Animal. Vehicle is completely unrelated and would never match at runtime. See ek9 -h E50010 for details.

handle()
        -> animal as Cat
        <- rtn as Integer: 0

Explanation: The dispatcher entry returns String. All handlers must return the same type. A handler returning Integer would create an inconsistent return type. See ek9 -h E05220 for details.


Question: How does EK9 check dispatcher return type compatibility?

Must all dispatcher handlers return the same type?

Can dispatcher handlers have different return types?

Answer:
All dispatcher handlers must return the same type as the dispatcher entry method (or a covariant subtype). If a handler returns a different, incompatible type, the compiler raises E05220.

WHY RETURN TYPES MUST MATCH
Callers invoke the dispatcher without knowing which handler will execute. The return type must be consistent so the caller can use the result regardless of which handler was selected at runtime.

EXACT MATCH
The simplest approach is to use the same return type for all handlers:
  describe() as dispatcher
    -> shape as Shape
    <- rtn as String: "shape"
  describe()
    -> shape as Circle
    <- rtn as String: "circle"

VOID DISPATCHERS
Dispatchers with no return type (void) require all handlers to also have no return type.

WHAT TO DO
If all handlers should return the same type: ensure exact match. If you need handler-specific return types: consider restructuring to return a common base type or using a wrapper.

See Q60 for dispatcher fundamentals. See Q614 for parameter type validation. See Q616 for dispatcher ambiguity.

defines module qa.dispatchervalidation.returntypes

  defines class

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

    Circle extends Shape
      override name() as pure
        <- rtn as String: "circle"
      default operator ?

    Rectangle extends Shape
      override name() as pure
        <- rtn as String: "rectangle"
      default operator ?

    //CORRECT: All handlers return String (matching dispatcher entry)
    ShapeDescriber
      describe() as dispatcher
        -> shape as Shape
        <- rtn as String: "Shape: " + shape.name()

      describe()
        -> shape as Circle
        <- rtn as String: "Circle with round edges"

      describe()
        -> shape as Rectangle
        <- rtn as String: "Rectangle with four sides"

    //CORRECT: Void dispatcher (no return type for any handler)
    ShapePrinter
      print() as dispatcher
        -> shape as Shape
        content <- "Printing: " + shape.name()
        require content?

      print()
        -> shape as Circle
        content <- "Printing circle"
        require content?

      print()
        -> shape as Rectangle
        content <- "Printing rectangle"
        require content?

  defines function

    testReturnTypeMatch()
      describer <- ShapeDescriber()
      circle <- Circle()
      rect <- Rectangle()

      require describer.describe(circle) == "Circle with round edges"
      require describer.describe(rect) == "Rectangle with four sides"

    testVoidDispatcher()
      printer <- ShapePrinter()
      printer.print(Circle())
      printer.print(Rectangle())

Java: method overloading requires exact return type match (not covariant). Kotlin: when expressions can return a common type. Rust: match arms must return same type. Scala: pattern matching returns common supertype. EK9: E05220 enforces return type compatibility across all dispatcher handlers.

E05220 ambiguity compatible consistent covariant dispatch dispatcher handler match return type validate

describe()
        -> shape as Circle
        <- rtn as Integer: 42

Explanation: The dispatcher entry returns String. A handler returning Integer is incompatible. All handlers must return the same type as the entry method. See ek9 -h E05220 for details.

describe()
        -> shape as Rectangle

Explanation: The dispatcher entry dispatches on Shape. A handler for an unrelated type Animal is outside the Shape hierarchy and can never match. See ek9 -h E50010 for details.


Question: What is dispatcher ambiguity and how is it detected?

When does a dispatcher become ambiguous?

How does EK9 resolve dispatcher cost ties?

Answer:
Dispatcher ambiguity (E05230) occurs when two or more handlers match an argument type at equal cost. EK9 uses a cost-based dispatch model and refuses to guess when costs are tied.

COST MODEL
EK9 assigns dispatch costs based on type distance: exact type match costs 0.00, each class inheritance step costs 0.05, and each trait implementation step costs 0.10. The handler with the lowest cost wins.

WHEN AMBIGUITY OCCURS
Ambiguity arises when a class implements two traits and both traits have handlers at the same cost. Since both trait implementations are at equal distance, the dispatcher cannot choose.

EXAMPLE
If Duck implements both Flyable and Swimmable, and the dispatcher has handlers for both traits at the same depth, dispatching a Duck is ambiguous (both handlers cost 0.10).

RESOLUTION STRATEGIES
Strategy 1 - Explicit handler: add a handler for the ambiguous concrete type (exact match at cost 0.00 wins). Strategy 2 - Bridge trait: create a combining trait that encompasses both competing traits, giving it a shorter path. Strategy 3 - Decomposed dispatchers: split into separate dispatchers, each handling one concern.

EK9 REFUSES TO GUESS
Unlike languages that pick 'first declared' or 'most recently added', EK9 treats ambiguity as a design error. The compiler requires structural resolution.

See Q617 for diamond trait ambiguity. See Q60 for dispatcher fundamentals. See Q255 for cost-based method resolution.

defines module qa.dispatchervalidation.ambiguity

  defines class

    //CORRECT: No ambiguity because only one trait hierarchy is dispatched
    Vehicle as abstract
      kind() as pure abstract
        <- rtn as String?
      default operator ?

    Car extends Vehicle
      override kind() as pure
        <- rtn as String: "car"
      default operator ?

    Truck extends Vehicle
      override kind() as pure
        <- rtn as String: "truck"
      default operator ?

    Motorcycle extends Vehicle
      override kind() as pure
        <- rtn as String: "motorcycle"
      default operator ?

    //Clear hierarchy: Car, Truck, Motorcycle all extend Vehicle
    //No ambiguity because single-parent inheritance has clear costs
    VehicleProcessor
      process() as dispatcher
        -> vehicle as Vehicle
        <- rtn as String: "Vehicle: " + vehicle.kind()

      process()
        -> vehicle as Car
        <- rtn as String: "Car: " + vehicle.kind()

      process()
        -> vehicle as Truck
        <- rtn as String: "Truck: " + vehicle.kind()

      process()
        -> vehicle as Motorcycle
        <- rtn as String: "Motorcycle: " + vehicle.kind()

  defines function

    testUnambiguousDispatch()
      processor <- VehicleProcessor()

      car <- Car()
      truck <- Truck()
      moto <- Motorcycle()

      require processor.process(car) == "Car: car"
      require processor.process(truck) == "Truck: truck"
      require processor.process(moto) == "Motorcycle: motorcycle"

Java: method overloading resolved at compile time, no runtime ambiguity concept. C++: multiple inheritance ambiguity resolved with virtual base classes. Python: MRO (C3 linearization) provides deterministic resolution. Kotlin: compiler error for ambiguous overloads. Julia: multiple dispatch with ambiguity detection. EK9: E05230 detects runtime dispatch ambiguity at compile time, requires structural resolution.

E05230 ambiguity bridge cost dispatch dispatcher explicit resolution tie trait validate

require processor.process(car).toUpperCase() == "Car: car"

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

Explanation: The dispatcher entry takes Vehicle. A handler for Shape is outside the Vehicle hierarchy and can never be reached through dispatch. See ek9 -h E50010 for details.


Question: How can trait diamond patterns cause dispatcher ambiguity?

What is the diamond problem in EK9 dispatchers?

How do multiple trait implementations affect dispatch?

Why does implementing two traits cause E05230?

Answer:
When a class implements two traits and a dispatcher has handlers for both traits, the dispatch cost to both handlers is identical (0.10 each). This creates ambiguity (E05230) because the compiler cannot determine which handler should win.

THE DIAMOND PROBLEM
Consider: Duck implements both Flyable and Swimmable. A dispatcher has handlers for Flyable (cost 0.10) and Swimmable (cost 0.10). When dispatching a Duck, both handlers are equally good matches. This is the diamond problem applied to dispatch.

BRIDGE TRAIT SOLUTION
Create a combining trait that encompasses both competing traits:
  AmphibiousFlyer with trait of Flyable, Swimmable
Then Duck implements ONLY AmphibiousFlyer (not Flyable and Swimmable directly). The dispatcher handler for AmphibiousFlyer has cost 0.10, while Flyable and Swimmable handlers have cost 0.20 (two hops through the bridge). The bridge handler wins.

CRITICAL: the concrete type must implement ONLY the bridge trait, not the individual traits directly. If Duck implements all three, all handlers are at cost 0.10 and ambiguity remains.

EXPLICIT HANDLER SOLUTION
Add a handler for the specific ambiguous type (e.g., Duck) at cost 0.00 (exact match), which beats any trait handler.

See Q616 for ambiguity basics. See Q60 for dispatcher fundamentals. See Q107 for multiple trait implementation.

defines module qa.dispatchervalidation.diamond

  defines trait

    //Bridge trait pattern to resolve diamond ambiguity
    Flyable
      canFly() as abstract
        <- rtn as Boolean?

    Swimmable
      canSwim() as abstract
        <- rtn as Boolean?

    //Bridge trait: combines both capabilities
    AmphibiousFlyer with trait of Flyable, Swimmable

  defines class

    //Duck implements ONLY the bridge trait (not Flyable/Swimmable directly)
    //This gives bridge handler cost 0.10, individual traits cost 0.20
    Duck with trait of AmphibiousFlyer
      override canFly()
        <- rtn as Boolean: true
      override canSwim()
        <- rtn as Boolean: true

    //Eagle implements only Flyable
    Eagle with trait of Flyable
      override canFly()
        <- rtn as Boolean: true

    //Fish implements only Swimmable
    Fish with trait of Swimmable
      override canSwim()
        <- rtn as Boolean: true

  defines function

    testBridgeTrait()
      duck <- Duck()
      eagle <- Eagle()
      fish <- Fish()

      require duck.canFly()
      require duck.canSwim()
      require eagle.canFly()
      require fish.canSwim()

Java: no runtime dispatch, diamond problem handled by interface default method rules. C++: virtual inheritance for diamond problem. Python: C3 linearization (MRO) provides deterministic resolution. Kotlin: explicit override resolution for diamond conflicts. Rust: no class inheritance, trait coherence rules. EK9: E05230 detects diamond dispatch ambiguity, resolved structurally via bridge traits or explicit handlers.

E05230 ambiguity bridge cost diamond dispatch handler implement multiple sealed trait validate visitor

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

Explanation: When implementing a trait abstract method, the override keyword is required. Omitting it on canFly triggers shadowing detection. See ek9 -h E05120 for details.


Question: How do sealed dispatchers enforce exhaustive handler coverage?

Must I handle all types in a sealed dispatcher?

How does EK9 enforce exhaustive dispatch on sealed types?

Answer:
When a dispatcher's parameter type is a sealed type (one with 'allow only'), the compiler requires handlers for ALL permitted concrete types. Missing a handler raises E05260.

EXHAUSTIVE CHECKING
The compiler cross-references the 'allow only' list with the set of dispatcher handlers. Every concrete type in the list must have a matching handler (either exact or through a supertype).

COMPILE-TIME SAFETY
When you add a new type to the 'allow only' list, EVERY dispatcher operating on that type fails to compile until a handler is added. This guarantees all dispatch points are updated when the type hierarchy grows.

UNSEALED DISPATCHERS
Dispatchers on unsealed types (without 'allow only') do NOT require exhaustive handlers. The entry method acts as a catch-all fallback for unhandled types.

ABSTRACT TYPES
Abstract types in the hierarchy do not need handlers because they cannot be instantiated at runtime. Only concrete types require handlers.

BOTH CLASSES AND TRAITS
Exhaustive checking works for both sealed classes (allow only on class) and sealed traits (allow only on trait). The mechanism is identical.

See Q299 for sealed trait dispatchers. See Q302 for sealed class dispatchers. See Q614 for handler type hierarchy. See Q607 for sealed class requirements.

defines module qa.dispatchervalidation.exhaustive

  defines trait

    //Sealed trait: requires exhaustive dispatch
    Command allow only CreateCommand, UpdateCommand, DeleteCommand
      execute() as abstract
        <- rtn as String?

  defines class

    CreateCommand with trait of Command
      override execute()
        <- rtn as String: "created"

    UpdateCommand with trait of Command
      override execute()
        <- rtn as String: "updated"

    DeleteCommand with trait of Command
      override execute()
        <- rtn as String: "deleted"

    //CORRECT: exhaustive dispatcher handles ALL permitted types
    CommandProcessor
      process() as dispatcher
        -> cmd as Command
        <- rtn as String: cmd.execute()

      process()
        -> cmd as CreateCommand
        <- rtn as String: "Processing create: " + cmd.execute()

      process()
        -> cmd as UpdateCommand
        <- rtn as String: "Processing update: " + cmd.execute()

      process()
        -> cmd as DeleteCommand
        <- rtn as String: "Processing delete: " + cmd.execute()

  defines function

    testExhaustiveDispatch()
      processor <- CommandProcessor()

      createCmd <- CreateCommand()
      updateCmd <- UpdateCommand()
      deleteCmd <- DeleteCommand()

      require processor.process(createCmd) == "Processing create: created"
      require processor.process(updateCmd) == "Processing update: updated"
      require processor.process(deleteCmd) == "Processing delete: deleted"

Java: exhaustive switch on sealed types (Java 21+). Kotlin: exhaustive when on sealed classes/interfaces. Rust: exhaustive match on enums. Scala: exhaustive match on sealed traits. TypeScript: exhaustive switch with never type. EK9: E05260 enforces exhaustive dispatch handler coverage on sealed types.

E05260 allow ambiguity closed complete coverage dispatch exhaustive handler only sealed validate visitor

require processor.process(createCmd).toUpperCase() == "Processing create: created"

process()
        -> cmd as CreateCommand

Explanation: The dispatcher entry takes Command. A handler for String is outside the Command hierarchy. All handler parameter types must be subtypes of the entry parameter type. See ek9 -h E50001 for details.

Explanation: The sealed trait Command allows only CreateCommand, UpdateCommand, and DeleteCommand. Removing the DeleteCommand handler leaves the dispatcher non-exhaustive. All permitted types must have handlers. See ek9 -h E05260 for details.


Question: What happens with two-parameter dispatchers and type validation?

Can EK9 dispatchers dispatch on multiple parameters?

How does multi-parameter dispatch work in EK9?

Does E05210 apply to each parameter in a dispatcher?

Answer:
EK9 supports true multiple dispatch: dispatchers can dispatch on two or more parameters simultaneously. The type hierarchy validation (E05210) applies to EACH dispatchable parameter independently.

MULTIPLE DISPATCH
A two-parameter dispatcher examines the runtime type of both arguments to select the most specific handler:
  intersect() as dispatcher
    -> s1 as Shape, s2 as Shape
  intersect()
    -> s1 as Circle, s2 as Circle
  intersect()
    -> s1 as Circle, s2 as Rectangle

TYPE CHECKING PER PARAMETER
Each handler parameter must be a subtype of the corresponding dispatcher entry parameter. If the first entry parameter is Shape, all handlers' first parameters must extend Shape. The same applies to the second parameter.

COST CALCULATION
The total dispatch cost is the sum of costs for each parameter. A handler with Circle (cost 0.05) + Rectangle (cost 0.05) has total cost 0.10. Exact matches on both parameters (cost 0.00 + 0.00) always win.

AMBIGUITY WITH MULTIPLE PARAMETERS
Ambiguity (E05230) can occur when two handlers have the same total cost. For example, handler(Circle, Rectangle) and handler(Rectangle, Circle) both cost 0.10 when called with a type that matches both.

TRUE MULTIPLE DISPATCH
This is true multiple dispatch, a feature only Julia and Common Lisp natively support among mainstream languages. EK9 provides it with compile-time validation.

See Q60 for dispatcher fundamentals. See Q614 for single-parameter hierarchy. See Q616 for ambiguity detection.

defines module qa.dispatchervalidation.twoparam

  defines class

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

    Circle extends Shape
      override name() as pure
        <- rtn as String: "circle"
      default operator ?

    Rectangle extends Shape
      override name() as pure
        <- rtn as String: "rectangle"
      default operator ?

    //CORRECT: Two-parameter dispatcher with all types in hierarchy
    CollisionDetector
      collides() as dispatcher
        ->
          s1 as Shape
          s2 as Shape
        <- rtn as String: `${s1.name()} vs ${s2.name()}`

      collides()
        ->
          s1 as Circle
          s2 as Circle
        <- rtn as String: "circle-circle collision"

      collides()
        ->
          s1 as Circle
          s2 as Rectangle
        <- rtn as String: "circle-rectangle collision"

      collides()
        ->
          s1 as Rectangle
          s2 as Rectangle
        <- rtn as String: "rectangle-rectangle collision"

  defines function

    testTwoParamDispatch()
      detector <- CollisionDetector()

      circle <- Circle()
      rect <- Rectangle()

      require detector.collides(circle, circle) == "circle-circle collision"
      require detector.collides(circle, rect) == "circle-rectangle collision"
      require detector.collides(rect, rect) == "rectangle-rectangle collision"

Java: no multiple dispatch (single dispatch via virtual methods). Python: functools.singledispatch (single parameter only). Julia: built-in multiple dispatch (most similar to EK9). Common Lisp: CLOS multi-methods. Rust: no dispatch mechanism. Kotlin: no multiple dispatch. EK9: true multiple dispatch with compile-time E05210 validation per parameter.

E05210 E05230 ambiguity cost dispatch handler multi multiple parameter sealed true two validate visitor

collides()
        ->
          s1 as Circle
          s2 as String

Explanation: Each parameter in a multi-parameter dispatcher must be a subtype of the corresponding entry parameter. The entry takes Shape for both, so String is invalid for either parameter. See ek9 -h E05210 for details.

collides()
        ->
          s1 as Circle
          s2 as Circle
        <- rtn as Integer: 0


Question: How does dispatcher resolution work across a class hierarchy?

What are the dispatch resolution rules in EK9?

How does EK9 choose which handler to call?

What is the cost model for dispatcher resolution?

Answer:
EK9 uses a cost-based model to resolve dispatchers. When a dispatcher is called, the compiler has already validated the hierarchy at compile time. At runtime, the most specific (lowest cost) handler is selected.

COST MODEL
Exact type match: cost 0.00. Each class inheritance step: cost 0.05 per level. Each trait implementation step: cost 0.10 per level. The handler with the lowest total cost wins.

FALLBACK TO ENTRY
If no specific handler matches, the dispatcher entry method executes. This is the catch-all fallback for types without dedicated handlers.

INHERITANCE DEPTH MATTERS
A handler for a direct parent (cost 0.05) beats a handler for a grandparent (cost 0.10). More specific handlers always win over less specific ones.

COMPILE-TIME VALIDATION
All type hierarchy checks (E05210), return type checks (E05220), purity checks (E05170), and access checks (E05180) happen at compile time. Runtime dispatch only selects the handler; correctness is guaranteed by the compiler.

TRAIT VS CLASS COST
Trait implementation costs more than class inheritance (0.10 vs 0.05). This reflects the structural reality that class inheritance is a stronger type relationship than trait implementation.

See Q60 for dispatcher fundamentals. See Q612 for purity matching. See Q614 for type hierarchy validation. See Q255 for cost-based method resolution.

defines module qa.dispatchervalidation.rules

  defines class

    //Three-level hierarchy to demonstrate cost-based resolution
    Entity as abstract
      kind() as pure abstract
        <- rtn as String?
      default operator ?

    LivingEntity extends Entity as open
      override kind() as pure
        <- rtn as String: "living"
      default operator ?

    Person extends LivingEntity
      override kind() as pure
        <- rtn as String: "person"
      default operator ?

    //Dispatcher with handlers at different hierarchy depths
    EntityProcessor
      process() as dispatcher
        -> entity as Entity
        <- rtn as String: "Generic: " + entity.kind()

      //Cost 0.05 from LivingEntity (one step from Entity)
      process()
        -> entity as LivingEntity
        <- rtn as String: "Living: " + entity.kind()

      //Cost 0.10 from Person (two steps from Entity)
      //But cost 0.00 for exact Person match
      process()
        -> entity as Person
        <- rtn as String: "Person: " + entity.kind()

  defines function

    testCostBasedResolution()
      processor <- EntityProcessor()

      person <- Person()
      //Person matches Person handler at cost 0.00 (exact match)
      require processor.process(person) == "Person: person"

Java: virtual dispatch via vtable (single dispatch, O(1)). C++: virtual function tables with dynamic_cast for type checking. Python: MRO-based method resolution. Kotlin: no runtime dispatch, compile-time overload resolution. Julia: multiple dispatch with method cache. EK9: cost-based multiple dispatch with compile-time hierarchy validation.

ambiguity class compile cost depth dispatch entry fallback handler hierarchy resolution sealed validate visitor

Explanation: The dispatcher entry takes Entity. All handler parameter types must be within the Entity hierarchy. String is unrelated and would never match. See ek9 -h E50060 for details.

require processor.process(person).toUpperCase() == "Person: person"


Question: What is the complete set of dispatcher compile-time checks?

How does EK9 validate dispatchers at compile time?

What are all the dispatcher-related E05xxx error codes?

Answer:
EK9 performs six distinct compile-time checks on dispatchers, catching errors that would be runtime failures in other languages.

PURITY MATCHING (E05170)
All handlers must match the dispatcher entry's purity. A pure dispatcher requires all handlers to be pure. A non-pure dispatcher requires all handlers to be non-pure.

ACCESS VALIDATION (E05180)
Dispatcher handlers must be accessible. A dispatcher in a child class cannot target a private method in the parent class.

TYPE HIERARCHY (E05210)
Every handler's parameter type must be a subtype of the entry's parameter type. Handlers for unrelated types are rejected.

RETURN TYPE (E05220)
All handlers must return the same type (or a covariant subtype) as the entry method.

AMBIGUITY DETECTION (E05230)
When two handlers match at equal cost (especially with multiple trait implementations), the compiler rejects the ambiguity rather than guessing.

EXHAUSTIVE COVERAGE (E05260)
Dispatchers on sealed types (with 'allow only') must have handlers for all permitted concrete types.

TOGETHER
These six checks guarantee that at runtime, every dispatch call will find exactly one valid handler with correct purity, access, type, and return type. No runtime dispatch errors are possible.

See Q612 for E05170. See Q613 for E05180. See Q614 for E05210. See Q615 for E05220. See Q616 for E05230. See Q618 for E05260.

defines module qa.dispatchervalidation.summary

  defines trait

    //Sealed trait for exhaustive dispatch demo
    Action allow only SaveAction, LoadAction
      perform() as pure abstract
        <- rtn as String?

  defines class

    SaveAction with trait of Action
      override perform() as pure
        <- rtn as String: "saved"

    LoadAction with trait of Action
      override perform() as pure
        <- rtn as String: "loaded"

    //Dispatcher demonstrating multiple checks passing
    ActionRunner
      //Pure dispatcher (E05170 check: all handlers pure)
      run() as pure dispatcher
        -> action as Action
        <- rtn as String: action.perform()

      //Handler type in hierarchy (E05210 check)
      //Return type matches (E05220 check)
      //Exhaustive coverage (E05260 check: both SaveAction and LoadAction handled)
      run() as pure
        -> action as SaveAction
        <- rtn as String: "Running save: " + action.perform()

      run() as pure
        -> action as LoadAction
        <- rtn as String: "Running load: " + action.perform()

  defines function

    testDispatcherChecks()
      runner <- ActionRunner()

      saveAction <- SaveAction()
      loadAction <- LoadAction()

      require runner.run(saveAction) == "Running save: saved"
      require runner.run(loadAction) == "Running load: loaded"

Java: only static overload resolution, no runtime dispatch validation. Kotlin: exhaustive when on sealed types only. Rust: exhaustive match on enums but no multi-dispatch. Julia: multiple dispatch with runtime ambiguity errors. Python: singledispatch with no compile-time validation. EK9: six compile-time dispatch checks eliminate all runtime dispatch errors.

run() as pure
        -> action as SaveAction

Explanation: The dispatcher entry run is declared as pure. Every handler must match this purity level. Omitting as pure on a handler creates a purity mismatch. See ek9 -h E05170 for details.

require runner.run(saveAction).toUpperCase() == "Running save: saved"

Explanation: The dispatcher entry takes Action. All handler parameter types must be subtypes of Action. String is outside the Action hierarchy. See ek9 -h E50001 for details.


Question: How do I format an EK9 source file?

Does EK9 have a built-in formatter like gofmt?

How do I canonically format a single .ek9 file?

Answer:
EK9 has a built-in code formatter, similar to gofmt for Go or rustfmt for Rust. Use the -f flag to format a single file.

BASIC USAGE
  ek9 -f myfile.ek9
This parses the file first, then rewrites it in canonical format. The file must be valid EK9 (parseable) for -f to work.

WHAT IT CHANGES
- Normalises indentation to canonical two-space indent.
- Standardises whitespace around operators and keywords.
- Aligns block structure consistently.
- Removes trailing whitespace.
- Ensures consistent blank-line usage between constructs.

WHAT IT PRESERVES
- Comments (both doc-comments and inline comments) are preserved.
- Blank lines that separate logical sections are maintained.
- The semantic meaning of the code is never altered.

WHEN TO USE -f
Use -f after writing or editing a file manually. It is safe to run repeatedly because the output is idempotent: formatting an already-formatted file produces the same file.

See Q623 for formatting all project files. See Q624 for force-formatting unparseable files. See Q625 for why EK9 enforces a standard format. See Q626 for integrating formatting into your workflow. See Q16 for indentation rules.

defines module qa.codeformatting.single

  defines function

    <?-
      A function that would be reformatted by ek9 -f
      if written with inconsistent spacing.
      After formatting, indentation is canonical.
    -?>
    greet() as pure
      -> name as String
      <- message as String: `Hello, ${name}!`

    calculateArea() as pure
      ->
        width as Float
        height as Float
      <-
        area as Float: width * height

  defines program

    FormatDemo()
      stdout <- Stdout()

      greeting <- greet("World")
      stdout.println(greeting)

      roomArea <- calculateArea(4.5, 3.2)
      stdout.println(`Area: ${roomArea}`)

Go: gofmt built into toolchain, universally adopted, single canonical style. Rust: rustfmt (cargo fmt) built into toolchain. Python: black (external, opinionated), autopep8, yapf. Java: google-java-format, spotless (all external plugins). JavaScript: prettier (external). EK9: ek9 -f built into compiler binary, same philosophy as gofmt (one true format, no configuration).

auto-format canonical format formatter gofmt indent migrate rustfmt style whitespace

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


Question: How do I format all EK9 source files in my project?

How do I format an entire EK9 project at once?

Answer:
Use the -F flag (uppercase) to format all EK9 source files in the project at once.

BASIC USAGE
  ek9 -F myproject.ek9
This discovers all .ek9 files referenced by the project and formats each one to canonical style. Every file must be parseable for this to succeed.

DIFFERENCE FROM -f
- ek9 -f myfile.ek9 formats a single named file.
- ek9 -F myproject.ek9 formats all files in the project.
Both require the files to parse successfully first.

TYPICAL WORKFLOW
After cloning a project or pulling changes:
  ek9 -F myproject.ek9
This ensures your entire codebase follows the canonical format before you start editing.

After a code review or merge:
  ek9 -F myproject.ek9
This normalises any formatting differences introduced during the merge.

IDEMPOTENCY
Running -F on an already-formatted project produces no changes. This makes it safe to run in CI to verify that all files are correctly formatted.

See Q622 for formatting a single file. See Q624 for force-formatting unparseable files. See Q625 for why EK9 enforces a standard format. See Q626 for integrating formatting into your workflow.

defines module qa.codeformatting.allfiles

  defines function

    <?-
      Multiple functions representing a multi-file project.
      After ek9 -F, every file follows the same format.
    -?>
    add() as pure
      ->
        left as Integer
        right as Integer
      <-
        result as Integer: left + right

    multiply() as pure
      ->
        left as Integer
        right as Integer
      <-
        result as Integer: left * right

  defines program

    FormatAllDemo()
      stdout <- Stdout()

      sum <- add(3, 4)
      product <- multiply(3, 4)

      stdout.println(`Sum: ${sum}`)
      stdout.println(`Product: ${product}`)

Go: gofmt ./... formats all Go files in directory tree. Rust: cargo fmt formats all crate sources. Python: black . formats all Python files in directory. Java: google-java-format with glob patterns (manual setup). JavaScript: prettier --write . (external tool). EK9: ek9 -F formats all project sources in one command, built into the compiler.

F all bulk canonical files format indent normalise project style

Explanation: Integer has no toString() method; convert with the $ prefix operator ($product) or use string interpolation. See ek9 -h E50060 for details.


Question: How do I format an EK9 file that does not parse?

How do I force-format a broken EK9 file?

Can the formatter fix syntax errors in my code?

Answer:
Use the -ff flag (force format) to attempt formatting on files that cannot be parsed. Use -fF to force-format all project files.

FORCE FORMAT SINGLE FILE
  ek9 -ff myfile.ek9
This attempts to reformat the file even if it contains syntax errors. The formatter applies what corrections it can based on the partial parse.

FORCE FORMAT ALL FILES
  ek9 -fF myproject.ek9
This force-formats every .ek9 file in the project, attempting to fix unparseable files.

ALL FOUR FORMAT FLAGS
  ek9 -f  myfile.ek9    Format single file (must parse).
  ek9 -F  myproject.ek9  Format all project files (must parse).
  ek9 -ff myfile.ek9    Force format single file (attempts fix).
  ek9 -fF myproject.ek9  Force format all project files (attempts fix).

WHEN TO USE FORCE FORMAT
- After pasting code from another language that needs significant restructuring.
- When a merge conflict leaves a file in a broken state.
- When indentation has become corrupted (tabs mixed with spaces, wrong levels).
- When AI-generated code has formatting issues that prevent parsing.

LIMITATIONS
Force format makes a best-effort attempt. If the file is severely broken (missing keywords, fundamentally wrong structure), the formatter cannot fully recover it. Always compile after force-formatting to verify the result.

See Q622 for standard single-file formatting. See Q623 for formatting all project files. See Q625 for why EK9 enforces a standard format.

defines module qa.codeformatting.force

  defines function

    <?-
      A well-formatted function.
      After force-formatting a broken version, it would look like this.
    -?>
    sanitizeInput() as pure
      -> raw as String
      <- cleaned as String: raw.trim()

    isNonEmpty() as pure
      -> text as String
      <- result as Boolean: text?

  defines program

    ForceFormatDemo()
      stdout <- Stdout()

      userInput <- "  hello world  "
      cleaned <- sanitizeInput(userInput)
      hasContent <- isNonEmpty(cleaned)

      stdout.println(`Cleaned: '${cleaned}'`)
      stdout.println(`Has content: ${hasContent}`)

Go: gofmt requires valid Go syntax, no force mode. Rust: rustfmt requires valid Rust syntax, no force mode. Python: black requires valid Python syntax. JavaScript: prettier can handle some malformed code. EK9: ek9 -ff provides explicit force-format mode for broken files, a unique capability among language formatters.

broken error fF ff fix force format indent recover style syntax unparseable


Question: Why does EK9 enforce a single standard format?

Why can I not configure the EK9 formatter?

Why does EK9 have only one formatting style?

What is the benefit of a non-configurable formatter?

Answer:
EK9 enforces a single canonical format for the same reason Go does with gofmt: formatting debates are a waste of engineering time.

THE GOFMT PRINCIPLE
Go's gofmt proved that a single, non-configurable formatter eliminates an entire category of team friction. Before gofmt, Go teams argued about brace placement, tab width, and blank lines. After gofmt, every Go file looks the same. The debate simply vanished.

EK9 APPLIES THE SAME LOGIC
The EK9 formatter produces one canonical output. There are no configuration flags for indent size, brace style, or line length. This is a deliberate design decision, not a missing feature.

BENEFITS
1. Zero formatting debates in code reviews. Every file looks identical.
2. Diffs show only semantic changes, never whitespace noise.
3. AI-generated code matches human-written code exactly.
4. New team members produce correctly-formatted code from day one.
5. Merge conflicts from formatting differences are eliminated.

THIS IS PART OF THE VERTICAL INTEGRATION
EK9 collapses 15+ external tools into the compiler. The formatter is one of them. Instead of configuring prettier, ESLint, editorconfig, and pre-commit hooks separately, EK9 provides ek9 -f built into the same binary that compiles, tests, and packages your code.

See Q622 for formatting a single file. See Q623 for formatting all project files. See Q310 for quality checks at compile time. See Q16 for indentation rules.

defines module qa.codeformatting.whystandard

  defines class

    <?-
      A class written in canonical EK9 format.
      Every developer's version of this class looks identical
      after formatting because there is only one style.
    -?>
    Temperature
      degrees as Float: 0.0
      scale as String: "C"

      Temperature()
        ->
          initialValue as Float
          initialScale as String
        degrees: initialValue
        scale: initialScale

      toCelsius() as pure
        <- result as Float: degrees

      toDisplay() as pure
        <- result as String: `${degrees} ${scale}`

      operator $ as pure
        <- result as String: toDisplay()

      default operator ?

  defines program

    WhyStandardFormatDemo()
      stdout <- Stdout()

      boiling <- Temperature(100.0, "C")
      stdout.println($boiling)

Go: gofmt established the 'one true format' principle. No configuration, universally adopted. Rust: rustfmt has some configuration options but defaults are strongly encouraged. Python: black is 'the uncompromising formatter' (few options). Java: teams still argue about google-java-format vs checkstyle vs IntelliJ defaults. JavaScript: prettier has some configuration. EK9: follows gofmt philosophy exactly, zero configuration, one canonical format.

canonical configure convention debate format gofmt indent migrate opinionated standard style team

Explanation: Temperature has no toString() method. Use the $ prefix operator for string conversion. See ek9 -h E50060 for details.

boiling <- Temperature(100.0, "C").toCelsius().intValue()

Explanation: Float has no intValue() method in EK9. Use explicit conversion if needed. See ek9 -h E50060 for details.


Question: How do I integrate EK9 formatting into my development workflow?

Should I format before or after committing?

How do I set up a format-on-save workflow for EK9?

Answer:
The EK9 formatter integrates naturally into both local development and CI pipelines.

LOCAL WORKFLOW
Format before committing:
  ek9 -F myproject.ek9
  git add -A
  git commit -m "feature: add new module"
This ensures every commit contains canonically formatted code.

PRE-COMMIT HOOK
Add a git pre-commit hook that formats and re-stages:
  #!/bin/sh
  ek9 -F myproject.ek9
  git add -u
This catches formatting issues before they reach the repository.

CI VERIFICATION
In CI, verify formatting without modifying files. Run:
  ek9 -F myproject.ek9
  git diff --exit-code
If -F produces any changes, git diff returns non-zero and the CI step fails. This ensures all committed code is correctly formatted.

AFTER AI CODE GENERATION
When an AI generates EK9 code, format it immediately:
  ek9 -ff generated.ek9
The -ff (force format) handles cases where the AI output might not parse perfectly. Then compile to verify correctness.

AFTER MERGE
After resolving merge conflicts:
  ek9 -fF myproject.ek9
Force-format handles any indentation corruption from the merge.

See Q622 for formatting a single file. See Q623 for formatting all files. See Q624 for force-formatting. See Q625 for why EK9 enforces a standard format. See Q281 for AI code verification.

defines module qa.codeformatting.workflow

  defines function

    <?-
      Functions that represent a typical workflow:
      write code, format, compile, test, commit.
    -?>
    validateAge() as pure
      -> age as Integer
      <- valid as Boolean: false

      minimumAge <- 0
      maximumAge <- 150
      if age >= minimumAge and age <= maximumAge
        valid: true

    formatName() as pure
      ->
        firstName as String
        lastName as String
      <-
        fullName as String: `${firstName} ${lastName}`

  defines program

    WorkflowDemo()
      stdout <- Stdout()

      ageIsValid <- validateAge(25)
      name <- formatName("Jane", "Doe")

      stdout.println(`Name: ${name}`)
      stdout.println(`Valid age: ${ageIsValid}`)

Go: gofmt integrated into most editors, goimports for import management, CI uses gofmt -l to check. Rust: cargo fmt, CI uses cargo fmt --check. Python: black with pre-commit framework, CI uses black --check. Java: spotless plugin in Maven/Gradle, CI runs spotless:check. JavaScript: prettier with husky/lint-staged, CI runs prettier --check. EK9: ek9 -F for formatting, git diff --exit-code in CI, -ff for AI/merge recovery, all built into one binary.

CI commit format git hook indent integrate pipeline pre-commit save style verify workflow


Question: How do I profile an EK9 program?

How do I find performance bottlenecks in EK9?

How do I measure function call times in EK9?

Answer:
EK9 has built-in profiling. Append 'p' to any test flag to enable performance profiling.

BASIC PROFILING
  ek9 -tp myproject.ek9
This runs all tests with profiling instrumentation and produces a human-readable summary showing call counts and timing for each function and method.

PROFILING OUTPUT FORMATS
The 'p' suffix works with every test format:
  ek9 -tp  myproject.ek9   Human-readable profiling summary.
  ek9 -t0p myproject.ek9   Terse profiling (CI pass/fail).
  ek9 -t2p myproject.ek9   JSON profiling data (for AI and tools).
  ek9 -t3p myproject.ek9   JUnit XML with profiling annotations.
  ek9 -t6p myproject.ek9   HTML dashboard with flame graph.

WHAT GETS MEASURED
For each function and method the profiler records:
- Call count: how many times it was invoked.
- Total time: wall-clock time including calls to other functions.
- Self time: time in this function only, excluding callees.
- Average, minimum, and maximum call times.
- Percentile distribution: p50, p95, p99.

NO OPTIMISATION
Profiling automatically forces -O0 (no optimisation). This ensures that probe-to-source mapping is accurate. Every function call is measured as written, with no inlining or dead code elimination.

See Q628 for profiling tests specifically. See Q629 for reading flame graphs. See Q630 for identifying hot methods. See Q631 for benchmarking two approaches. See Q322 for profiling overview. See Q207 for test output formats. See Q752 for fuzz HTML dashboard (similar report pattern).

defines module qa.profilingdeep.program

  defines function

    <?-
      Recursive function that shows up as a hot method in profiling.
      The profiler records call count and self-time for each invocation.
    -?>
    factorial() as pure
      -> number as Integer
      <- result as Integer: 1

      recursionBase <- 1
      if number > recursionBase
        subResult <- factorial(number - 1)
        result: number * subResult

    sumUpTo() as pure
      -> limit as Integer
      <- total as Integer: 0

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

  defines program

    ProfileProgramDemo()
      stdout <- Stdout()

      tenFactorial <- 10
      result <- factorial(tenFactorial)
      stdout.println(`10! = ${result}`)

      hundredSum <- 100
      sum <- sumUpTo(hundredSum)
      stdout.println(`Sum 1..100 = ${sum}`)

Java: async-profiler, JFR, or VisualVM (all external tools, require JVM flags). Python: cProfile (stdlib), py-spy (external, sampling). Rust: perf, flamegraph crate (external). Go: go tool pprof with -cpuprofile flag (built-in but requires code changes). JavaScript: Chrome DevTools profiler (browser only). EK9: append 'p' to any test flag for built-in profiling, zero code changes, multiple output formats including JSON and HTML flame graph.

O0 bottleneck call count flame-graph performance profile profiling program self time total tp