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: any comparison or contains involving an unset Optional gives an UNSET Boolean - neither true nor false - so printing it prints nothing.

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

On an unset Optional, contains gives an unset Boolean, not false. Check item? first, or print (item contains 42)? to see that it is unset.

COMPARISON

  opt1 == opt2                        true if both set with the same value, false if both set with different values
  opt1 <> opt2                        not equal

If either Optional is unset - including two unset Optionals - the result of == and <> is an unset Boolean.

COPY AND MERGE

  copied :=: original                 copy
  target :~: source                   merge: never overwrites a set value

An UNSET target takes the value of a set source; a SET target always keeps its own value, whatever the source; an unset source changes nothing. Use :=: (copy) or :^: (replace) to overwrite, or when an unset source must clear the target.

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 gives the string representation
- $$item gives the JSON representation
- #? item gives the 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}`)
      //contains on an unset Optional is unset, so print whether it is set
      stdout.println(`none contains 10 isSet: ${(none contains minQuantity)?}`)

      // === COMPARISON ===

      stdout.println(`a == b: ${a == b}`)
      stdout.println(`a <> c: ${a <> c}`)
      //Comparing with an unset Optional gives an unset Boolean
      stdout.println(`a <> none isSet: ${(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 - the target is left unchanged
      target2 <- Optional(5)
      target2 :~: Optional() of Integer
      stdout.println(`Merge from unset: ${target2}`)

      // Merge into a set target - the target keeps its own value
      target2 :~: Optional(9)
      stdout.println(`Merge into set: ${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: optional, absent, ternary, operations, safe, merge, comparison, copy, contains, string, start, hashcode, beginner, guard, reassignment, null-safe, isset, json, first, intro