How does collection type inference work in EK9?

← Collections and Data Structures · Ref: Q127

EK9 infers collection types from their literal elements. The compiler determines the common type from all elements in the literal.

FIRST-ELEMENT RULE

The first element establishes the base type:

  numbers <- [1, 2, 3]            infers List of Integer
  names <- ["Alice", "Bob"]       infers List of String
  flags <- [true, false, true]    infers List of Boolean

TYPE COERCION IN LITERALS

When elements have compatible types, the compiler promotes to the wider type:

  mixed <- [1, 2.0, 3]            infers List of Float (Integer promotes to Float)

The promote (#^) operator handles type widening automatically.

EXPLICIT TYPING WHEN NEEDED

For empty collections, specify the type explicitly:

  emptyList <- List() of String
  emptyDict <- Dict() of (String, Integer)

The compiler cannot infer types from zero elements.

DICT LITERAL INFERENCE

Dict literals infer both key and value types:

  ages <- {"Alice": 30, "Bob": 25}   infers Dict of (String, Integer)
  scores <- {1: 95.0, 2: 87.5}       infers Dict of (Integer, Float)

SINGLE-ELEMENT CONSTRUCTION

Single-element constructors use the element type:

  single <- List(42)           creates List of Integer with one element
  opt <- Optional("hello")    creates Optional of String

See Q45 for List basics. See Q46 for Dict basics. See Q27 for static typing. See Q24 for type conversion and promotion. See Q128 for why collection types are closed by design.

See Q23 for basic types. See Q45 for list.

Example

defines module qa.collections.typeinference

  defines program

    TypeInferenceDemo()
      stdout <- Stdout()

      // === INFERRED FROM FIRST ELEMENT ===

      numbers <- [1, 2, 3, 4, 5]
      stdout.println(`Integers: ${numbers}`)

      names <- ["Alice", "Bob", "Charlie"]
      stdout.println(`Strings: ${names}`)

      flags <- [true, false, true]
      stdout.println(`Booleans: ${flags}`)

      // === TYPE PROMOTION ===

      floats <- [1.0, 2.5, 3.7]
      stdout.println(`Floats: ${floats}`)

      // === EXPLICIT TYPING FOR EMPTY ===

      emptyStrings <- List() of String
      stdout.println(`Empty strings: ${emptyStrings}`)

      emptyDict <- Dict() of (String, Integer)
      stdout.println(`Empty dict: ${emptyDict}`)

      // === DICT LITERAL INFERENCE ===

      ages <- {"Alice": 30, "Bob": 25, "Charlie": 35}
      stdout.println(`Ages: ${ages}`)

      // === SINGLE ELEMENT CONSTRUCTION ===

      singleList <- List(42)
      stdout.println(`Single: ${singleList}`)

      singleOpt <- Optional("hello")
      stdout.println(`Optional: ${singleOpt}`)

Common mistakes

E50001 — ArrayList is a Java type that does not exist in EK9. Use 'List() of String' for an empty typed list. EK9 has its own collection types. See ek9 -h E50001 for details.

Incorrect:

emptyStrings <- ArrayList()

Correct:

emptyStrings <- List() of String

E50001 — HashMap is a Java type that does not exist in EK9. Use Dict literal syntax with curly braces or 'Dict() of (K, V)' for empty dicts. See ek9 -h E50001 for details.

Incorrect:

ages <- HashMap()

Correct:

ages <- {"Alice": 30, "Bob": 25, "Charlie": 35}

E06010 — Optional, List, Dict, and Result are generic types that require type parameters. Use 'Optional() of String' for explicit typing, or pass a value like 'Optional(42)' to let the compiler infer the type. See ek9 -h E06010 for details.

Incorrect:

singleOpt <- Optional()

Correct:

singleOpt <- Optional("hello")

E06010 — Dict requires two type parameters for key and value types. Use 'Dict() of (String, Integer)' or a dict literal like '{"key": 42}' which infers types from the elements. See ek9 -h E06010 for details.

Incorrect:

emptyDict <- Dict()

Correct:

emptyDict <- Dict() of (String, Integer)

E50001 — Vector is a Java/C++ type that does not exist in EK9. Use List for ordered sequences. EK9 has its own collection types: List, Dict, PriorityQueue, Optional, and Result. See ek9 -h E50001 for details.

Incorrect:

singleList <- Vector(42)

Correct:

singleList <- List(42)

E50001 — LinkedList is a Java type that does not exist in EK9. Use list literal syntax [1.0, 2.5, 3.7] or List() of Float. See ek9 -h E50001 for details.

Incorrect:

floats <- LinkedList(1.0, 2.5, 3.7)

Correct:

floats <- [1.0, 2.5, 3.7]

E06010 — List is a generic type that requires a type parameter. Use 'List() of String' for explicit typing, or pass a value like 'List(42)' to let the compiler infer the type. See ek9 -h E06010 for details.

Incorrect:

emptyStrings <- List()

Correct:

emptyStrings <- List() of String

E50001 — Array is not an EK9 type. Use list literal syntax [1, 2, 3, 4, 5] for creating a List of Integer. See ek9 -h E50001 for details.

Incorrect:

numbers <- Array(1, 2, 3, 4, 5)

Correct:

numbers <- [1, 2, 3, 4, 5]
Other ways to ask this
  • How does EK9 determine the type of a list literal?
  • What happens when list elements have different types in EK9?
  • How does EK9 infer Dict literal types?

Coming from another language?

Java: diamond operator <> since Java 7, var since Java 10, List.of() infers types. Python: dynamically typed, no inference needed. JavaScript: dynamically typed. Rust: turbofish ::<T> when inference fails, Vec::new() needs type annotation or usage context. Go: no generics until 1.18, type inference from assignment. Kotlin: type inference from literal elements. EK9: first-element inference, automatic promotion for compatible types, explicit typing for empty collections.

Keywords: automatic, inference, list, dict, coercion, element, promote, type, literal, data-structure, generic, collection