Question: How do I create an enumeration in EK9?

What operators do enumerations get automatically?

Answer:
Enumerations are defined under 'defines type' with a value list. They automatically get 24 operators and 3 constructors with no boilerplate.

BASIC ENUMERATION
  defines type
    Season
      Spring, Summer, Autumn, Winter
Values ordered by declaration.

AUTOMATIC OPERATORS (24 + 3 constructors)
  Comparison: ==, <>, <, >, <=, >=, <=> (same-type and String)
  Conversion: $ (String), #^ (promote), $$ (JSON)
  Hash/Set: #?, ?
  First/Last: #<, #>
  Constructors: Season() (unset), Season(Season) (copy), Season(String) (from string)

TRI-STATE
  Season() is unset. Season("Invalid") returns unset. ? checks state. No exceptions on invalid input.

ITERATION
  for season in Season
Or: cat Season > stdout, cat Season | collect as List of Season.

SWITCH
Compiler requires all enum values listed in cases (E07310). Duplicates detected (E02060).

NO METHODS
Enums are pure value types. No methods, fields, or custom constructors. Use functions for behaviour, Dicts for associated data.

See Q73 for exhaustive switch. See Q96 for operators. See Q100 for Java comparison. See Q219 for free operators. See Q223 for constrained enums. See Q224 for enum streams. See Q238 for the fixed operator set.

defines module qa.oop.enumeration

  defines type

    Season
      Spring
      Summer
      Autumn
      Winter

  defines function

    describeSeason() as pure
      -> season as Season
      <- description <- String()

      switch season
        case Season.Spring
          description: "flowers bloom"
        case Season.Summer
          description: "warm and sunny"
        case Season.Autumn
          description: "leaves falling"
        case Season.Winter
          description: "cold and snowy"
        default
          description: "no season set"

  defines program

    EnumerationDemo()
      stdout <- Stdout()

      // === BASIC ENUMERATION: qualified access ===

      favourite <- Season.Summer
      stdout.println(`Favourite: ${favourite}`)

      // === AUTOMATIC COMPARISON OPERATORS ===

      stdout.println(`Spring < Winter: ${Season.Spring < Season.Winter}`)
      stdout.println(`Summer == Summer: ${Season.Summer == Season.Summer}`)

      // === STRING COMPARISON: enum vs String directly ===

      stdout.println(`Direct string compare: ${favourite == "Summer"}`)

      // === FIRST AND LAST ===

      stdout.println(`First: ${#< favourite}`)
      stdout.println(`Last: ${#> favourite}`)

      // === CONVERSION: $, #? ===

      asString <- $favourite
      stdout.println(`As string: ${asString}`)
      stdout.println(`Hash: ${#?favourite}`)

      // === TRI-STATE: unset enumerations ===

      unset <- Season()
      stdout.println(`Unset isSet: ${unset?}`)

      // === STRING CONSTRUCTION: safe, no exceptions ===

      fromString <- Season("Autumn")
      stdout.println(`From valid string: ${fromString}`)

      invalid <- Season("Monsoon")
      stdout.println(`From invalid string isSet: ${invalid?}`)

      // === GUARD EXPRESSION: safe string parsing ===

      if parsed <- Season("Winter")
        stdout.println(`Parsed: ${parsed}`)

      // === ITERATION: for...in ===

      stdout.println("All seasons:")
      for season in Season
        stdout.println(`  ${season}: ${describeSeason(season)}`)

      // === STREAM PIPELINE ===

      seasons <- cat Season | collect as List of Season
      stdout.println(`Count: ${length seasons}`)

      // === STREAM TO OUTPUT ===

      stdout.println("Streamed:")
      cat Season > stdout

      // === SWITCH WITH MULTIPLE CASE VALUES ===

      for season in Season
        category <- switch season
          <- rtn as String: String()
          case Season.Spring, Season.Summer
            rtn: "warm half"
          case Season.Autumn, Season.Winter
            rtn: "cold half"
          default
            rtn: "unknown"
        stdout.println(`${season} is ${category}`)

Java: enum valueOf() throws on invalid. Python: Enum['NAME'] raises KeyError. Go: iota constants, no type safety. EK9: value list, 24 auto operators, invalid string returns unset, for...in iteration, no methods.

JSON automatic comparison composition constructor enum enumeration first hashcode iteration last operator pipeline stream string switch tri-state type unset value

Explanation: The $ operator works on enumeration INSTANCES, not the enumeration TYPE. Use $ on variables like $favourite, not on the type name Season. See ek9 -h E07630 for details.

Spring
      Summer
      Autumn
      Winter

Spring
      Summer
      SPRING
      Winter

Explanation: EK9 normalizes enumeration names (uppercase + remove underscores) to detect confusing duplicates. Spring and SPRING both normalize to SPRING, triggering E01050. Use clearly distinct names. See ek9 -h E01050 for details.

Explanation: When switching on an enumeration, the compiler requires all values to be explicitly listed in case clauses. Removing Autumn and Winter cases leaves the switch non-exhaustive, triggering E07310. See ek9 -h E07310 for details.

Explanation: Duplicate enumeration values in switch cases are detected at compile time. Replacing Autumn with a second Spring creates a duplicate, triggering E02060. See ek9 -h E02060 for details.

Oracle can generate an enumeration type with named values.


Question: How do EK9 enumerations differ from Java enums?

Why are EK9 enums simpler than Java enums?

Can EK9 enums have methods and fields like Java?

What can Java enums do that EK9 enumerations cannot?

How do I migrate Java enum patterns to EK9?

Answer:
EK9 enumerations are deliberately simpler than Java enums. They are pure value types with no methods, no fields, and no associated data. Every enum automatically gets 24 capabilities: 14 comparison operators (same-type and string), $, $$, #?, ?, #<, #>, #^, three constructors, and built-in iteration.

KEY DIFFERENCES FROM JAVA
1. No methods or fields — behaviour goes in standalone functions, data in Dicts (composition over enum methods).
2. Safe string construction — Priority("Invalid") returns unset, not IllegalArgumentException.
3. Direct string comparison — 'priority == "High"' works without conversion.
4. Exhaustive switch — adding a value forces updating ALL switches (E07310). Java's default silently swallows new values.

EK9 COMPOSITION PATTERN
  Java: Priority.HIGH.getLabel() with field on enum
  EK9: priorityLabel(Priority.High) with standalone function

See Q99 for creating enumerations. See Q73 for exhaustive enum switch. See Q219 for auto-generated operators. See Q225 for the composition pattern in detail.

defines module qa.oop.enumvsjava

  defines type

    Priority
      Low
      Medium
      High
      Critical

  defines function

    <?-
      In Java this would be a method ON the enum.
      In EK9 behaviour lives in standalone functions.
    -?>
    priorityLabel() as pure
      -> priority as Priority
      <- label as String: switch priority
        <- rtn as String: String()
        case Priority.Low
          rtn: "Low - handle when convenient"
        case Priority.Medium
          rtn: "Medium - handle soon"
        case Priority.High
          rtn: "High - handle today"
        case Priority.Critical
          rtn: "Critical - handle immediately"
        default
          rtn: "No priority set"

    isUrgent() as pure
      -> priority as Priority
      <- rtn as Boolean: priority >= Priority.High

  defines program

    EnumVsJavaDemo()
      stdout <- Stdout()

      // === ALL 24 AUTO-GENERATED CAPABILITIES ===

      high <- Priority.High

      //Same-type comparison (== <> < > <= >= <=>)
      stdout.println(`Ordering: High > Low = ${high > Priority.Low}`)
      stdout.println(`Compare: High <=> Medium = ${high <=> Priority.Medium}`)

      //String comparison (== <> < > <= >= <=>)
      stdout.println(`Equals string: ${high == "High"}`)
      stdout.println(`Not equal string: ${high <> "Low"}`)

      //String conversion ($ #^)
      stdout.println(`Dollar: ${high}`)

      //JSON serialization ($$)
      stdout.println(`JSON: ${$high}`)

      //Hash code (#?)
      stdout.println(`Hash: ${#? high}`)

      //Is set (?)
      stdout.println(`Is set: ${high?}`)

      //First / Last (#< #>)
      stdout.println(`First: ${#< high}`)
      stdout.println(`Last: ${#> high}`)

      // === SAFE STRING CONSTRUCTION: no exception ===
      // Java: Priority.valueOf("Urgent") throws IllegalArgumentException
      // EK9: Priority("Urgent") returns unset

      //Constructor from String (unset if no match)
      fromString <- Priority("High")
      stdout.println(`Valid: ${fromString}, isSet: ${fromString?}`)

      invalid <- Priority("Urgent")
      stdout.println(`Invalid isSet: ${invalid?}`)

      //Copy constructor
      copy <- Priority(high)
      stdout.println(`Copy: ${copy}`)

      //Default constructor (unset)
      unsetPriority <- Priority()
      stdout.println(`Unset isSet: ${unsetPriority?}`)

      // === GUARD: safe string-to-enum parsing ===

      if parsed <- Priority("Critical")
        stdout.println(`Parsed: ${parsed}`)

      // === TRI-STATE: unset enum (no null in EK9) ===

      stdout.println(priorityLabel(unsetPriority))

      // === BEHAVIOUR VIA FUNCTIONS (replaces Java enum methods) ===

      for priority in Priority
        stdout.println(priorityLabel(priority))

      // === DICT FOR ASSOCIATED DATA (replaces Java enum fields) ===

      icons <- {
        Priority.Low: "...",
        Priority.Medium: "(!)",
        Priority.High: "(!!)",
        Priority.Critical: "!!!"
        }

      for priority in Priority
        icon <- icons.getOrDefault(priority, "?")
        stdout.println(`${priority}: ${icon}`)

      // === STREAM PIPELINE: built-in iteration ===
      // Java: Arrays.stream(Priority.values())
      // EK9: cat Priority | ...

      urgent <- cat Priority
        | filter by isUrgent
        | collect as List of Priority
      stdout.println(`Urgent count: ${length urgent}`)

Java: enums are full classes with fields/methods, valueOf() throws. Python: Enum with methods, KeyError on invalid. Kotlin: enum class with properties. EK9: pure value types, 24 auto-generated capabilities, no methods/fields, safe string construction returns unset.

Dict comparison composition construction enumeration exhaustive field function java method migrate operator simple string switch tri-state type unset value valueOf

Explanation: EK9 enumerations cannot have methods. Java developers often expect to call methods like label() on enum values. The compiler reports the method as not resolved. In EK9, behaviour belongs in standalone functions that take the enum as a parameter. See ek9 -h E50060 for details.

Explanation: Enumerations only support specific operators. Using negate (-) on an enum instance triggers E07620 — operator not defined. Enumerations support comparison, string conversion, isSet, first/last, and hash operators only. See ek9 -h E07620 for details.

Explanation: Every switch over an enumeration must cover ALL values. Removing a case triggers E07310 — cases should cover all enumerated values. EK9 does not allow silent omission even when a default is present. See ek9 -h E07310 for details.

Explanation: Enumerations only support a specific set of operators: comparison (== <> < > <= >= <=>), string conversion ($ #^), JSON ($$), hash (#?), isSet (?), and first/last (#< #>). Using any other operator like bitwise negate (~) on an enum instance produces this error. See ek9 -h E07620 for details.

Low
      Medium
      High
      Critical

Explanation: EK9 normalizes enumeration names (uppercase + remove underscores) to detect confusing duplicates. LOW and low both normalize to LOW. Java allows case-different enum constants but EK9 prevents this source of confusion. See ek9 -h E01050 for details.


Question: Why are types closed by default in EK9?

What does closed by default mean in EK9?

How does EK9 prevent fragile base class problems?

What are the three levels of type openness in EK9?

Answer:
EK9 types are closed by default, meaning they cannot be extended unless explicitly marked with 'as open' or 'as abstract'. This prevents the fragile base class problem and enforces composition over inheritance.

THREE LEVELS OF TYPE OPENNESS
EK9 provides three levels of control over type extension:
  1. CLOSED (default) — cannot be extended at all
  2. OPEN ('as open') — can be extended by any class
  3. SEALED ('allow only') — can only be extended by explicitly named classes
This gives precise control: most types stay closed, some are selectively opened, and sealed types restrict extension to a known set.

WHICH CONSTRUCTS SUPPORT OPEN AND ABSTRACT?
Five constructs support 'as open' and 'as abstract': functions, records, traits, classes, and components. Traits are inherently open (designed to be implemented) so 'as open' on a trait is accepted for syntax consistency but has no additional effect. Services and text blocks are always closed — they cannot be extended.

CLOSED BY DEFAULT PRINCIPLE
When you define a class without modifiers, it is final:
  defines class
    Config
      host <- String()
Attempting to extend Config produces compile error E05030.

SEALED TYPES WITH 'ALLOW ONLY'
For types that need limited extensibility, EK9 provides 'allow only' — the equivalent of Java 17's sealed classes and Kotlin's sealed classes. Both traits and classes support this:
  Shape allow only Circle, Square, Triangle
    area() as abstract
      <- rtn as Float?
Only the named classes can implement Shape. Any other class attempting 'with trait of Shape' triggers E05240. Classes with 'allow only' must also be declared 'as open' since classes are closed by default.

THE PROBLEM WITH OPEN TYPES
Open types create maintenance risks: subclasses depend on implementation details, overriding methods can break invariants, and changes to the base class can silently break subclasses (fragile base class problem).

HISTORICAL EVIDENCE
Java's open-by-default ArrayList, HashMap, and other collection classes have been a source of bugs for decades. Subclassing collections mixes collection behaviour with application logic.

MODERN TREND
Swift structs are closed. Rust has no inheritance. Kotlin classes are final by default. Java added sealed classes in Java 17. EK9 follows this modern consensus and goes further by making closed the default.

WHAT THIS MEANS IN PRACTICE
Use composition and delegation instead of inheritance:
  Employee
    role as Role?
This approach is more flexible and avoids tight coupling.

See Q102 for making types extensible with 'as open'. See Q109 for composition patterns. See Q93 for class basics. See Q106 for traits as an alternative. See Q128 for why built-in collection types (List, Dict) are closed and the delegation pattern. See Q212 for composition over inheritance pattern. See Q268 for how closed types prevent OWASP access control vulnerabilities. See Q298 for sealed traits with 'allow only'. See Q301 for sealed classes with 'allow only'. See Q299 for sealed types with dispatchers.

defines module qa.oop.closedbydefault

  defines trait

    SealedTrait allow only AllowedPrinter

      describe() as abstract
        <- rtn as String?

  defines record

    //Closed record - cannot be extended (no 'as open')
    ClosedRecord
      value <- 0
      default operator

    //Open record - can be extended
    OpenRecord as open
      name <- String()
      default operator

    ExtendedRecord extends OpenRecord
      extra <- String()
      default operator

  defines class

    Config
      host <- "localhost"
      port <- 8080

      host() as pure
        <- rtn as String: host

      port() as pure
        <- rtn as Integer: port

      default operator

    //Open class - can be extended
    OpenClass as open
      label <- String()

      label() as pure
        <- rtn as String: label

      default operator

    ExtendedClass extends OpenClass
      detail <- String()
      default operator

    //Sealed trait implementation - allowed
    AllowedPrinter with trait of SealedTrait
      override describe()
        <- rtn as String: "allowed"

      default operator

    //Not in the allow only list
    UnlistedPrinter

      unlisted()
        <- rtn as String: "not allowed"

      default operator

    ServerRunner
      config <- Config()

      ServerRunner()
        -> config as Config
        this.config: config

      describe()
        <- rtn as String: `Server at ${config.host()}:${config.port()}`

      default operator ?

  defines program

    ClosedByDefaultDemo()
      stdout <- Stdout()

      // === CLOSED BY DEFAULT ===

      config <- Config()
      stdout.println(`Config: ${config}`)

      // === OPEN CLASS CAN BE EXTENDED ===

      ext <- ExtendedClass()
      stdout.println(`Extended: ${ext}`)

      // === OPEN RECORD CAN BE EXTENDED ===

      rec <- ExtendedRecord()
      stdout.println(`Extended record: ${rec}`)

      // === SEALED TRAIT — ONLY ALLOWED TYPES ===

      allowed <- AllowedPrinter()
      stdout.println(`Sealed: ${allowed.describe()}`)

      // === COMPOSITION INSTEAD OF INHERITANCE ===

      server <- ServerRunner(config)
      stdout.println(server.describe())

Java: classes are open by default, must use 'final' to prevent extension (most developers forget); sealed classes added in Java 17 with 'permits' clause. Python: all classes are open, no way to prevent extension. Rust: no inheritance, uses traits and composition exclusively. Go: no inheritance, only interface embedding and struct composition. Kotlin: classes are final by default, must use 'open' keyword; sealed classes restrict subclasses to same file. Swift: classes are final by default with 'final' keyword, structs cannot be inherited. EK9: three levels — closed (default), open ('as open'), sealed ('allow only' with named permitted types). Closed by default like Kotlin, with sealed types equivalent to Java 17 sealed classes.

abstract allow base class closed component composition default exhaustive final fragile function inheritance migrate object-oriented only open permit record restrict sealed swift trait

closed type
sealed class
final class
open closed

Explanation: Config is closed by default (no 'as open' modifier). Attempting to extend it triggers E05030 — not open to be extended. EK9 types are closed by default to prevent the fragile base class problem. Use composition instead. See ek9 -h E05030 for details.

Explanation: ClosedRecord has no 'as open' modifier, so it cannot be extended. Records are also closed by default, just like classes. See ek9 -h E05030 for details.

Explanation: Class properties are private by default in EK9. Accessing 'host' directly from outside the class fails; use the accessor method 'host()' instead. See ek9 -h E06180 for details.

UnlistedPrinter with trait of SealedTrait

      override describe()

Explanation: When a trait uses 'allow only' to restrict implementations, only the named classes can implement it. A class not in the list triggers E05240 — type is not permitted to extend/implement this sealed type. This is EK9's equivalent of Java's sealed interfaces. See ek9 -h E05240 for details.


Question: How do I make a class extensible with 'as open'?

How do I allow a class to be extended in EK9?

Answer:
In EK9, use 'as open' to allow a class to be extended. Without it, classes are closed by default (E05030). Abstract classes are automatically open since they must be extended to be useful.

THE AS OPEN MODIFIER
Mark a class as open to allow subclasses:
  Shape as open
    name <- String()
Now other classes can extend Shape. Without 'as open', attempting to extend Shape triggers E05030.

WHICH CONSTRUCTS SUPPORT AS OPEN?
Five constructs support 'as open': functions, records, classes, components, and traits. Traits are inherently open (designed to be implemented) so 'as open' on a trait is accepted for syntax consistency but has no additional effect. Services and text blocks cannot be extended.

ABSTRACT CLASSES ARE AUTOMATICALLY OPEN
Abstract classes are inherently open because they require concrete subclasses:
  Vehicle as abstract
    speed() as abstract
      <- rtn as Float?
'as abstract' implies 'as open' — you never need both.

INHERITANCE SYNTAX
Use 'extends' or 'is' to inherit:
  Circle extends Shape
  Circle is Shape
Both are equivalent. Use 'override' for overridden methods — it is mandatory in EK9, not optional like in Java.

OVERRIDE IS MANDATORY
Unlike Java where @Override is an annotation you can forget, EK9 requires the 'override' keyword on any method that overrides a parent method. Omitting it triggers E05120. This prevents accidental shadowing.

SEALED ALTERNATIVE: ALLOW ONLY
If you want limited extensibility rather than fully open, use 'allow only' to restrict which classes can extend:
  Shape allow only Circle, Square, Triangle as open
This is EK9's equivalent of Java 17's sealed classes. See Q298-Q301 for details.

WHEN TO USE AS OPEN
Use 'as open' when you deliberately design a type for extension. Keep open types small in number — most types should remain closed. Prefer composition (Q109) and traits (Q106) over inheritance where possible.

See Q101 for why types are closed by default. See Q103 for abstract classes. See Q93 for class basics. See Q106 for traits. See Q570 for override mechanics in open class hierarchies. See Q298 for sealed traits with 'allow only'. See Q301 for sealed classes.

defines module qa.oop.asopen

  defines class

    Shape as open
      name <- "shape"

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

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

      describe()
        <- rtn as String: "I am a " + name

      default operator

    Circle extends Shape
      radius <- 0.0

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

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

      override describe()
        <- rtn as String: `I am a circle with radius ${radius}`

      default operator ?

    Vehicle as abstract
      speed() as abstract
        <- rtn as Float?

      describe()
        <- rtn as String: `Vehicle at ${speed()} mph`

      default operator ?

    Car is Vehicle
      topSpeed <- 120.0

      Car()
        -> topSpeed as Float
        this.topSpeed: topSpeed

      override speed()
        <- rtn as Float: topSpeed

      default operator ?

  defines record

    Coordinate
      xPos as Float: 0.0
      yPos as Float: 0.0

      default operator ?

  defines program

    AsOpenDemo()
      stdout <- Stdout()

      // === AS OPEN: explicit extension ===

      shape <- Shape("triangle")
      stdout.println(shape.describe())

      circle <- Circle(5.0)
      stdout.println(circle.describe())

      // === ABSTRACT: automatically open ===

      car <- Car(150.0)
      stdout.println(car.describe())

      // === OVERRIDE ===

      stdout.println(`Circle name: ${circle.name()}`)
      stdout.println(`Car speed: ${car.speed()}`)

Java: all classes are open by default, use 'final' to close; @Override annotation is optional (easily forgotten); sealed classes added in Java 17 with 'permits'. Python: all classes open, no way to seal. Rust: no inheritance at all, traits only. Go: no inheritance, embedding only. Kotlin: classes final by default, 'open' keyword to allow extension (same approach as EK9), 'override' keyword mandatory (same as EK9). Swift: classes not final by default but 'final' keyword available. EK9: closed by default, 'as open' to allow extension, 'as abstract' is automatically open, 'extends' or 'is' for inheritance, 'override' mandatory not optional, 'allow only' for sealed types.

abstract allow base child class component define extends extensible function inherit inheritance is modifier object-oriented only open override parent record sealed subclass super virtual

as open
open class
extensible
inheritance

Explanation: When overriding a method from a parent class, the 'override' keyword is mandatory. Omitting it on Circle's describe() triggers E05120 because the method exists in Shape. See ek9 -h E05120 for details.

Explanation: Without 'as open', Shape would be closed by default and Circle could not extend it. Attempting to extend a closed class triggers E05030 — not open to be extended. EK9 types are closed by default to prevent the fragile base class problem. See ek9 -h E05030 for details.

Explanation: Car must use 'override' when implementing the abstract method speed() from Vehicle. Omitting 'override' triggers E05120. See ek9 -h E05120 for details.

Explanation: A class defined in 'defines class' cannot extend a record from 'defines record' because they are different construct types (genus). Classes extend classes, records extend records. See ek9 -h E50020 for details.

Explanation: Circle's constructor calls super("circle") which expects Shape's single-String constructor. Vehicle has a different constructor signature (make, year). Extending Vehicle with Shape's super call triggers E50060 — constructor not resolved. See ek9 -h E50060 for details.

Oracle can generate an extensible class with 'as open' modifier and correct inheritance structure.


Question: What are abstract classes and methods in EK9?

How do I create an abstract class in EK9?

When should I use abstract vs trait in EK9?

Answer:
Abstract classes define incomplete types that must be extended with concrete implementations. They can have both implemented and abstract methods, and they can hold state (properties).

ABSTRACT CLASSES
Mark a class as abstract with 'as abstract':
  Formatter as abstract
    prefix <- String()
    format() as abstract
      -> text as String
      <- rtn as String?
Abstract classes cannot be instantiated directly (E50080) — they are incomplete types. 'as abstract' implies 'as open' — you never need both.

WHICH CONSTRUCTS SUPPORT AS ABSTRACT?
Five constructs support 'as abstract': functions, records, classes, components, and traits. Abstract functions are unique to EK9 among mainstream languages. A method without a body must be declared 'as abstract' (E07110).

ABSTRACT METHODS
Abstract methods have no body. Subclasses must provide implementations using the mandatory 'override' keyword:
  override format()
    -> text as String
    <- rtn as String: prefix + data
Omitting 'override' triggers E05120. The compiler ensures all abstract methods are implemented — omitting any triggers E07140.

ABSTRACT FUNCTIONS
EK9 also supports abstract functions:
  defines function
    Parser as abstract
      -> input as String
      <- result as String?
Concrete functions extend abstract ones with 'is' or 'extends'.

WHEN TO USE ABSTRACT VS TRAIT
Use abstract class when: you need shared state (properties), constructors, and partial implementation with a single inheritance chain.
Use trait when: you need pure behaviour contracts that multiple unrelated classes can implement. EK9 supports multiple traits but single class inheritance.

See Q102 for 'as open'. See Q101 for closed by default. See Q51 for abstract functions. See Q106 for traits. See Q110 for trait vs abstract comparison. See Q111 for components. See Q263 for factory pattern returning abstract class implementations. See Q560 for purity contracts on abstract methods. See Q570 for override mechanics. See Q580 for constructor delegation in class hierarchies.

defines module qa.oop.abstractclasses

  defines function

    Parser as pure abstract
      -> input as String
      <- result as String?

    JsonParser is Parser as pure
      -> input as String
      <- result as String: "parsed:" + input

  defines class

    Formatter as abstract
      prefix <- String()

      Formatter()
        -> prefix as String
        this.prefix: prefix

      format() as abstract
        -> text as String
        <- rtn as String?

      prefix() as pure
        <- rtn as String: prefix

      default operator ?

    BracketFormatter extends Formatter
      BracketFormatter()
        super("[")

      override format()
        -> text as String
        <- rtn as String: `${prefix()}${text}]`

      default operator ?

    HtmlFormatter extends Formatter
      tag <- "p"

      HtmlFormatter()
        -> tag as String
        super(`<${tag}>`)
        this.tag: tag

      override format()
        -> text as String
        <- rtn as String: `${prefix()}${text}</${tag}>`

      default operator ?

  defines program

    AbstractDemo()
      stdout <- Stdout()

      // === ABSTRACT CLASS with concrete subclass ===

      bracket <- BracketFormatter()
      stdout.println(bracket.format("hello"))

      html <- HtmlFormatter("div")
      stdout.println(html.format("content"))

      // === ABSTRACT FUNCTION ===

      result <- JsonParser("data")
      stdout.println(result)

      // === Polymorphism ===

      formatters <- List() of Formatter
      formatters += BracketFormatter()
      formatters += HtmlFormatter("span")

      for formatter in formatters
        stdout.println(formatter.format("test"))

Java: abstract class with abstract methods, can have state and constructors, single inheritance. Python: ABC with @abstractmethod decorator, no enforcement at parse time. Rust: no abstract classes, use traits with default implementations. Go: no abstract types, interfaces are implicitly implemented. Kotlin: abstract class with abstract members, can have state, single inheritance. EK9: 'as abstract' modifier, abstract methods with no body, abstract functions (unique), automatically open for extension, subclasses use 'override'.

abstract class component concrete extends function hierarchy implement incomplete is method object-oriented open override record state trait virtual

Explanation: When implementing an abstract method from a parent class, the 'override' keyword is mandatory. Omitting it on BracketFormatter's format() triggers E05120. See ek9 -h E05120 for details.

Explanation: Only abstract or open classes can be extended. Abstract classes like Formatter are automatically open. Extending a closed class triggers E50010 — not open to be extended. See ek9 -h E50010 for details.

format()
        -> text as String
        <- rtn as String?

Explanation: A method without a body must be declared 'as abstract'. Omitting the modifier triggers E07110 — implementation not provided so must be declared as abstract. See ek9 -h E07110 for details.

Explanation: A class cannot extend a function because they are different construct types (genus). Classes extend classes and functions extend functions. See ek9 -h E50020 for details.

Explanation: BracketFormatter extends abstract Formatter but must implement all abstract methods. Removing the override of format() leaves the abstract method unimplemented, triggering E07130 — all abstract methods must be implemented in concrete subclasses. See ek9 -h E07130 for details.

Explanation: Abstract classes cannot be instantiated directly — they are incomplete types with unimplemented methods. Attempting to call Formatter() triggers E50080 — cannot make a call on an abstract function/type directly. Create a concrete subclass instead. See ek9 -h E50080 for details.

Formatter as open
      prefix <- String()

Explanation: An abstract method can only exist inside an abstract class. If the class is not marked 'as abstract' but contains an abstract method, E50040 is triggered — cannot be abstract in a non-abstract construct. Either mark the class 'as abstract' or provide a method body. See ek9 -h E50040 for details.


Question: Why does EK9 require explicit constructors for uninitialised properties?

What happens if a class property has no default value in EK9?

How do I handle uninitialised fields in an EK9 class?

Why must I write a constructor when a property is not initialised?

Answer:
EK9 requires that every property is either given a default value at declaration or initialised in a constructor. If a property is declared without a value, the compiler requires a constructor that sets it. This prevents null surprises.

THE RULE
Properties declared without values must be initialised in a constructor:
  Connection
    host as String?
    port as Integer?
    Connection()
      -> host as String, port as Integer
      this.host: host
      this.port: port
The '?' suffix means 'declared but not yet initialised'. Without '?' the compiler rejects the declaration immediately with E08180.

FLOW ANALYSIS ENFORCEMENT
EK9 uses flow analysis to verify that every property is initialised before use:
  E08180 — property not marked '?' and not initialised (must use '?' or provide a value)
  E08070 — property declared with '?' but never assigned in any constructor
  E08060 — property used before it has been initialised (would be NullPointerException in Java)
This eliminates the entire category of 'field not initialised' bugs at compile time.

WHY THIS MATTERS
In Java, fields default to null. You discover the problem at runtime with NullPointerException. In Python, missing attributes cause AttributeError at runtime. In Kotlin, lateinit crashes at runtime if accessed before init. EK9 catches ALL of these at compile time.

HOW TO SATISFY THE RULE
Option 1: Provide a default value at declaration:
  host <- "localhost"
Option 2: Use unset default (type constructor):
  host <- String()
Option 3: Declare without value and initialise in constructor:
  host as String?
  MyClass() -> host as String; this.host: host
Note: property initialisers must be simple — literals, type constructors, or literal collections only. Function calls and expressions are not allowed (E04050). Use a constructor for computed values.

See Q29 for unset variables. See Q94 for constructors. See Q93 for class basics. See Q632 for define-before-use patterns. See Q633 for initialisation across branches. See Q692 for field initialisation patterns.

defines module qa.oop.uninitialised

  defines class

    Connection
      host as String?
      port as Integer?

      default private Connection()

      Connection()
        ->
          host as String
          port as Integer
        this.host: host
        this.port: port

      describe()
        <- rtn as String: `${host}:${port}`

      default operator ?

    DatabaseConfig
      host <- "localhost"
      port <- 5432
      database <- String()

      DatabaseConfig()
        -> database as String
        this.database: database

      describe()
        <- rtn as String: `${host}:${port}/${database}`

      default operator ?

  defines program

    UninitialisedDemo()
      stdout <- Stdout()

      // === UNINITIALISED PROPERTIES: must be set in constructor ===

      conn <- Connection("db.example.com", 3306)
      stdout.println(`Connection: ${conn.describe()}`)
      stdout.println(`IsSet: ${conn?}`)

      // === MIX: some defaults, some uninitialised ===

      db <- DatabaseConfig("myapp")
      stdout.println(`Database: ${db.describe()}`)

      // === Default values work without constructor args ===

      stdout.println(`DB isSet: ${db?}`)

Java: fields default to null/0/false, no compile-time enforcement of initialisation. Python: no field declarations, set in __init__, AttributeError if accessed before set. Rust: all fields must be initialised in struct literal, compiler enforces. Go: fields zero-valued by default (empty string, 0, nil). Kotlin: lateinit var for deferred initialisation, crashes at runtime if accessed too early. EK9: compile-time enforcement that properties are either declared with values or initialised in constructors, no null defaults.

NullPointerException analysis compile constructor default field flow initialise lateinit migrate null object-oriented property required safety uninitialised uninitialized value

Explanation: Properties declared without '?' and without a default value must be initialised inline. The '?' suffix marks a property as explicitly uninitialised, requiring constructor assignment. If neither '?' nor a default is provided the compiler may report E08180. See ek9 -h E08180 for details.

Explanation: If a constructor does not initialise all uninitialised properties, the compiler's flow analysis detects that 'port' is never assigned any value and reports E08060 — variable declared but never initialised. Every property must be set on every code path. See ek9 -h E08060 for details.

Explanation: If the constructor forgets to initialise 'host' but a method later reads it, E08060 is triggered — is/may not be initialised before use. In Java this would be a NullPointerException at runtime. EK9 catches it at compile time through flow analysis. See ek9 -h E08060 for details.

Explanation: Property initialisers must be simple values: a literal ('localhost'), a type constructor (String()), or a literal collection. Function calls and expressions are not allowed in property declarations. Java developers are used to 'private String host = computeDefault()' but EK9 requires E50001 — type must be a simple aggregate/list/dict. Use a constructor to compute values instead. See ek9 -h E50001 for details.

Explanation: Properties declared without '?' and without a default value must be initialised inline. The '?' suffix marks a property as explicitly uninitialised, requiring constructor assignment. If neither '?' nor a default is provided, E08180 is triggered. See ek9 -h E08180 for details.

Explanation: Property initialisers must be simple values: a literal ('localhost'), a type constructor (String()), or a literal collection. Function calls and expressions like String().trim() are not allowed in property declarations. Use a constructor for computed values. See ek9 -h E04050 for details.


Question: How does method dispatch work in EK9?

How do I dispatch on runtime types in EK9?

Answer:
EK9 supports method dispatch via the 'as dispatcher' keyword. A dispatcher method acts as an entry point that automatically routes to the most specific overloaded method based on the runtime type of arguments.

BASIC DISPATCH
Normal method calls use compile-time types. When you need runtime type dispatch, mark the base method with 'as dispatcher':
  render() as dispatcher
    -> shape as Shape
    <- rtn as String: shape.draw()
The runtime selects the most specific 'render' overload. The dispatcher method is the fallback when no specific handler matches.

OVERLOADED HANDLERS
Overloaded methods handle specific types:
  render()
    -> c as Circle
    <- rtn as String: "circle: " + c.draw()
  render()
    -> r as Rectangle
    <- rtn as String: "rectangle: " + r.draw()

COMPILE-TIME VALIDATION
The compiler validates dispatchers extensively:
  E05180 — private method in super has same name as dispatcher (won't participate in dispatch)
  E05210 — handler type not in base type hierarchy (e.g. Integer handler in Shape dispatcher)
  E05260 — dispatcher on sealed type missing handlers for permitted types
  E05220 — handler return type incompatible with dispatcher return type
  E06320 — handler parameter count differs from dispatcher
  E05170 — purity mismatch (dispatcher pure but handler not pure)
  E07820 — multiple methods marked 'as dispatcher' with same name

SEALED TYPES WITH DISPATCHERS
When a trait uses 'allow only', the compiler enforces exhaustive dispatch:
  Shape allow only Circle, Square, Triangle
A dispatcher on Shape must have handlers for ALL permitted types (Circle, Square, AND Triangle) or E05260 is triggered. This eliminates the missing-case bugs that plague Visitor patterns.

DOUBLE DISPATCH
Dispatchers can take two arguments:
  intersect() as dispatcher
    -> s1 as Shape, s2 as Shape
With overloads for specific type pairs, the runtime picks the best match. Each parameter is independently validated against its base type's hierarchy.

WHEN TO USE DISPATCHER
Use dispatchers when you need visitor-like patterns without the visitor boilerplate. They replace Java's instanceof chains, C#'s type switches, and the full Visitor pattern — with compile-time safety guarantees.

See Q96 for operators. See Q60 for function dispatching. See Q106 for traits. See Q107 for multiple traits. See Q211 for double dispatch pattern. See Q298 for sealed traits. See Q612-Q621 for dispatcher validation Q&As.

defines module qa.oop.dispatch

  defines trait

    <?-
      Sealed trait: only Circle, Square, Triangle can implement it.
      Dispatchers on Shape must handle ALL three types (E05260).
    -?>
    Shape allow only Circle, Square, Triangle
      area() as abstract
        <- rtn as Float?

  defines class

    Circle with trait of Shape
      override area()
        <- rtn as Float: 3.14
      default operator ?

    Square with trait of Shape
      override area()
        <- rtn as Float: 1.0
      default operator ?

    Triangle with trait of Shape
      override area()
        <- rtn as Float: 0.5
      default operator ?

    <?-
      Renderer dispatches on Shape.
      Because Shape is sealed, ALL permitted types must have handlers.
    -?>
    Renderer

      render() as dispatcher
        -> shape as Shape
        <- rtn as String: `unknown shape area: ${shape.area()}`

      render()
        -> circle as Circle
        <- rtn as String: `circle area: ${circle.area()}`

      render()
        -> square as Square
        <- rtn as String: `square area: ${square.area()}`

      render()
        -> triangle as Triangle
        <- rtn as String: `triangle area: ${triangle.area()}`

  defines program

    DispatchDemo()
      stdout <- Stdout()

      renderer <- Renderer()

      // === DISPATCHER: routes to most specific overload ===

      shapes <- List() of Shape
      shapes += Circle()
      shapes += Square()
      shapes += Triangle()

      for shape in shapes
        stdout.println(renderer.render(shape))

      // === Direct calls also work ===

      stdout.println(renderer.render(Circle()))
      stdout.println(renderer.render(Triangle()))

Java: Visitor pattern with accept/visit boilerplate, or instanceof chains. Python: functools.singledispatch for single argument, no multi-dispatch built-in. Rust: no runtime dispatch, pattern matching on enums instead. Go: type switch for runtime dispatch, single argument only. Kotlin: Visitor pattern or when-is chains, no built-in multi-dispatch. EK9: 'as dispatcher' keyword enables automatic runtime dispatch to most specific overload, supports multiple arguments for double dispatch, no visitor boilerplate.

allow dispatch dispatcher double exhaustive handler hierarchy method object-oriented only overload pattern polymorphism purity runtime sealed specific type visitor

Explanation: When implementing an abstract method from Shape in a subclass like Circle, the 'override' keyword is mandatory. Omitting it triggers E05120. See ek9 -h E05120 for details.

Explanation: A dispatcher handler's parameter type must be in the base dispatch type's hierarchy. An Integer handler in a Shape dispatcher can never be reached because Integer is not a subtype of Shape. This triggers E50060. See ek9 -h E50060 for details.

Explanation: Renderer is closed by default (not marked 'as open'). Attempting to extend a closed class triggers E05030. Only traits and classes marked 'as open' or 'as abstract' can be extended. See ek9 -h E05030 for details.

area() as abstract
        <- rtn as Float?

Explanation: Traits are stateless behaviour contracts — they cannot have properties or fields. The Shape trait should define abstract methods (like area()), not hold state. Move fields to implementing classes. See ek9 -h E07075 for details.


Question: What are traits and how do I use them in EK9?

What is the EK9 equivalent of Java interfaces?

How do traits differ from interfaces in EK9?

How do I define behaviour contracts in EK9?

Can traits have constructors or private methods in EK9?

Answer:
Traits define behaviour contracts with optional default implementations. Unlike abstract classes, traits cannot hold mutable state.

DEFINING A TRAIT
  defines trait
    Printable
      display()
        <- rtn as String?
Methods can have bodies (defaults) or be abstract.

IMPLEMENTING
Use 'with trait of':
  Product with trait of Printable, Describable
    override display()
      <- rtn as String: name

DEFAULT METHODS
Classes only need to override abstract methods or methods they want to customise.

TRAIT VS JAVA INTERFACE
EK9 traits support 'allow only' restriction and trait delegation beyond Java 8+ defaults.

See Q93 for class basics. See Q103 for abstract classes. See Q107 for multiple traits. See Q108 for implementing. See Q109 for composition. See Q110 for trait vs abstract. See Q264 for adapter pattern. See Q266 for delegation. See Q569 for pure methods. See Q576 for override resolution.

defines module qa.oop.traits

  defines trait

    Printable
      display()
        <- rtn as String?

    Describable
      describe()
        <- rtn as String: "No description available"

      details() as abstract
        <- rtn as String?

    <?-
      Formattable extends Printable — creates a trait hierarchy.
      Classes using Formattable get Printable indirectly.
    -?>
    Formattable extends Printable
      format() as abstract
        <- rtn as String?

  defines class

    Product with trait of Printable, Describable
      name <- String()
      price <- 0.0

      Product()
        ->
          name as String
          price as Float
        this.name: name
        this.price: price

      override display()
        <- rtn as String: name

      override details()
        <- rtn as String: `${name} costs ${price}`

      default operator ?

    <?-
      Report uses Formattable (which extends Printable).
      Printable is NOT an immediate trait of Report.
    -?>
    Report with trait of Formattable, Describable
      title <- String()

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

      override display()
        <- rtn as String: title

      override details()
        <- rtn as String: `Report: ${title}`

      override format()
        <- rtn as String: `[${title}]`

      showFormatted()
        <- rtn as String: String()

        //Formattable is immediate — this is valid
        rtn: Formattable.format()

      default operator ?

  defines function

    <?-
      Functions cannot access trait methods directly.
      Must use the object reference instead.
    -?>
    formatItem()
      -> item as Printable
      <- result as String: item.display()

  defines program

    TraitsDemo()
      stdout <- Stdout()

      // === IMPLEMENTING A TRAIT ===

      product <- Product("Widget", 9.99)
      stdout.println(`Display: ${product.display()}`)

      // === DEFAULT METHOD (from Describable) ===

      stdout.println(`Describe: ${product.describe()}`)

      // === ABSTRACT METHOD (must override) ===

      stdout.println(`Details: ${product.details()}`)

      // === TRAIT CHECK ===

      stdout.println(`IsSet: ${product?}`)

      // === TRAIT HIERARCHY (Formattable extends Printable) ===

      report <- Report("Q4 Sales")
      stdout.println(`Format: ${report.format()}`)
      stdout.println(`ShowFormatted: ${report.showFormatted()}`)

      // === FUNCTION WITH TRAIT PARAMETER ===

      stdout.println(`FormatItem: ${formatItem(product)}`)

Java: interfaces with defaults (8+). Python: ABCs/Protocol. Rust: traits with defaults. Go: implicit interfaces. Swift: protocols with extensions. EK9: 'with trait of', 'allow only' restriction, delegation with 'by'.

abstract behaviour contract default define implement interface method migrate object-oriented printable protocol swift trait

Explanation: When implementing an abstract trait method in a class, the 'override' keyword is required. Product must use 'override display()' to implement Printable's abstract method. See ek9 -h E05120 for details.

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

name <- "default"

      display()
        <- rtn as String?

Explanation: Traits are stateless behaviour contracts and cannot have properties or fields. State belongs in implementing classes. Use abstract accessor methods in the trait instead. See ek9 -h E07075 for details.

    Printable
      display()
        <- rtn as String?

    Printable
      Printable()
        -> name as String

      display()
        <- rtn as String?

Explanation: Traits cannot have constructors. They are stateless behaviour contracts with no instance creation logic. Construction belongs in implementing classes. See ek9 -h E07070 for details.

      details() as abstract
        <- rtn as String?

      private details() as abstract
        <- rtn as String?

Explanation: Trait methods cannot have access modifiers (private, protected). All trait methods are always public. This differs from Java interfaces where private methods are allowed since Java 9. See ek9 -h E07270 for details.

      format() as abstract
        <- rtn as String?

      format() as dispatcher
        -> item as Printable
        <- rtn as String?

Explanation: Dispatchers are only supported in classes, not traits. The 'as dispatcher' keyword requires a concrete class context for the runtime to route overloaded methods. See ek9 -h E07810 for details.

Explanation: When a class declares 'with trait of Formattable' and Formattable extends Printable, only Formattable is an immediate trait. Calling Printable.display() is rejected because Printable is indirect. This prevents tight coupling to the trait hierarchy. See ek9 -h E06160 for details.

<- result as String: Printable.display()

Explanation: Trait method access (TraitName.method()) is only supported inside classes or dynamic classes that have that trait. Functions and programs cannot call trait methods directly. Use the object reference instead. See ek9 -h E06170 for details.


Question: Can a class implement multiple traits in EK9?

How do I use multiple traits on one class in EK9?

What happens when two traits have the same method?

Answer:
Yes, in EK9, a class can implement multiple traits. When traits have conflicting method signatures, you must resolve the ambiguity by providing an explicit override.

MULTIPLE TRAIT IMPLEMENTATION
List traits separated by commas:
  Report with trait of Printable, Exportable

CONFLICT RESOLUTION
When two traits define the same method, override it in the class:
  override format()
    <- rtn as String: Printable.format()
You can choose which trait's default to call using 'TraitName.method()' syntax.

ALLOW ONLY RESTRICTIONS
Traits can restrict which classes may implement them:
  Response allow only JsonResponse, XmlResponse
Only the listed classes can use 'with trait of Response'.

AMBIGUOUS METHODS
The compiler detects when trait methods conflict and requires explicit resolution. This prevents silent bugs from accidental method shadowing.

See Q106 for trait basics. See Q108 for implementing traits step by step. See Q105 for dispatching with traits. See Q576 for class and trait override resolution.

defines module qa.oop.multipletraits

  defines trait

    Serializable
      toText()
        <- rtn as String: "serialized"

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

    Loggable
      toText()
        <- rtn as String: "logged"

      logLevel()
        <- rtn as String: "INFO"

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

  defines class

    Event with trait of Serializable, Loggable
      message <- String()

      Event()
        -> message as String
        this.message: message

      override toText()
        <- rtn as String: message

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

  defines program

    MultipleTraitsDemo()
      stdout <- Stdout()

      // === MULTIPLE TRAITS ===

      event <- Event("User logged in")

      // toText() resolved by explicit override
      stdout.println(`Text: ${event.toText()}`)

      // logLevel() from Loggable (no conflict)
      stdout.println(`Level: ${event.logLevel()}`)

      stdout.println(`IsSet: ${event?}`)

Java: class implements multiple interfaces, default method conflicts require override with InterfaceName.super.method(). Python: multiple inheritance with MRO (Method Resolution Order), diamond problem handled by C3 linearisation. Rust: multiple trait implementations, explicit disambiguation with <Type as Trait>::method(). Go: struct satisfies multiple interfaces implicitly, no conflict possible (no defaults). Kotlin: multiple interfaces, conflicts resolved with super<InterfaceName>.method(). EK9: multiple traits with 'with trait of A, B', conflicts resolved with 'TraitName.method()' syntax, 'allow only' restricts implementors.

abstract allow ambiguous class conflict diamond implement multiple object-oriented only open override resolve trait virtual

Explanation: When both Serializable and Loggable define toText(), the implementing class Event must explicitly override it to resolve the conflict. Omitting 'override' triggers E05120. See ek9 -h E05120 for details.

Explanation: The '?' operator is defined in both traits. Event must use 'override operator ?' to resolve the conflict and provide its own implementation. See ek9 -h E05120 for details.

Explanation: When two traits define the same method (toText() in both Serializable and Loggable), the implementing class must provide an explicit override to resolve the conflict. Without it, the compiler cannot determine which trait's default to use. See ek9 -h E06150 for details.


Question: How do I implement a trait for my class?

What is the syntax for implementing a trait in EK9?

How do I add trait methods to a class in EK9?

How do I override trait default methods?

Answer:
Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure).

In EK9, implement a trait using 'with trait of' on the class declaration. You must override all abstract methods from the trait. You can optionally override default methods.

SYNTAX
Use 'with trait of TraitName' after the class name:
  EmailSender with trait of Sender
    override send()
      -> message as String
      <- rtn as Boolean: true

IMPLEMENTING REQUIRED METHODS
Abstract trait methods must be overridden in the class:
  defines trait
    Sender
      send() as abstract
        -> message as String
        <- rtn as Boolean?
The class must provide 'override send()' with a body.

OVERRIDING DEFAULT METHODS
Methods with bodies in the trait are optional to override:
  defines trait
    Sender
      retry()
        <- rtn as Integer: 3
The class inherits retry() returning 3, or can override it.

TRAIT HIERARCHIES
Traits can extend other traits:
  ReliableSender with trait of Sender
    acknowledge() as abstract
      <- rtn as Boolean?
A class implementing ReliableSender must implement both send() and acknowledge().

See Q106 for trait basics. See Q107 for multiple traits. See Q93 for class basics. See Q115 for dynamic classes implementing traits.

defines module qa.oop.implementtrait

  defines trait

    Sender
      send() as abstract
        -> message as String
        <- rtn as Boolean?

      retry() as pure
        <- rtn as Integer: 3

    ReliableSender with trait of Sender
      acknowledge() as abstract
        <- rtn as Boolean?

  defines class

    EmailSender with trait of Sender
      override send()
        -> message as String
        <- rtn as Boolean: true

      default operator ?

    SmtpSender with trait of ReliableSender
      override send()
        -> message as String
        <- rtn as Boolean: true

      override acknowledge()
        <- rtn as Boolean: true

      override retry() as pure
        <- rtn as Integer: 5

      default operator ?

  defines program

    ImplementTraitDemo()
      stdout <- Stdout()

      // === BASIC TRAIT IMPLEMENTATION ===

      email <- EmailSender()
      stdout.println(`Send: ${email.send("Hello")}`)
      stdout.println(`Default retry: ${email.retry()}`)

      // === TRAIT HIERARCHY ===

      smtp <- SmtpSender()
      stdout.println(`Send: ${smtp.send("Hello")}`)
      stdout.println(`Acknowledge: ${smtp.acknowledge()}`)
      stdout.println(`Override retry: ${smtp.retry()}`)

Java: 'class MyClass implements MyInterface', must implement all abstract methods, @Override annotation optional but recommended. Python: class MyClass(ABC) with @abstractmethod, no enforcement until instantiation. Rust: 'impl Trait for Type' block with all required methods. Go: implicit implementation, no keyword needed, just match method signatures. Kotlin: 'class MyClass : MyInterface', override keyword required. EK9: 'ClassName with trait of TraitName', override keyword required for all overridden methods, compiler enforces abstract method implementation.

abstract class default hierarchy implement method object-oriented open override required sender syntax trait virtual

Explanation: When implementing an abstract trait method, the 'override' keyword is mandatory. EmailSender must use 'override send()' to implement Sender's abstract send method. See ek9 -h E05120 for details.

Explanation: When overriding a default trait method, the 'override' keyword is required. SmtpSender overrides Sender's default retry() so it must use 'override'. See ek9 -h E05120 for details.

//send not implemented

      override acknowledge()

Explanation: SmtpSender implements ReliableSender which extends Sender. The abstract send() from Sender must be overridden. Omitting it triggers E07130 — not declared abstract but still has abstract methods. See ek9 -h E07130 for details.

override snd()
        -> message as String
        <- rtn as Boolean: true

Explanation: The 'override' keyword claims a method exists in the hierarchy, but 'snd' does not match any method in Sender. This catches typos and refactoring errors — if a trait method is renamed, all implementors are flagged. See ek9 -h E05110 for details.

Oracle can generate required method stubs when implementing a trait for a class.


Question: How do I use composition instead of inheritance in EK9?

What is the 'by' keyword for trait delegation in EK9?

How do I avoid inheritance with composition in EK9?

Answer:
EK9 supports composition over inheritance through trait delegation with the 'by' keyword. A class delegates trait methods to a field, avoiding tight inheritance coupling.

WHY COMPOSITION
Inheritance creates tight coupling between parent and child. Composition lets you combine behaviours flexibly through delegation. EK9's closed-by-default types encourage this approach.

DELEGATION WITH BY KEYWORD
Delegate trait methods to a field:
  Worker with trait of Task by delegate
    delegate as Task?
    Worker()
      -> delegate as Task
      this.delegate: delegate
All Task methods are automatically forwarded to the delegate field.

TRAIT DELEGATION PATTERN
Combine multiple trait delegations:
  Employee with trait of Role by role, Payroll by payroll
    role as Role?
    payroll as Payroll?

COMPOSITION VS INHERITANCE DECISION
Use composition when: you need to combine multiple behaviours, swap implementations at runtime, or the relationship is 'has-a' not 'is-a'.
Use inheritance when: there is a genuine 'is-a' relationship and you control the base class.

See Q101 for closed by default. See Q106 for traits. See Q108 for implementing traits. See Q128 for applying composition to wrap closed collection types. See Q210 for trait delegation pattern. See Q212 for composition over inheritance. See Q264 for adapter pattern using composition. See Q315 for inheritance depth limits.

defines module qa.oop.composition

  defines trait

    Logger
      log()
        -> message as String
      override operator ? as pure
        <- rtn as Boolean: true

    Formatter
      format() as pure
        -> content as String
        <- rtn as String?
      override operator ? as pure
        <- rtn as Boolean: true

  defines class

    ConsoleLogger with trait of Logger
      stdout as Stdout: Stdout()

      override log()
        -> message as String
        stdout.println(message)

      default operator ?

    BracketFormatter with trait of Formatter
      override format() as pure
        -> content as String
        <- rtn as String: `[${content}]`

      default operator ?

    Service with trait of Logger by logger, Formatter by formatter
      logger as Logger?
      formatter as Formatter?

      default private Service()

      Service()
        ->
          logger as Logger
          formatter as Formatter
        this.logger: logger
        this.formatter: formatter

      process()
        -> content as String
        formatted <- format(content)
        log(formatted)

      override operator ? as pure
        <- rtn as Boolean: logger? and formatter?

  defines program

    CompositionDemo()
      stdout <- Stdout()

      // === COMPOSITION VIA DELEGATION ===

      logger <- ConsoleLogger()
      formatter <- BracketFormatter()

      service <- Service(logger, formatter)
      service.process("Hello composition")
      service.process("Delegation works")

      // === DELEGATED METHODS ALSO CALLABLE DIRECTLY ===

      service.log("Direct log call")
      stdout.println(service.format("Direct format"))

Java: no delegation syntax, must manually write forwarding methods or use IDE generation. Python: no delegation syntax, use __getattr__ for dynamic delegation. Rust: no delegation, manual forwarding or Deref trait abuse. Go: struct embedding provides automatic method forwarding, closest to EK9 delegation. Kotlin: 'by' keyword for interface delegation, identical concept to EK9. EK9: 'with trait of T by field' delegates all trait methods to the field automatically, combinable with multiple traits.

by combine composition decouple delegation field flexible forward inheritance migrate object-oriented trait

composition
composition over inheritance
delegation

Explanation: When implementing an abstract or default trait method in a concrete class, the 'override' keyword is required. ConsoleLogger must use 'override log()' to implement Logger's log method. See ek9 -h E05120 for details.

Explanation: ConsoleLogger is closed by default (no 'as open'). Use trait delegation with 'by' instead of inheritance to compose behaviours. See ek9 -h E05030 for details.

    CompositeTrait with trait of Logger by logger
      logger as Logger?

Explanation: Changing class name to 'CompositeTrait' makes the existing constructor named 'Service()' no longer match the class name. It becomes a regular method, and 'default' modifier on a regular method triggers E07090 — 'default' is only valid for constructors. See ek9 -h E07090 for details.


Question: What is the difference between a trait and an abstract class?

When should I use a trait vs an abstract class in EK9?

How do traits compare to abstract classes in EK9?

Should I use a trait or an abstract base class?

Answer:
In EK9, traits and abstract classes both define contracts, but differ in state, multiplicity, and purpose. Traits provide pure behaviour. Abstract classes provide shared state and partial implementation.

KEY DIFFERENCES
State: Abstract classes can hold properties. Traits cannot hold mutable state.
Multiple: A class can implement many traits but extend only one abstract class.
Purpose: Traits define what a type CAN DO. Abstract classes define what a type IS.

WHEN TO USE EACH
Use a trait when: you want behaviour shared across unrelated classes, you need multiple implementations, or you want delegation support.
Use an abstract class when: you need shared state (properties), you need constructors, or you have a genuine type hierarchy.

CAN YOU COMBINE THEM
Yes. A class can extend an abstract class AND implement traits:
  Car extends Vehicle with trait of Insurable, Trackable
This combines the state from Vehicle with behaviours from Insurable and Trackable.

DECISION GUIDE
Ask: does the contract need state? If yes, abstract class. Does the contract need to be mixed into multiple unrelated types? If yes, trait.

See Q103 for abstract classes. See Q106 for traits. See Q119 for constructs overview.

defines module qa.oop.traitvsabstract

  defines trait

    Insurable
      premium() as pure
        <- rtn as Float: 100.0

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

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

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

  defines class

    Vehicle as abstract
      make <- String()
      year <- Integer()

      Vehicle()
        ->
          make as String
          year as Integer
        this.make: make
        this.year: year

      describe()
        <- rtn as String: `${make} (${year})`

      speed() as abstract
        <- rtn as Float?

      default operator ?

    Car extends Vehicle with trait of Insurable, Trackable
      topSpeed <- 0.0

      Car()
        ->
          make as String
          year as Integer
          topSpeed as Float
        super(make, year)
        this.topSpeed: topSpeed

      override speed()
        <- rtn as Float: topSpeed

      override premium() as pure
        <- rtn as Float: 200.0

      override location()
        <- rtn as String: "garage"

      default operator ?

    Truck extends Vehicle
      payload <- 0.0

      Truck()
        ->
          make as String
          year as Integer
          payload as Float
        super(make, year)
        this.payload: payload

      override speed()
        <- rtn as Float: 80.0

      default operator ?

  defines program

    TraitVsAbstractDemo()
      stdout <- Stdout()

      // === ABSTRACT CLASS: shared state ===

      car <- Car("Toyota", 2024, 130.0)
      stdout.println(`Car: ${car.describe()}, speed: ${car.speed()}`)

      // === TRAIT: behaviour contract ===

      stdout.println(`Premium: ${car.premium()}`)
      stdout.println(`Location: ${car.location()}`)

      // === Abstract without trait ===

      truck <- Truck("Ford", 2023, 5000.0)
      stdout.println(`Truck: ${truck.describe()}, speed: ${truck.speed()}`)

Java: interfaces (no state) vs abstract classes (state + partial impl), single inheritance for classes. Python: ABCs for abstract, mixins for trait-like behaviour, multiple inheritance allowed. Rust: only traits exist, no abstract classes, traits can have default methods. Go: only interfaces, no abstract classes, no state in interfaces. Kotlin: interfaces with properties (no backing fields) vs abstract classes, similar trade-offs. EK9: traits for behaviour contracts (no mutable state), abstract classes for shared state and hierarchy, combinable on same class.

abstract behaviour class combine decision difference hierarchy multiple object-oriented oop state trait

Explanation: When implementing an abstract method from Vehicle, both Car and Truck must use 'override speed()'. Omitting 'override' triggers E05120. See ek9 -h E05120 for details.

Explanation: When overriding a default trait method from Insurable, the 'override' keyword is mandatory. Car must use 'override premium()' to provide its custom implementation. See ek9 -h E05120 for details.

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

Explanation: The method is speed(), not getSpeed(). EK9 uses descriptive method names without Java-style 'get' prefix. See ek9 -h E50060 for details.


Question: What are components in EK9?

How does dependency injection work in EK9?

Answer:
Components in EK9 are constructs designed for dependency injection (DI). They are registered in application definitions and injected into programs and other components using the '!' suffix.

COMPONENT BASICS
Define a component under 'defines component':
  defines component
    Repository as abstract
      findAll() as abstract
        <- rtn as List of String?
      default operator ?

ABSTRACT COMPONENTS
Define abstract components as contracts. Concrete implementations extend them:
  InMemoryRepository extends Repository
    override findAll()
      <- rtn as List of String: ["item1", "item2"]

INJECTION
Use the '!' suffix to inject a registered component:
  repository as Repository!
The runtime resolves which concrete implementation was registered.

APPLICATION REGISTRATION
Register components in an application definition:
  defines application
    MyApp
      register InMemoryRepository() as Repository

PROGRAM WITH APPLICATION
Link a program to an application for injection:
  Program1 with application of MyApp
    repository as Repository!
    items <- repository.findAll()

COMPONENT VS CLASS
Components are for DI-managed singletons and services. Classes are for regular objects created directly.

See Q93 for class basics. See Q103 for abstract classes. See Q112 for services. See Q113 for text constructs. See Q114 for aspects. See Q119 for constructs overview. See Q227 for compile-time DI validation. See Q228 for registration ordering. See Q231 for program-application linking.

See Q324 for @Autowired equivalent. See Q325 for @Component equivalent.

See Q667 for abstract injection only. See Q668 for injectable contexts. See Q672 for program-application link.

defines module qa.oop.components

  defines component

    Repository as abstract
      findAll() as abstract
        <- rtn as List of String?

      default operator ?

    InMemoryRepository extends Repository
      override findAll()
        <- rtn <- List() of String
        rtn += "Alice"
        rtn += "Bob"
        rtn += "Charlie"

      default operator ?

  defines application

    MyApp
      register InMemoryRepository() as Repository

  defines program

    ComponentDemo() with application of MyApp
      stdout <- Stdout()

      // === INJECTION with ! suffix ===

      repository as Repository!

      // === USE INJECTED COMPONENT ===

      items <- repository.findAll()
      for item in items
        stdout.println(`Item: ${item}`)

      stdout.println(`Total: ${length items}`)

Java: Spring @Component/@Service with @Autowired injection, CDI @Inject. Python: no built-in DI, frameworks like dependency-injector or FastAPI Depends. Rust: no built-in DI, manual wiring or shaku crate. Go: no built-in DI, wire or dig packages. Kotlin: Koin or Dagger for DI, no language-level support. EK9: 'defines component' is a language-level construct, 'register X as Y' in application, '!' suffix for injection, compile-time validated DI.

abstract application component dependency inject injection inversion ioc object-oriented register service

component
dependency injection component

Explanation: When implementing an abstract method from the parent component, the 'override' keyword is mandatory. InMemoryRepository must use 'override findAll()'. See ek9 -h E05120 for details.

Explanation: Without 'as abstract', Repository contains abstract method 'findAll()' but the component itself is not marked abstract. The compiler reports E50040 — cannot be abstract in a non-abstract construct. See ek9 -h E50040 for details.

Explanation: Component methods cannot use the 'protected' access modifier. Components are DI-managed singletons without subclass hierarchies, so 'protected' is meaningless. Use 'private' or 'public' instead. See ek9 -h E07250 for details.

Explanation: Only abstract components can be injected. Injecting a concrete component directly bypasses the DI contract — always inject the abstract type and register the concrete implementation in the application. See ek9 -h E08150 for details.

Explanation: Dependency injection with '!' only works with components. DataProcessor is a class, not a component — classes are constructed directly, not injected. Use 'defines component' for injectable types. See ek9 -h E50010 for details.

Explanation: Changing 'defines component' to 'defines class' makes Repository a class instead of a component. The register statement requires component types, so the compiler rejects it with E50020 — incompatible genus. Components and classes are distinct constructs in EK9. See ek9 -h E50020 for details.

Explanation: Repository is declared 'as abstract' and cannot be instantiated directly. The register statement must use a concrete implementation (InMemoryRepository), not the abstract base type. See ek9 -h E50080 for details.


Question: How do services work in EK9 for REST and HTTP?

How does EK9 map HTTP methods to operators?

Answer:
EK9 has a built-in service construct for REST/HTTP endpoints. Services map HTTP methods to operators and named methods, with URI binding and parameter extraction built into the language.

SERVICE CONSTRUCT
Define a service with its URI:
  defines service
    Items :/items

URI MAPPING
The service name maps to a base URI path. Methods map to sub-paths:
  listAll() :/             maps to GET /items
  byId() as GET for :/{id}  maps to GET /items/{id}

HTTP METHOD MAPPING
EK9 maps operators to HTTP verbs semantically:
  operator += :/           POST (add to collection)
  operator -= :/{id}       DELETE (remove from collection)
  operator :~: :/{id}      PATCH (merge/partial update)
  operator :^: :/{id}      PUT (replace)
  Named methods default to GET.

PATH AND CONTENT BINDING
  -> id as String                         PATH parameter (assumed)
  -> content as String :=: CONTENT        request body
  -> request as HTTPRequest :=: REQUEST   full request object

HTTPRESPONSE TRAIT
Service methods return HTTPResponse. Use dynamic classes to implement it inline with trait delegation.

APPLICATION REGISTRATION
Register the service in an application:
  register Items()

See Q111 for components. See Q106 for traits. See Q96 for operator semantics. See Q114 for aspects. See Q199 for REST GET endpoints. See Q200 for CRUD operator mapping. See Q201 for building HTTPResponse. See Q203 for application wiring.

See Q325 for @Component/@Service equivalent.

defines module qa.oop.services

  defines text for "en"

    ServiceContent
      welcome()
        "Welcome to the EK9 Service"

  defines service

    Info :/info open

      welcome() as GET for :/welcome
        <- response as HTTPResponse: () with trait HTTPResponse
          content <- ServiceContent("en")

          override cacheControl() as pure
            <- rtn as String: "no-store"
          override contentType() as pure
            <- rtn as String: "text/plain"
          override contentLanguage() as pure
            <- rtn as String: "en"
          override content()
            <- rtn as String: content.welcome()
          override status() as pure
            <- rtn as Integer: 200
          default operator ?

  defines application

    ServiceApp
      register Info()

  defines program

    ServiceDemo()
      stdout <- Stdout()

      // === SERVICE CONSTRUCT ===
      // Services are defined with 'defines service'
      // and registered in applications

      stdout.println("Service registered in application")
      stdout.println("HTTP methods map to operators:")
      stdout.println("  += is POST, -= is DELETE")
      stdout.println("  :~: is PATCH, :^: is PUT")
      stdout.println("  Named methods default to GET")

Java: JAX-RS @GET/@POST annotations or Spring @RestController with @RequestMapping, annotation-driven. Python: Flask/Django with @app.route decorators, framework-dependent. Rust: actix-web or axum with handler functions and extractors. Go: net/http handlers with manual routing or gorilla/mux. Kotlin: Ktor routing DSL or Spring Boot annotations. EK9: language-level 'defines service' with operator-to-HTTP mapping, URI binding in declaration, parameter binding with PATH/CONTENT/REQUEST keywords.

delete endpoint get http object-oriented operator patch post put rest service uri

service
REST service
HTTP service
web service

Explanation: When implementing HTTPResponse trait methods in a dynamic class, the 'override' keyword is required for each method. Omitting it triggers E05120. See ek9 -h E05120 for details.

override getStatus() as pure
            <- rtn as Integer: 200

Explanation: The HTTPResponse trait method is 'status()', not 'getStatus()'. Using 'override' with a non-existent method name triggers E05110 — false override claim. EK9 does not use Java-style getter naming. See ek9 -h E05110 for details.

Explanation: EK9 does not have a toString() method. Use the $ operator or call the appropriate method directly. See ek9 -h E50060 for details.


Question: How does the text construct work for internationalization?

How does EK9 handle multiple languages and locales?

Answer:
EK9 has a built-in 'text' construct for internationalization. You define text components per locale, and the compiler validates that all locales have the same methods.

TEXT CONSTRUCT
Define text for a specific locale:
  defines text for "en"
    Greetings
      hello
        "Hello!"
      welcomeUser
        -> name as String
        `Welcome, ${name}!`

TEXT METHODS
Methods can be simple (no parameters) or parameterised with typed arguments:
  farewell
    -> name as String
    `Goodbye, ${name}!`
Parameters are interpolated with '${variable}' syntax.

MULTIPLE LOCALES
Define the same text component for each locale:
  defines text for "fr"
    Greetings
      hello
        "Bonjour!"
      welcomeUser
        -> name as String
        `Bienvenue, ${name}!`

COMPILER VALIDATION
The compiler checks that all locales define exactly the same methods with the same parameter types. Missing or mismatched methods are compile-time errors.

RUNTIME LOCALE SELECTION
Create the text component with a locale string:
  greetings <- Greetings("en")
  greetings.hello()

See Q37 for strings. See Q43 for escape and interpolation. See Q111 for components. See Q119 for constructs overview.

defines module qa.oop.textconstruct

  defines text for "en"

    Greetings
      hello
        "Hello!"
      welcomeUser
        -> name as String
        `Welcome, ${name}!`
      farewell
        -> name as String
        `Goodbye, ${name}!`

  defines text for "fr"

    Greetings
      hello
        "Bonjour!"
      welcomeUser
        -> name as String
        `Bienvenue, ${name}!`
      farewell
        -> name as String
        `Au revoir, ${name}!`

  defines program

    TextDemo()
      stdout <- Stdout()

      // === ENGLISH TEXT ===

      testName <- "Steve"

      en <- Greetings("en")
      stdout.println(en.hello())
      stdout.println(en.welcomeUser(testName))
      stdout.println(en.farewell(testName))

      // === FRENCH TEXT ===

      fr <- Greetings("fr")
      stdout.println(fr.hello())
      stdout.println(fr.welcomeUser(testName))
      stdout.println(fr.farewell(testName))

Java: ResourceBundle with .properties files, MessageFormat for parameters, no compile-time validation across locales. Python: gettext with .po files, string formatting, no compile-time locale validation. Rust: fluent-rs or gettext-rs crates, external file based. Go: go-i18n package, JSON/TOML message files, no compile-time cross-locale validation. Kotlin: same as Java ResourceBundle. EK9: language-level 'defines text for locale' construct, typed parameters with interpolation, compiler validates all locales have matching method signatures.

compiler constant construct i18n internationalization interpolation language locale object-oriented text translation validation

bonjour
        -> name as String
        `Bonjour encore, ${name}!`

Explanation: All locale variants of a text component must define exactly the same methods. Renaming 'farewell' to 'bonjour' in the fr variant means fr no longer has a 'farewell' method matching en, triggering E07150. See ek9 -h E07150 for details.

Explanation: Each text type name must appear only once per locale. Changing fr to en creates a second 'Greetings' definition for locale en, triggering E07155 — duplicate text type for locale. See ek9 -h E07155 for details.

Explanation: Language codes must follow the pattern [a-z]+(_[A-Z]+)?. Uppercase base 'EN' should be lowercase 'en'. Use 'en_GB' not 'en-GB' or 'en_gb'. See ek9 -h E07900 for details.


Question: How do aspects work with components for AOP?

How does aspect-oriented programming work in EK9?

How do I add logging or timing to components in EK9?

Answer:
EK9 supports Aspect-Oriented Programming through the Aspect base class and application registration. Aspects wrap component calls with before and after advice.

ASPECT CLASS
Create an aspect by extending the built-in Aspect class:
  LoggingAspect extends Aspect
    override beforeAdvice()
      -> joinPoint as JoinPoint
      <- rtn as PreparedMetaData: PreparedMetaData(joinPoint)
    override afterAdvice()
      -> preparedMetaData as PreparedMetaData

BEFORE AND AFTER ADVICE
beforeAdvice() runs before each method call. afterAdvice() runs after. The JoinPoint provides the component name and method name.

JOINPOINT
The JoinPoint parameter gives execution context:
  joinPoint.componentName()   the component being called
  joinPoint.methodName()      the method being called

REGISTERING WITH ASPECTS
Use 'with aspect of' in application registration:
  register Solution1() as Config with aspect of LoggingAspect(), TimerAspect()
Multiple aspects chain in declaration order.

CHAINING MULTIPLE ASPECTS
Multiple aspects wrap in order: the first listed runs outermost. Each aspect's beforeAdvice runs before the next, and afterAdvice runs in reverse order.

See Q111 for components. See Q112 for services. See Q102 for 'as open'. See Q119 for constructs overview. See Q227 for compile-time DI validation.

See Q337 for transaction aspects.

defines module qa.oop.aspects

  defines component

    Config as abstract
      getValue() as abstract
        <- rtn as String?
      default operator ?

    ProductionConfig extends Config
      override getValue()
        <- rtn as String: "production-database"
      default operator ?

  defines class

    SimpleAspect extends Aspect
      label <- String()

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

      override beforeAdvice()
        -> joinPoint as JoinPoint
        <- rtn as PreparedMetaData: PreparedMetaData(joinPoint)
        Stdout().println(`${label} BEFORE: ${joinPoint.componentName()}.${joinPoint.methodName()}`)

      override afterAdvice()
        -> preparedMetaData as PreparedMetaData
        joinPoint <- preparedMetaData.joinPoint()
        Stdout().println(`${label} AFTER: ${joinPoint.componentName()}.${joinPoint.methodName()}`)

      default operator ?

  defines application

    AspectApp
      register ProductionConfig() as Config with aspect of SimpleAspect("LOG")

  defines program

    AspectsDemo() with application of AspectApp
      stdout <- Stdout()

      // === INJECTION with aspects applied ===

      config as Config!
      stdout.println(`Value: ${config.getValue()}`)

Java: Spring AOP with @Before/@After annotations and proxy-based weaving, AspectJ for compile-time weaving. Python: decorators for simple AOP, no JoinPoint concept built-in. Rust: no built-in AOP, procedural macros for compile-time code generation. Go: no built-in AOP, middleware pattern for HTTP, manual wrapping. Kotlin: Spring AOP same as Java, no language-level AOP. EK9: language-level Aspect base class, beforeAdvice/afterAdvice methods, JoinPoint for context, 'register with aspect of' syntax in application definitions.

advice after aop aspect before component cross-cutting joinpoint logging object-oriented register timing weave

Explanation: When implementing Aspect's abstract methods beforeAdvice() and afterAdvice(), the 'override' keyword is mandatory. Omitting it triggers E05120. See ek9 -h E05120 for details.

Explanation: ProductionConfig must use 'override' when implementing Config's abstract method getValue(). See ek9 -h E05120 for details.

register ProductionConfig() as Config with aspect of NonExistentAspect()

Explanation: The aspect type used in 'with aspect of' must be a defined type. If the type does not exist, E50001 is triggered — not resolved. Check spelling and ensure the aspect class is defined. See ek9 -h E50001 for details.

Explanation: Aspect is a built-in class, not a component. Defining an aspect subclass inside 'defines component' triggers E50020 because components cannot extend classes — incompatible genus. Aspect subclasses belong in 'defines class'. See ek9 -h E50020 for details.

Explanation: Config is declared 'as abstract' and cannot be instantiated directly. The register statement must use a concrete implementation (ProductionConfig), not the abstract base type. See ek9 -h E50080 for details.


Question: How do dynamic classes work in EK9?

How do I create anonymous classes in EK9?

What are inline trait implementations in EK9?

How do I capture variables in a dynamic class?

How do I implement a trait inline without a named class?

Answer:
Dynamic classes in EK9 are anonymous inline classes that implement traits. They capture variables from the enclosing scope, similar to dynamic functions but for trait implementations.

DYNAMIC CLASS BASICS
Create an anonymous class inline using '() with trait of':
  response <- () with trait of HTTPResponse
    override content() as pure
      <- rtn as String: "hello"
    default operator ?

CAPTURE VARIABLES
Capture values from the enclosing scope. Any type can be captured — String, Boolean, Integer, or any other type:
  handler <- (message: msg) with trait of Handler
    override handle()
      <- rtn as String: message
    default operator ?

MULTIPLE CAPTURES
Capture multiple variables of different types in a single dynamic class:
  logger <- (logging: enableLog, prefix: tag) with trait of Handler
    override handle()
      <- rtn as String: `[${prefix}] logging=${logging}`
    default operator ?

CAPTURE FIXED VALUES
Literal values and function returns must use named captures:
  configured <- (maxRetries: 3, label: "RETRY") with trait of Handler
Simple variable identifiers can be unnamed, but expressions must be named.

IMPLEMENTING TRAITS INLINE
Dynamic classes can implement any trait. Override abstract methods and provide a default operator. The trait delegation pattern also works:
  (delegate: baseHandler) with trait of Handler by delegate

WHEN TO USE DYNAMIC CLASSES
Use dynamic classes for one-off trait implementations, especially in service responses, callbacks, and strategy patterns where a full named class would be excessive.

See Q52 for dynamic functions. See Q106 for traits. See Q108 for implementing traits. See Q112 for services using dynamic classes. See Q233 for dynamic class dependency injection. See Q262 for observer pattern with dynamic function listeners. See Q263 for factory pattern with dynamic class products.

defines module qa.oop.dynamicclasses

  defines trait

    Handler
      handle() as abstract
        <- rtn as String?

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

    Transformer
      transform() as abstract
        -> input as String
        <- rtn as String?

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

  defines class

    DefaultHandler with trait of Handler
      override handle()
        <- rtn as String: "default"
      default operator ?

  defines function

    getPrefix()
      <- rtn as String: "AUTO"

  defines program

    DynamicClassDemo()
      stdout <- Stdout()

      // === DYNAMIC CLASS: anonymous trait implementation ===

      greeting <- () with trait of Handler
        override handle()
          <- rtn as String: "Hello from dynamic class"
        default operator ?

      stdout.println(greeting.handle())

      // === CAPTURE: single String variable ===

      msg <- "Captured message"
      captured <- (message: msg) with trait of Handler
        override handle()
          <- rtn as String: message
        default operator ?

      stdout.println(captured.handle())

      // === CAPTURE: single Boolean variable ===

      verbose <- true
      detailed <- (showDetail: verbose) with trait of Handler
        override handle()
          <- rtn as String: `Detail: ${showDetail}`
        default operator ?

      stdout.println(detailed.handle())

      // === CAPTURE: multiple variables (Boolean + String) ===

      enableLog <- true
      logTag <- "LOG"
      logger <- (logging: enableLog, prefix: logTag) with trait of Handler
        override handle()
          <- rtn as String: `[${prefix}] logging=${logging}`
        default operator ?

      stdout.println(logger.handle())

      // === CAPTURE: fixed literal values ===

      configured <- (maxRetries: 3, label: "RETRY") with trait of Handler
        override handle()
          <- rtn as String: `${label}: max ${maxRetries}`
        default operator ?

      stdout.println(configured.handle())

      // === CAPTURE: function return value ===

      autoPrefix <- getPrefix()
      auto <- (prefix: autoPrefix) with trait of Handler
        override handle()
          <- rtn as String: `[${prefix}] auto-configured`
        default operator ?

      stdout.println(auto.handle())

      // === CAPTURE: Boolean with Transformer trait ===

      shouldUpper <- true
      transformer <- (upper: shouldUpper) with trait of Transformer
        override transform()
          -> input as String
          <- rtn as String: `upper=${upper} input=${input}`
        default operator ?

      stdout.println(transformer.transform("hello"))

      // === DELEGATION ===

      base <- DefaultHandler()
      delegated <- (delegate: base) with trait of Handler by delegate

      stdout.println(delegated.handle())

Java: anonymous inner classes with 'new Interface() { ... }', captures effectively final variables from enclosing scope. Python: no anonymous classes, use lambdas or nested classes, closures capture by reference. Rust: closures implement Fn traits with move semantics, no anonymous struct implementations. Go: no anonymous interface implementations, closures capture variables. Kotlin: object expressions with 'object : Interface { ... }', captures from enclosing scope automatically. EK9: '() with trait of T' creates inline class with explicit named captures (name: value), supports any type including Boolean and Integer, delegation support via 'by', concise syntax for one-off implementations.

anonymous callback capture class closure delegation dynamic implement inline object-oriented scope trait

Explanation: Dynamic classes implementing a trait must use 'override' for abstract trait methods. The inline implementation of Handler's handle() method requires 'override'. See ek9 -h E05120 for details.

Explanation: A dynamic class implementing Handler must override all abstract methods. Omitting handle() leaves the abstract method unimplemented, triggering E07140. See ek9 -h E07140 for details.

badMethod() as abstract
          <- rtn as String?

Explanation: Dynamic classes are concrete inline implementations — they cannot contain abstract methods. Declaring a method 'as abstract' inside a dynamic class triggers E50040. See ek9 -h E50040 for details.

Explanation: Dynamic classes use 'with trait of' to implement traits. DefaultHandler is a class, not a trait — using it triggers E50020 because the genus is incompatible. Only traits can be used with dynamic class syntax. See ek9 -h E50020 for details.

(logging: enableLog, logTag) with trait of Handler

Explanation: When capturing variables in a dynamic class, either all captures must be named or all must be unnamed. Mixing named captures (logging: enableLog) with unnamed captures (logTag) triggers E06240. See ek9 -h E06240 for details.

Explanation: Literal values and expressions must use named captures. Unnamed captures only work for simple variable identifiers. Capturing literal '3' or '"RETRY"' without naming triggers E06230. See ek9 -h E06230 for details.

(logging: enableLog, logging: logTag) with trait of Handler

Explanation: Each capture field name must be unique within a dynamic class. Using 'logging' as the name for two different captures creates duplicate fields, triggering E02040. See ek9 -h E02040 for details.


Question: What does 'default operator' generate?

Which operators does 'default operator' create automatically?

How does auto-generated operator work in EK9?

What is the difference between 'default operator' and custom operators?

Answer:
In EK9, the 'default operator' keyword generates standard operators automatically based on a type's fields. It provides equality, comparison, string conversion, hashing, and isSet checking.

WHAT IT GENERATES
The following operators are auto-generated:
  ==   equality (field-by-field comparison)
  <>   inequality (negation of ==)
  <=>  comparison (returns Integer, field-by-field ordering)
  $    string conversion (concatenation of field strings)
  #?   hashcode (combined hash of all fields)
  ?    isSet (true when all fields are set)

HOW IT WORKS
For each field in declaration order, the generated operator compares, concatenates, or hashes. Comparison uses the first field as primary sort, second as secondary, etc.

WHEN TO USE IT
Use 'default operator' when field-by-field behaviour is correct. This covers most value types, records, and simple classes.

WHEN NOT TO USE IT
Override specific operators when you need custom logic: computed equality, selective comparison, custom string formatting, or partial isSet checking.

RECORD VS CLASS DEFAULTS
Both records and classes support 'default operator'. The generated behaviour is identical, but records expose fields publicly while classes keep them private.

See Q96 for custom operators. See Q93 for class basics. See Q97 for records. See Q98 for record operators. See Q99 for enumeration auto-operators. See Q238 for the complete fixed operator set. See Q242 for conversion operators. See Q245 for implementing a complete custom type.

defines module qa.oop.defaultoperator

  defines class

    Colour
      red <- Integer()
      green <- Integer()
      blue <- Integer()

      Colour()
        ->
          red as Integer
          green as Integer
          blue as Integer
        this.red: red
        this.green: green
        this.blue: blue

      default operator

    //Demonstrates individual default operator
    Weight
      grams <- Float()

      Weight()
        -> grams as Float
        this.grams: grams

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

  defines trait

    Displayable
      display() as pure abstract
        <- rtn as String?

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

  defines record

    Address
      street <- String()
      city <- String()

      Address()
        ->
          street as String
          city as String
        this.street: street
        this.city: city

      default operator

  defines program

    DefaultOperatorDemo()
      stdout <- Stdout()

      // === EQUALITY (==) and INEQUALITY (<>) ===

      red1 <- Colour(255, 0, 0)
      red2 <- Colour(255, 0, 0)
      blue <- Colour(0, 0, 255)

      stdout.println(`Equal: ${red1 == red2}`)
      stdout.println(`Not equal: ${red1 <> blue}`)

      // === COMPARISON (<=>) ===

      stdout.println(`Compare: ${red1 <=> blue}`)

      // === STRING ($) ===

      stdout.println(`String: ${red1}`)

      // === ISSET (?) ===

      stdout.println(`IsSet: ${red1?}`)

      // === RECORD with default operator ===

      addr1 <- Address("Main St", "Springfield")
      addr2 <- Address("Main St", "Springfield")
      stdout.println(`Address equal: ${addr1 == addr2}`)
      stdout.println(`Address: ${addr1}`)

Java: must manually write equals(), hashCode(), toString(), compareTo(). Lombok @Data generates them. Records (Java 16) auto-generate equals/hashCode/toString. Python: @dataclass generates __eq__, __repr__, __hash__. Rust: #[derive(Eq, Hash, Ord, Debug)] generates trait implementations. Go: no auto-generation, must write comparison functions manually. Kotlin: data class generates equals(), hashCode(), toString(), copy(). EK9: 'default operator' generates ==, <>, <=>, $, #?, ? from all fields in declaration order.

automatic comparison default equality field generate hashcode isset object-oriented operator string

Explanation: Class fields must be initialised either with a default value or through constructor assignment. Using 'Integer()' provides a default unset value. Declaring 'red as Integer' without '?' or a value may trigger E08180 if no constructor sets it. See ek9 -h E08180 for details.

Explanation: EK9 does not have an 'equals()' method. Use the == operator directly for equality comparison. See ek9 -h E50060 for details.

Explanation: EK9 does not have a compareTo() method. Use the <=> operator directly for comparison. The 'default operator' auto-generates <=> for you. See ek9 -h E50060 for details.

Explanation: EK9 does not have a toString() method. Use the $ operator or string interpolation. The 'default operator' auto-generates $ for string conversion. See ek9 -h E50060 for details.

Explanation: Not all operators support 'default' auto-generation. Operators like 'contains' have no meaningful field-by-field generation semantics. Only ==, <>, <=>, $, #?, and ? can be auto-generated. See ek9 -h E07220 for details.

Explanation: Traits cannot have 'default operator' because traits define contracts, not data-based implementations. Auto-generation needs concrete fields which traits do not have. Provide explicit operator implementations in traits instead. See ek9 -h E07030 for details.

Explanation: Any aggregate with fields must define operator ? for EK9's tri-state semantics. Without it, guard expressions and safe access patterns cannot inspect field state. Use 'default operator ?' or implement manually. See ek9 -h E07235 for details.