Why is List of Circle not compatible with List of Shape?
← Generics · Ref: Q653
EK9 creates truly independent types for each generic parameterisation. 'List of Circle' and 'List of Shape' are completely separate types with no relationship, even if Circle extends Shape.
TYPE INDEPENDENCE
Each parameterisation is a distinct type:
circles <- List() of Circle // one type shapes <- List() of Shape // completely different type numbers <- List() of Integer // yet another different type
NO ERASURE
Unlike Java where 'List<Circle>' and 'List<Shape>' become the same 'List' at runtime (erasure), EK9 maintains full type identity. They are separate types that happen to have similar structures.
NO COVARIANCE
You cannot pass 'List of Circle' where 'List of Shape' is expected. This prevents the type safety violation where adding a Square to a 'List of Shape' that is actually a 'List of Circle'.
METHOD OVERLOADING
Because types are independent, you can overload methods:
Processor
process()
-> circles as List of Circle
<- rtn as Integer: length circles
process()
-> shapes as List of Shape
<- rtn as Integer: length shapes
Java cannot do this due to erasure.
See Q198 for built-in generics. See Q195 for constrain by. See Q196 for multi-parameter generics. See Q655 for method overloading with generics. See Q642 for constructor inference.
Example
defines module qa.genericsdeep.typeindependence defines class Hue as abstract colourName as String: String() default Hue() as pure Hue() as pure -> n as String colourName :=? String(n) name() as pure <- rtn as String: String(colourName) default operator ? Red is Hue default Red() as pure Red() as pure -> n as String super(n) Blue is Hue default Blue() as pure Blue() as pure -> n as String super(n) defines program TypeIndependenceDemo() stdout <- Stdout() // === SEPARATE LISTS OF DIFFERENT TYPES === reds <- List() of Red reds += Red("Crimson") reds += Red("Scarlet") blues <- List() of Blue blues += Blue("Navy") // === EACH LIST IS AN INDEPENDENT TYPE === redCount <- length reds stdout.println(`Red count: ${redCount}`) blueCount <- length blues stdout.println(`Blue count: ${blueCount}`) // === CANNOT MIX: List of Red != List of Blue === // reds += Blue("Teal") // ERROR: Blue is not Red // === INDEPENDENT CONTAINERS === redOpt <- Optional(Red("Vermillion")) blueOpt <- Optional(Blue("Azure")) stdout.println(`Red optional set: ${redOpt?}`) stdout.println(`Blue optional set: ${blueOpt?}`) // Optional of Red and Optional of Blue are different types stdout.println("Each parameterised type is a separate independent type")
Common mistakes
E50060 — Each generic parameterisation creates a fully independent type. List of Red and List of Blue are completely separate types with no relationship, so adding a Blue to a List of Red is a type error. See ek9 -h E50060 for details.
Incorrect:
reds <- List() of Red reds += Blue("Navy")
Correct:
reds <- List() of Red
Other ways to ask this
- How does type independence work for parameterised types in EK9?
- Does EK9 use type erasure like Java?
- Can I pass List of Circle where List of Shape is expected?
Coming from another language?
Java: type erasure makes List<Circle> and List<Shape> the same at runtime, cannot overload. C++: templates create independent types (full monomorphisation). Rust: generics create independent types. Go: generics create independent types (Go 1.18+). Kotlin: same erasure as Java. EK9: fully independent types, no erasure, safe method overloading.
Keywords: parameterised, covariance, erasure, overload, migrate, independence, list, generic, type, separate, type-parameter