Why is 'animals as List of Animal: [ Dog() ]' a List of Animal when '[ Dog() ]' on its own is a List of Dog?
← Collections and Data Structures · Ref: Q1395
A List or Dict literal gets its type in one of two ways.
INFERRED - NO DECLARED TYPE
'pets <- [ Dog(), Cat() ]' works out the most specific type every element shares: with Dog and Cat both extending Animal it is a List of Animal. '[ Dog() ]' alone is a List of Dog - that is all the compiler has to go on.
DECLARED - YOU SAID WHICH TYPE
'animals as List of Animal: [ Dog() ]' is ALWAYS a List of Animal, whatever the elements happen to be, because you were explicit. Each element must then be assignable to Animal exactly as a single assignment would be; one that is not - a Rock - is E50030, reported at that element. The same holds for a plain assignment ('animals := [ Cat() ]'), a returning declaration ('<- rtn as List of Animal: [ Dog() ]'), a nested literal ('List of List of Animal: [ [ Dog() ] ]') and a Dict literal, whose keys and values are checked against the declared key and value types.
AN EXISTING LIST STAYS WHAT IT IS
'dogs <- [ Dog() ]' followed by 'animals as List of Animal: dogs' is E50030. That is not a literal but a second name for the same mutable list: through 'animals' you could add a Cat to something 'dogs' still promises holds only Dogs. Generic types are invariant for exactly this reason.
Example
defines module qa.collections.declaredliteraltype defines class Animal as open name <- "animal" describe() <- rtn as String: "some animal" default operator ? Dog extends Animal override describe() <- rtn as String: "dog" default operator ? Cat extends Animal override describe() <- rtn as String: "cat" default operator ? Rock weight <- 1 default operator ? defines program DeclaredLiteralTypeDemo() stdout <- Stdout() //Declared: this IS a List of Animal, so a Cat can be added to it. animals as List of Animal: [ Dog() ] animals += Cat() for animal in animals stdout.println(animal.describe()) //Inferred from both elements: also a List of Animal. pets <- [ Dog(), Cat() ] for pet in pets stdout.println(pet.describe())
Common mistakes
E50030 — Every element of a declared List of Animal must be an Animal; Rock is not, so the error is reported at Rock(). See ek9 -h E50030.
Incorrect:
animals as List of Animal: [ Dog(), Rock() ]
Correct:
animals as List of Animal: [ Dog() ]
E50030 — 'dogs' is an existing List of Dog; naming it as a List of Animal would let a Cat be added to a list that promises only Dogs. Generic types are invariant - only a literal takes the declared type. See ek9 -h E50030.
Incorrect:
dogs <- [ Dog(), Dog() ] pets as List of Animal: dogs
Correct:
pets <- [ Dog(), Cat() ]
Other ways to ask this
- How is the type of a list literal decided in EK9?
- Can a List of Animal be initialised with only Dog elements?
- Why can't I assign my List of Dog variable to a List of Animal?
- Does a Dict literal take its key and value types from the declaration?
Coming from another language?
Java infers 'List<Animal> a = List.of(new Dog())' from the target type but rejects assigning a List<Dog> to a List<Animal> (generics are invariant; Java arrays are covariant and fail at run time with ArrayStoreException). Kotlin and Swift also take a literal's element type from the declaration. EK9 does the same: a literal takes the declared type, an existing list does not change type.
Keywords: inferred type, common super type, collection, declared type, dict literal, variance, generics, invariant, E50030, list literal