Why does AI try to extend List, Dict, or closed types in EK9?

← What AI Gets Wrong About EK9 · Ref: Q278

AI models trained on Java generate 'class MyList extends ArrayList' or 'class TypedMap extends HashMap' patterns. In EK9, types are CLOSED by default. Built-in collection types (List, Dict, Optional, Result, PriorityQueue) cannot be extended. Attempting to extend them produces error E05030.

THE AI MISTAKE

AI generates 'MyList extends List of String' or 'SafeDict extends Dict of (String, Integer)'. This fails because List and Dict are closed types. The AI is importing the Java pattern where ArrayList and HashMap are open.

THE EK9 WAY: COMPOSITION

Wrap the collection as a private field and expose only the operations you need:

  TaskQueue
    items as List of String
    addTask()
      -> taskName as String
      items += taskName
    pending() as pure
      <- count as Integer: length items

The List is hidden. Clients interact with TaskQueue, not List.

WHY CLOSED BY DEFAULT

Closed types prevent mixing collection behavior with application logic. If you extend List, callers get both your custom API and every List method. This creates confusing, hard-to-maintain interfaces. Composition gives you complete control over the public API.

ADDING CUSTOM VALIDATION

Composition lets you add business rules that inheritance cannot:

  ValidatedList
    items as List of String
    add()
      -> entry as String
      if entry?
        items += entry

Only set (valid) strings are added. With inheritance, callers could bypass your add method and use the parent List methods directly.

EXPOSING ITERATION

Delegate to the wrapped collection when needed:

  allTasks() as pure
    <- tasks as List of String: List(items)

Return a copy to prevent external modification of internal state.

MODERN PRECEDENT

Swift structs are closed. Rust has no inheritance. Kotlin classes are final by default. EK9 follows this modern trend. Types require explicit 'as open' to allow extension.

See Q96 for class definitions. See Q212 for composition over inheritance. See Q210 for trait delegation. See Q281 for verifying AI code.

Example

defines module qa.ai.mistakes.closedtypes

  defines class

    // === COMPOSITION WRAPPING A LIST ===

    TaskQueue
      items as List of String: List() of String

      default TaskQueue()

      addTask()
        -> taskName as String
        if taskName?
          items += taskName

      pending() as pure
        <- count as Integer: length items

      allTasks() as pure
        <- tasks as List of String: items + List() of String

      override operator ? as pure
        <- isSet as Boolean: items?

      operator $ as pure
        <- asString as String: `TaskQueue(${pending()} tasks)`

    // === COMPOSITION WRAPPING A DICT ===

    Settings
      entries as Dict of (String, String): Dict() of (String, String)

      default Settings()

      put()
        ->
          key as String
          setting as String
        if key? and setting?
          entries += DictEntry(key, setting)

      get() as pure
        -> key as String
        <- setting as String: entries.getOrDefault(key, "")

      has() as pure
        -> key as String
        <- exists as Boolean: entries.contains(key)

      override operator ? as pure
        <- isSet as Boolean: entries?

  defines program

    ClosedTypeDemo()
      stdout <- Stdout()

      // === USE TASKQUEUE (COMPOSITION OVER INHERITANCE) ===

      queue <- TaskQueue()
      queue.addTask("Deploy")
      queue.addTask("Test")
      queue.addTask("Review")

      stdout.println($queue)
      stdout.println(`Pending: ${queue.pending()}`)

      tasks <- queue.allTasks()
      for taskItem in tasks
        stdout.println(`  Task: ${taskItem}`)

      // === USE SETTINGS (COMPOSITION OVER INHERITANCE) ===

      prefs <- Settings()
      prefs.put("theme", "dark")
      prefs.put("lang", "en")

      stdout.println(`Theme: ${prefs.get("theme")}`)
      stdout.println(`Has lang: ${prefs.has("lang")}`)
      stdout.println(`Missing: ${prefs.get("missing")}`)

Common mistakes

E05030 — EK9 types are closed by default. List, Dict, Optional, Result, and PriorityQueue cannot be extended. Use composition instead: wrap the collection as a private field. See ek9 -h E05030 for details.

Incorrect:

TaskQueue extends List of String
      items as List of String

Correct:

TaskQueue
      items as List of String

E05030 — Dict is a closed type and cannot be subclassed. Wrap it as a field and expose domain-specific methods instead. See ek9 -h E05030 for details.

Incorrect:

Settings extends Dict of (String, String)
      entries as Dict of (String, String)

Correct:

Settings
      entries as Dict of (String, String)
Other ways to ask this
  • Why does AI generate 'MyList extends List of String' in EK9?
  • How do I fix AI-generated class extension of closed types?
  • Why are EK9 types closed by default?

Coming from another language?

Java: ArrayList and HashMap are open, commonly extended (considered bad practice). Python: list is open, commonly subclassed. C++: virtual inheritance, open by default. Rust: no inheritance, composition is the pattern. Go: embedding for delegation, no inheritance. Kotlin: classes final by default like EK9. Swift: structs are closed. EK9: ALL types closed by default, 'as open' required for extension, built-in collections cannot be extended, composition is the natural pattern.

Keywords: hallucination, inherit, extend, list, delegate, dict, pitfall, open, common-error, composition, sealed, migrate, wrong, exhaustive, closed, ai, mistake