What operations does Dict support in EK9?

← Getting Started · Ref: Q90

Dict supports a rich set of operations beyond basic creation and lookup. This covers containment checks, key/value iterators, merging, copying, comparison, and conversions.

CONTAINS (KEY CHECK)

Three equivalent ways to check if a key exists:

  ages contains "Alice"           operator syntax
  "Bob" is in ages                natural language syntax
  "Zara" is not in ages           negated form

These check keys, not values.

KEYS AND VALUES ITERATORS

  keyIter <- ages.keys()          iterator over all keys
  valIter <- ages.values()        iterator over all values

These return iterators (not lists). Use in while loops:

  while keyIter?
    stdout.println(keyIter.next())

MERGING DICTS

+ creates a new merged dict (originals unchanged):

  combined <- dict1 + dict2

:~: merge operator (mutates the left-hand side):

  dict1 :~: dict2

When keys overlap, the right-hand side values win.

COPY AND REPLACE

:=: deep copy:

  copied :=: original

:^: replace contents entirely:

  target :^: source

COMPARISON

  dict1 == dict2                  equal if same key-value pairs
  dict1 <> dict2                  not equal

STRING AND JSON CONVERSION

  $ages                           string representation
  $$ages                          JSON object representation

The $$ operator converts Dict to JSON with keys as JSON keys and values as JSON values.

HASHCODE

  hash <- #? ages                 integer hashcode

EMPTY DICT IS SET (NOT UNSET)

CRITICAL: An empty dict IS set. Creating Dict() of (K, V) gives you a valid, set, empty dict. This follows EK9's collection semantics: collections are always meaningful when created, even if empty. An empty dict is not the same as a missing dict.

  emptyDict <- Dict() of (String, Integer)
  emptyDict?                      true (set)
  emptyDict is empty              true (no entries)

See Q46 (How does the Dict type work?) for creation, DictEntry, adding/removing, getOrDefault, and basic iteration. See Q45 (How does the List type work?) for the sister collection type. See Q29 (How do unset variables work?) for tri-state semantics. See Q129 for safe access patterns with missing keys. See Q260 for Dict key type requirements and custom keys.

Use 'ek9 -h Dict' to see the full API.

Example

defines module qa.dict.operations

  defines program
    DictOperationsDemo()
      stdout <- Stdout()

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

      // === CONTAINS (KEY CHECK) ===

      hasAlice <- ages contains "Alice"
      stdout.println(`Contains Alice: ${hasAlice}`)

      if "Bob" is in ages
        stdout.println("Found Bob")

      if "Zara" is not in ages
        stdout.println("Zara not found")

      // === KEYS AND VALUES ITERATORS ===

      keyIter <- ages.keys()
      while keyIter?
        stdout.println(`Key: ${keyIter.next()}`)

      valIter <- ages.values()
      while valIter?
        stdout.println(`Value: ${valIter.next()}`)

      // === MERGING DICTS ===

      d1 <- {"x": 1, "y": 2}
      d2 <- {"y": 99, "z": 3}

      // + creates a new merged dict
      merged <- d1 + d2
      stdout.println(`Merged: ${merged}`)

      // :~: merge operator mutates left-hand side
      d1 :~: d2
      stdout.println(`After merge: ${d1}`)

      // === COPY ===

      copied <- Dict() of (String, Integer)
      copied :=: ages
      stdout.println(`Copied: ${copied}`)

      // === COMPARISON ===

      c1 <- {"a": 1, "b": 2}
      c2 <- {"a": 1, "b": 2}
      c3 <- {"x": 9}

      require c1 == c2
      require c1 <> c3
      stdout.println(`c1 == c2: ${c1 == c2}`)
      stdout.println(`c1 <> c3: ${c1 <> c3}`)

      // === STRING AND JSON CONVERSION ===

      asString <- $ages
      stdout.println(`As string: ${asString}`)
      stdout.println(`As JSON: ${$ ages}`)

      // === HASHCODE ===

      hash <- #? ages
      stdout.println(`Hash: ${hash}`)

      // === EMPTY DICT IS SET ===

      emptyDict <- Dict() of (String, Integer)
      require emptyDict?
      require emptyDict is empty
      stdout.println(`Empty dict isSet: ${emptyDict?}`)
      stdout.println(`Empty dict isEmpty: ${emptyDict is empty}`)

Common mistakes

E50060 — Dict has no get() method in EK9. Use the contains operator for key existence checks, or getOrDefault() for value retrieval with a fallback. See ek9 -h E50060 for details.

Incorrect:

hasAlice <- ages.get("Alice")

Correct:

hasAlice <- ages contains "Alice"
Other ways to ask this
  • How do I get keys and values from an EK9 Dict?
  • How do I check if a Dict contains a key in EK9?
  • How do I merge, copy, or compare Dicts in EK9?

Coming from another language?

Java: HashMap .containsKey(), .keySet(), .values(), .putAll() for merge, no operator overloading. Python: 'in' operator, .keys(), .values(), dict union | (3.9+), == comparison. JavaScript: Object.keys(), Object.values(), spread for merge, no == comparison. Rust: .contains_key(), .keys(), .values(), .extend() for merge. Go: comma-ok idiom, no built-in merge or comparison. Kotlin: 'in' operator, .keys, .values, + for merge. EK9: 'contains'/'is in'/'is not in', .keys()/.values() iterators, + for new merged dict, :~: for in-place merge, :=: deep copy, == comparison, $$ for JSON.

Keywords: keys, copy, contains, hashcode, iterator, operations, first, dict, empty, set, start, values, json, intro, compare, merge, beginner