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