What operations does Optional support in EK9?

← Getting Started · Ref: Q84

Optional supports comparison, copy, merge, contains, and the ternary guard pattern. These operations follow EK9's tri-state semantics: two unset Optionals are equal, set and unset are not equal.

TERNARY GUARD

Extract a value or use a default in a single expression:

  value <- o? <- o.get() else String()

If 'o' is set, evaluates to o.get(); otherwise uses the default.

CONTAINS

Check if an Optional holds a specific value:

  item contains 42                    true if set AND value equals 42

An unset Optional never contains anything.

COMPARISON

  opt1 == opt2                        equal if both set with same value, or both unset
  opt1 <> opt2                        not equal

COPY AND MERGE

  copied :=: original                 deep copy
  target :~: source                   merge: sets target only if source is set

Merge is useful for layered defaults.

NO REASSIGNMENT IN SAFE BLOCKS

Once inside a guard, you cannot reassign the Optional:

  if o?
    o: Optional("other")             COMPILER ERROR

This prevents invalidating the safety guarantee.

STRING, JSON, AND HASHCODE

  $item                               string representation
  $$item                              JSON representation
  #? item                             hashcode

See Q47 for Optional basics (creation, guards, getOrDefault). See Q85 for Optional in stream pipelines. See Q29 for tri-state semantics. See Q98 for record copy, merge, and replace operators.

Example

defines module qa.optional.operations

  defines program
    OptionalOperations()
      stdout <- Stdout()

      a <- Optional(10)
      b <- Optional(10)
      c <- Optional(99)
      none <- Optional() of Integer

      // === TERNARY GUARD ===

      ternarySet <- a? <- a.get() else 0
      stdout.println(`Ternary (set): ${ternarySet}`)

      ternaryNone <- none? <- none.get() else 0
      stdout.println(`Ternary (empty): ${ternaryNone}`)

      // === CONTAINS ===

      minQuantity <- 10
      maxQuantity <- 99
      stdout.println(`a contains 10: ${a contains minQuantity}`)
      stdout.println(`a contains 99: ${a contains maxQuantity}`)
      stdout.println(`none contains 10: ${none contains minQuantity}`)

      // === COMPARISON ===

      stdout.println(`a == b: ${a == b}`)
      stdout.println(`a <> c: ${a <> c}`)
      stdout.println(`a <> none: ${a <> none}`)

      // === COPY ===

      copied <- Optional() of Integer
      copied :=: a
      require copied == a
      stdout.println(`Copied: ${copied}`)

      // === MERGE ===

      target <- Optional() of Integer
      target :~: Optional(77)
      stdout.println(`Merged: ${target}`)

      // Merge with unset source — target unchanged
      target2 <- Optional(5)
      target2 :~: Optional() of Integer
      stdout.println(`Merge no-op: ${target2}`)

      // === STRING, JSON, HASHCODE ===

      require a?
      stdout.println(`String: ${a}`)
      stdout.println(`JSON: ${$ a}`)
      stdout.println(`Hash: ${#? a}`)

Common mistakes

E08040 — Reassigning a variable inside a guard block invalidates the safety check. This triggers E08040 — reassignment/mutation within possible safe method access scope is not allowed. The guard guarantees a is set; reassigning could make it unset. See ek9 -h E08040 for details.

Incorrect:

if a?
        a :=: Optional(5)
        stdout.println(`String: ${$a}`)

Correct:

      require a?
      stdout.println(`String: ${a}`)

E08030 — Accessing .get() without a ? guard triggers E08030 — has not been checked before access. Always check with ? or use getOrDefault() to avoid needing a guard. See ek9 -h E08030 for details.

Incorrect:

ternarySet <- a.get()

Correct:

ternarySet <- a? <- a.get() else 0
Other ways to ask this
  • How do I compare Optionals in EK9?
  • What is the ternary guard pattern for Optional?
  • How do I copy or merge Optional values?

Coming from another language?

Java: Optional.equals() for comparison, no copy/merge, .map()/.flatMap() for transformations. Rust: Option implements PartialEq, Clone for copy, no merge concept. Kotlin: == on nullable types uses structural equality, no merge. Go: no Optional type, manual nil checks. EK9: full operator set (==, <>, :=:, :~:, contains, $, $$, #?) with consistent tri-state semantics and compile-time guard enforcement.

Keywords: beginner, hashcode, start, string, contains, copy, comparison, merge, safe, operations, ternary, absent, optional, intro, first, json, isset, null-safe, reassignment, guard