Design note: why EK9 types are closed by default
This note goes with Closed by Default in
Inheritance in EK9. In EK9 a class or record can be extended only
when it is declared as open (or is abstract), and the built-in concrete types (List, Dict, String,
Integer and the rest) can never be extended (E05030). Coming from Java, Python or
C#, that is the opposite of what you expect. It is a deliberate shift, made to remove a category of design
problems rather than to warn about them.
Semantic integrity
Extending a concrete type such as List or Dict mixes collection semantics with application logic. A
UserList is not a list; it has a list of users. Once it inherits every list operation, the
reader cannot tell which add runs, and the domain rules can be bypassed through the inherited ones.
Composition keeps the two apart.
Later languages took the same view
EK9 follows language design that learned from Java:
- Kotlin: classes are
finalby default; you addopento allow inheritance. -
Swift: structs cannot inherit at all; a class can be subclassed outside its own module only when it is
marked
open. - Rust: no class inheritance at all, only composition and traits.
- Go: no class inheritance; struct composition and interfaces.
These languages moved away from Java's "everything is open" model.
What open-by-default cost Java
| Problem | Example | Impact |
|---|---|---|
| Fragile Base Class Problem | Extending ArrayList, overriding one method breaks others | Hard-to-diagnose bugs, framework lock-in |
| Semantic Confusion | Stack extends Vector (Java standard library) |
Stack IS-A Vector? Violates Liskov Substitution Principle |
| Breaking Encapsulation | Subclasses access internal implementation details | Cannot refactor base class without breaking subclasses |
Joshua Bloch put the choice in Effective Java: "Design and document for inheritance or else prohibit it." EK9 takes the second path by default and the first only when you ask for it: prohibit, unless the type is explicitly designed for extension.
What you do instead
- Compose: hold the List (or Dict, or your own storage) as a property and expose only the operations you mean.
- Use traits for polymorphism, with delegation by
bywhere you would have inherited an implementation. -
Use
as openfor a hierarchy you designed, such as a framework base class or a classic Shape/Animal extension point, with documented extension contracts.