Why can't I extend List or Dict in EK9?

← Collections and Data Structures · Ref: Q128

EK9 collection types (List, Dict, PriorityQueue, Optional, Result) are closed by default and cannot be extended. This is a deliberate design decision for semantic integrity.

CLOSED BY DESIGN

Attempting to extend List or Dict produces a compile error:

  MyList extends List of String    Error: not open to be extended

This applies to all built-in generic collection types.

WHY CLOSED

Java's open ArrayList and HashMap are widely considered a design mistake:

  Subclassing mixes collection behavior with application logic
  Fragile base class problem: internal changes break subclasses
  Liskov substitution violations when overriding methods

Modern languages (Swift, Rust, Kotlin) avoid this pattern.

COMPOSITION ALTERNATIVE

Wrap the collection as a private field:

  ValidatedList
    items as List of String: List() of String
    add(item)
      if isValid(item)
        items += item

This keeps collection behavior separate from your domain logic.

DELEGATION PATTERN

Expose only the operations that make sense for your domain:

  size() <- length items
  get(index) <- items.getOrDefault(index, "")

Don't expose the full collection API if your type has different semantics.

See Q101 for why types are closed by default. See Q109 for composition over inheritance. See Q45 for List basics. See Q46 for Dict basics. See Q130 for mutating vs non-mutating operators on collections. See Q194 for defining generic classes. See Q212 for composition over inheritance pattern.

Example

defines module qa.collections.closedcollections

  defines class

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

      add()
        -> task as String
        if task? and not (items contains task)
          items += task

      remove()
        -> task as String
        items -= task

      size() as pure
        <- rtn as Integer: length items

      all() as pure
        <- rtn as List of String: items

      operator $ as pure
        <- rtn as String: $items

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

  defines program

    ClosedCollectionsDemo()
      stdout <- Stdout()

      // === COMPOSITION PATTERN ===

      tasks <- TaskList()
      tasks.add("Write code")
      tasks.add("Run tests")
      tasks.add("Deploy")

      // Duplicate is rejected by our add logic
      tasks.add("Run tests")

      stdout.println(`Tasks (${tasks.size()}): ${tasks}`)

      // Safe removal
      tasks.remove("Deploy")
      stdout.println(`After remove: ${tasks}`)

      // Get a copy of internal list
      allTasks <- tasks.all()
      stdout.println(`All: ${allTasks}`)

Common mistakes

E01073 — EK9 does not have null. The keyword is deliberately excluded. Use the ? operator to check if a variable is set. 'task?' checks tri-state (set/unset), replacing Java-style null checks. See ek9 -h E01073 for details.

Incorrect:

if task <> null

Correct:

if task? and not (items contains task)

E06180 — Class fields are always private in EK9. The 'items' field inside TaskList cannot be accessed directly — this is why composition works. You control the API through public methods like all(). See ek9 -h E06180 for details.

Incorrect:

allTasks <- tasks.items

Correct:

allTasks <- tasks.all()

E50060 — TaskList has no .push() method. The class defines 'add()' as its public API. With composition, you name methods to match your domain. See ek9 -h E50060 for details.

Incorrect:

tasks.push("Write code")

Correct:

tasks.add("Write code")

E50060 — EK9 does not have toString(). Use string interpolation or the $ operator. TaskList defines operator $ for string conversion. See ek9 -h E50060 for details.

Incorrect:

stdout.println(tasks.toString())

Correct:

stdout.println(`Tasks (${tasks.size()}): ${tasks}`)
Other ways to ask this
  • Why are collection types closed in EK9?
  • How do I add custom behavior to a List in EK9?
  • What is the alternative to subclassing collections in EK9?

Coming from another language?

Java: ArrayList and HashMap are open (considered design mistake). Python: can extend list and dict freely (mixin pattern). JavaScript: can extend Array. Rust: no inheritance, composition required. Go: no inheritance, embed structs. Kotlin: classes final by default (like EK9). EK9: all collection types closed, use composition and delegation instead of inheritance.

Keywords: list, extend, collection, migrate, dict, closed, wrap, delegation, sealed, exhaustive, design, data-structure, inherit, composition