How does the List type work in EK9?

← Getting Started · Ref: Q45

EK9 has a generic List type for ordered, indexable collections. Lists use square bracket literal syntax [1, 2, 3] and the generic declaration List of T.

LIST CREATION

Four ways to create lists:

  numbers <- [1, 2, 3, 4, 5]      literal syntax, compiler infers List of Integer
  emptyList <- List() of String    typed empty list
  single <- List(42)               single-element construction
  fruits <- [
    "apple",
    "banana"
    ]                              multi-line literal for readability

GENERIC SYNTAX

EK9 uses 'of' instead of angle brackets: List of String, List of Integer. No <> needed.

ADDING ELEMENTS

+= mutates the list in place:

  names += "Dave"             appends Dave
  names += ["Eve", "Frank"]   appends both

+ creates a new list (original unchanged):

  moreNames <- names + "Grace"

REMOVING ELEMENTS

-= mutates the list:

  fruits -= "banana"          removes banana

- creates a new list without the element:

  without <- fruits - "banana"

ACCESSING ELEMENTS

Safe element access with getOrDefault:

  first <- numbers.getOrDefault(0, 0)                   first element
  last <- numbers.getOrDefault(length numbers - 1, 0)   last element

getOrDefault always returns a value: either the element at the index, or the provided default. No exceptions, no unset results.

LENGTH AND EMPTY CHECK

  len <- length numbers        number of elements
  isEmpty <- numbers is empty  true if no elements

ITERATION

  for fruit in fruits
    stdout.println(fruit)

EMPTY LIST IS SET

CRITICAL: An empty list IS set. Creating List() of String gives you a valid, set, empty list. Empty is not the same as unset.

See Q88 for operations (contains, reverse, copy, merge, comparison). See Q89 for stream pipelines (filter, map, collect). See Q37 for string interpolation with lists. See Q39 for integers used in list indexing. See Q120 for sorting lists. See Q125 for head/tail/skip to limit streams. See Q130 for mutating vs non-mutating operators. See Q131 for Python comprehension equivalents. See Q180 for list add. See Q183 for list first and last.

Example

defines module qa.list

  defines function

    isEven() as pure
      -> num as Integer
      <- rtn as Boolean: num mod 2 == 0

    intToString() as pure
      -> num as Integer
      <- rtn as String: $num

  defines program
    ListDemo()
      stdout <- Stdout()

      // === LIST CREATION ===

      // Literal syntax — compiler infers List of Integer
      numbers <- [1, 2, 3, 4, 5]
      stdout.println(`Numbers: ${numbers}`)

      // Typed empty list
      emptyList <- List() of String
      stdout.println(`Empty: ${emptyList}`)

      // Single-element construction
      single <- List(42)
      stdout.println(`Single: ${single}`)

      // Multi-line literal
      fruits <- [
        "apple",
        "banana",
        "cherry"
        ]
      stdout.println(`Fruits: ${fruits}`)

      // === ADDING ELEMENTS ===

      names <- List() of String
      names += "Alice"
      names += "Bob"
      names += "Charlie"
      stdout.println(`After +=: ${names}`)

      // + creates a new list (original unchanged)
      moreNames <- names + "Dave"
      stdout.println(`Original: ${names}`)
      stdout.println(`With Dave: ${moreNames}`)

      // === REMOVING ELEMENTS ===

      without <- fruits - "banana"
      stdout.println(`Without banana: ${without}`)

      fruits -= "cherry"
      stdout.println(`After -=: ${fruits}`)

      // === ACCESSING ELEMENTS ===

      first <- numbers.getOrDefault(0, 0)
      last <- numbers.getOrDefault(length numbers - 1, 0)
      stdout.println(`First: ${first}, Last: ${last}`)

      third <- numbers.getOrDefault(2, 0)
      stdout.println(`Index 2: ${third}`)

      // === LENGTH AND EMPTY ===

      lenNumbers <- length numbers
      stdout.println(`Length: ${lenNumbers}`)

      numbersEmpty <- numbers is empty
      stdout.println(`Numbers empty: ${numbersEmpty}`)

      listEmpty <- emptyList is empty
      stdout.println(`Empty list empty: ${listEmpty}`)

      // === BASIC ITERATION ===

      for fruit in fruits
        stdout.println(`Fruit: ${fruit}`)

      // === EMPTY LIST IS SET ===

      require emptyList?
      require emptyList is empty
      stdout.println(`Empty list isSet: ${emptyList?}`)

Common mistakes

E06020 — Generic types like List only take the number of parameters that actually parameterize them

Incorrect:

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

Correct:

numbers <- [1, 2, 3, 4, 5]

E06010 — The generic type must be parameterized with a type

Incorrect:

emptyList <- List()

Correct:

emptyList <- List() of String
Other ways to ask this
  • How do I create and use lists in EK9?
  • What is the literal syntax for lists in EK9?
  • How do I add and remove items from a list in EK9?

Coming from another language?

Java: ArrayList<T> with verbose generics, no literal syntax (List.of() since Java 9 but immutable). Python: built-in list with [] literal, no static type safety. JavaScript: Array with [] literal, no type safety. Rust: Vec<T> with vec![] macro. Go: slices with make()/append(), no generics until Go 1.18. Kotlin: listOf()/mutableListOf(). EK9: [1, 2, 3] literal, List of T generic (no angle brackets), getOrDefault for safe access, += and + for adding, -= and - for removing.

Keywords: iterate, getOrDefault, list, beginner, for, generic, remove, array, collection, add, intro, migrate, start, length, literal, empty, first