Question: How do I run code concurrently or in parallel in EK9?

How do I process items in parallel in EK9?

How does EK9 handle parallel processing?

Answer:
EK9 uses the async operation in stream pipelines for parallel processing. Instead of managing threads directly, you express parallelism as a pipeline stage that invokes each stream element concurrently.

STREAM PIPELINES WITH ASYNC
The stream must contain zero-arg function delegates. The async stage invokes each one in parallel on a virtual thread; the return value flows into the next stage:
  cat workers | async | map with toLine > stdout
async itself takes NO function parameter — it calls the stream element. There is no 'async with processor' form; the function to call IS the stream element.

THE ASYNC OPERATION
async is a stream pipeline operation like call, filter, or map. The stream type must be a zero-arg function delegate that returns a mainValue; that return value becomes the next stream's element type. async only differs from call in that elements are dispatched concurrently to virtual threads.

WHEN TO USE ASYNC
Use async for I/O-bound or computation-heavy operations where items can be processed independently:
- Fetching multiple URLs (build a List of url-fetcher functions, then `| async`)
- Processing multiple files (List of file-reader functions, then `| async`)
- Making parallel API calls (List of request functions, then `| async`)

TURNING DATA INTO ASYNC WORK
If you start with data rather than function delegates, use `map with` to project each item to a zero-arg function delegate, then `| async` to invoke them in parallel:
  cat urls | map with toFetcher | async | filter by isSuccess > stdout
Here toFetcher takes a URL and returns a zero-arg function whose body actually performs the fetch. The async stage then runs all those fetchers concurrently.

VIRTUAL THREADS
EK9 runs on Java 25 with virtual threads. The async operation leverages virtual threads for lightweight concurrency. You get parallelism without managing thread pools or executors.

THREAD-BOUNDARY IMPLICATIONS
Each `| async` invocation is a NEW thread context. If a worker captures a MutexLock that the calling pipeline is already holding, you will get E08253 (cross-thread same lock) because MutexLock reentrancy is per-thread. See Q1299 for the recovery pattern.

See Q89 for stream pipelines. See Q158 for MutexLock (which shows the canonical LockableAddressSet pattern). See Q160 for concurrency model. See Q1299 for the async + same-lock pitfall. See Q125 for head/tail.

defines module qa.concurrency.parallel

  defines function

    doubleIt() as pure
      -> number as Integer
      <- rtn as Integer: number * 2

    isLarge() as pure
      -> number as Integer
      <- rtn <- Boolean()
      batchSize <- 50
      rtn: number > batchSize

  defines program

    ParallelProcessingDemo()
      stdout <- Stdout()

      // === STREAM PIPELINE ===

      items <- List() of Integer
      items += 10
      items += 20
      items += 30
      items += 40
      items += 50

      // Standard pipeline (sequential)
      cat items | map with doubleIt | filter by isLarge > stdout

      // For parallel: stream must contain zero-arg function delegates.
      //   cat workers | async | map with toLine > stdout
      // async takes NO parameter — the function IS the stream element.
      // To start from data: cat urls | map with toFetcher | async > stdout

      // See Q158 for the canonical async-safe shared-state
      // pattern (LockableAddressSet via MutexLock of AddressSet).

      stdout.println("EK9 uses async in stream pipelines for parallelism")

Java: ExecutorService, CompletableFuture, or parallel streams. Python: asyncio, multiprocessing, or concurrent.futures. Rust: async/await with tokio, or rayon for data parallelism. Go: goroutines with channels. EK9: async in stream pipelines (cat funcs | async) where each stream element is a zero-arg function delegate that async invokes in parallel — leveraging Java 25 virtual threads for lightweight concurrency. No coloured-function problem; no thread pool wiring.

async compile concurrency concurrent migrate parallel pipeline process stream thread virtual

Explanation: EK9 has no .stream() method chains. Use pipe syntax: 'cat items | map with fn | filter by fn > stdout'. EK9 expresses pipelines through operators, not method calls. See ek9 -h E50060 for details.

Explanation: EK9 List has no .add() method. Use the += operator. See ek9 -h E50060 for details.


Question: Does EK9 have async/await, goroutines, or manual thread management?

How is EK9's concurrency model different from Go or Rust?

Does EK9 support goroutines or channels?

Answer:
EK9 does not have async/await, goroutines, channels, or manual thread management. Instead, it treats concurrency as a pipeline problem solved with stream operations and MutexLock.

WHAT EK9 DOES NOT HAVE
EK9 deliberately excludes:
- async/await syntax (no colored function problem)
- Goroutines or lightweight coroutines
- Channels or message-passing primitives
- Thread.start() or manual thread creation
- Future/Promise types as first-class constructs

WHY NO ASYNC/AWAIT OR GOROUTINES
async/await creates the 'colored function' problem where async functions cannot be called from sync functions without propagating async throughout the call chain. Goroutines and channels add complexity for coordination. EK9 avoids both by making concurrency a pipeline concern, not a function-level concern.

THE EK9 APPROACH
EK9 provides two concurrency mechanisms:
1. Stream pipelines where the stream type is a zero-arg function delegate and `| async` invokes each element in parallel on virtual threads: `cat workers | async | map with toLine > stdout`. async takes NO function argument — the function IS the stream element.
2. MutexLock of T for protecting shared state, held as a field on a wrapping class so the lock has a stable identity for compile-time data-race and deadlock detection.
These two primitives cover the vast majority of concurrent programming needs.

VIRTUAL THREADS
EK9 runs on Java 25 virtual threads. Virtual threads are extremely lightweight (millions per process) and handle blocking I/O efficiently. The runtime manages scheduling automatically.

COMPILE-TIME SAFETY
The pairing of MutexLock + closed-world call graph + statically-derivable lock identity lets the compiler prove the absence of data races (E08251), lock-order deadlocks (E08252), same-lock-across-thread-boundary issues (E08253), and same-type lock nesting whose acquisition order cannot be statically proven (E08255 — the bank-transfer / dining-philosophers shape) WITHOUT runtime overhead and WITHOUT developer annotations. No other mainstream language does this at compile time.

CONCURRENCY IS A PIPELINE PROBLEM
Most concurrency is about processing items in parallel or protecting shared data. Pipelines handle the first, MutexLock handles the second. No thread pools, no executors, no callback hell.

DESIGN PHILOSOPHY
Like removing break/continue/return, EK9 removes low-level concurrency primitives that cause bugs. Thread safety violations, deadlocks, and race conditions are among the hardest bugs to diagnose. Simpler primitives produce more reliable concurrent programs.

See Q158 for MutexLock (which shows the canonical LockableAddressSet pattern). See Q159 for async pipelines. See Q1298 for lock-order cycle deadlock recovery. See Q1299 for the async + same-lock pitfall. See Q1300 for multi-lock design. See Q89 for stream pipelines. See Q144 for control flow design philosophy.

defines module qa.concurrency.philosophy

  defines function

    transform() as pure
      -> number as Integer
      <- rtn as String: "processed: " + $number

  defines program

    ConcurrencyModelDemo()
      stdout <- Stdout()

      // === THE EK9 APPROACH ===

      // EK9 treats concurrency as a pipeline problem:

      // 1. Stream pipelines for parallel processing
      items <- List() of Integer
      items += 1
      items += 2
      items += 3

      cat items | map with transform > stdout

      // For parallel: cat workers | async | map with toLine > stdout
      // (stream type IS the function — async takes no parameter)

      // 2. MutexLock as a field — see Q158 for the canonical
      // LockableAddressSet pattern.

      // EK9 has no async/await, no goroutines, no Thread.start(),
      // no Future. Runs on Java 25 virtual threads.

      stdout.println("EK9 concurrency: pipelines + MutexLock, no thread management")

Java: CompletableFuture, ExecutorService, synchronized, virtual threads. Python: asyncio with async/await. Rust: async/await with tokio or async-std runtimes. Go: goroutines with channels and select. Kotlin: coroutines with suspend functions. EK9: stream pipelines with async operation and MutexLock, running on Java 25 virtual threads with no explicit thread management.

async await channel concurrency design future goroutine migrate model parallel philosophy promise thread

Explanation: EK9 has no .stream() method chains. Use pipe syntax: 'cat items | map with fn > stdout'. Concurrency is a pipeline problem in EK9. See ek9 -h E50060 for details.


Question: How do I safely get a value from a Dict when the key might not exist?

How do I avoid exceptions when accessing a Dict key in EK9?

What is the safe way to look up a Dict value in EK9?

How do I handle missing Dict keys in EK9?

Answer:
EK9 Dicts use getOrDefault(key, default) for safe value access. There is no .get() method that could return an unset value. This design eliminates null pointer exceptions from dictionary lookups.

GETORDEFAULT
Always returns a usable value:
  age <- ages.getOrDefault("Alice", 0)
If the key exists, returns its value. If missing, returns the default you provide. No exceptions, no unset results.

CONTAINS CHECK
Test for key existence before access:
  if ages contains "Alice"
    stdout.println("Alice is in the dict")
The contains operator checks keys, not values.

GUARD PATTERN WITH FOR
Iterate safely over entries without worrying about missing keys:
  for entry in ages
    stdout.println(`${entry.key()}: ${entry.value()}`)
Every entry yielded by iteration is guaranteed to be set.

IS IN / IS NOT IN
Natural language syntax for key checks:
  if "Bob" is in ages
    stdout.println("Found Bob")
  if "Zara" is not in ages
    stdout.println("Zara not found")

See Q46 for Dict basics. See Q90 for Dict operations. See Q129 for missing key patterns. See Q165 for why EK9 has no .get() method. See Q243 for coalescing operators (??, ?:, <?, >?) used for safe value selection.

defines module qa.safeaccess.dictsafe

  defines program
    DictSafeAccessDemo()
      stdout <- Stdout()

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

      // === GETORDEFAULT ===

      // Key exists — returns actual value
      aliceAge <- ages.getOrDefault("Alice", 0)
      stdout.println(`Alice age: ${aliceAge}`)

      // Key missing — returns default
      missingAge <- ages.getOrDefault("Zara", 0)
      stdout.println(`Missing age (default 0): ${missingAge}`)

      // === CONTAINS CHECK ===

      if ages contains "Alice"
        stdout.println("Alice is in the dict")

      if ages contains "Unknown"
        stdout.println("Should not print")

      // === IS IN / IS NOT IN ===

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

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

      // === SAFE ITERATION ===

      // Every entry from iteration is guaranteed set
      for entry in ages
        stdout.println(`${entry.key()}: ${entry.value()}`)

      // === COMBINING PATTERNS ===

      // Use getOrDefault for computation with fallback
      score <- ages.getOrDefault("Dave", -1)
      stdout.println(`Dave score (or -1): ${score}`)

Java: map.get(key) returns null if missing (NPE risk), map.getOrDefault(key, default) exists but is rarely used. Python: dict[key] throws KeyError, dict.get(key, default) is the safe alternative. Rust: HashMap.get() returns Option, must unwrap or match. Go: val, ok := m[key] comma-ok idiom, zero value if missing. Kotlin: map[key] returns null, map.getOrDefault() available. EK9: only getOrDefault() exists, no .get() that could return unset, contains for key checks.

access contains default dict exception getOrDefault guard key lookup missing null null-safe safe

Explanation: Dict has no get() method that could return an unset value. Use getOrDefault(key, default) which always returns a usable value. This prevents null/unset bugs from missing key lookups. See ek9 -h E50060 for details.

Explanation: Dict has no getValue() method. Use getOrDefault(key, default) which always returns a usable value. See ek9 -h E50060 for details.


Question: How do I safely access a List element by index?

How do I avoid index out of bounds in EK9?

What is the safe way to get an element from a List in EK9?

How do I access list elements without exceptions in EK9?

Answer:
EK9 Lists use getOrDefault(index, default) for safe element access. If the index is within bounds, returns the element. If out of bounds, returns the default. No exceptions.

GETORDEFAULT BY INDEX
Always returns a usable value:
  first <- numbers.getOrDefault(0, -1)
  missing <- numbers.getOrDefault(99, -1)
No IndexOutOfBoundsException, no null, no crashes.

LENGTH CHECK
Check bounds manually when needed:
  if length numbers > 0
    stdout.println(`Has items`)

EMPTY CHECK
Test whether a list has any elements:
  if numbers is empty
    stdout.println("No items")

SAFE ITERATION
Iterating with for-in never goes out of bounds:
  for item in numbers
    stdout.println($item)
Each item yielded is guaranteed to be set.

STREAM HEAD FOR FIRST ELEMENT
Use head 1 to safely get the first element:
  cat numbers | head 1 | collect as List of Integer
Returns an empty list if the source is empty.

See Q45 for List basics. See Q88 for List operations. See Q125 for head/tail/skip in streams.

defines module qa.safeaccess.listsafe

  defines function
    getList()
      <- rtn <- List() of Integer

  defines program
    ListSafeAccessDemo()
      stdout <- Stdout()

      numbers <- [10, 20, 30, 40, 50]

      // === GETORDEFAULT BY INDEX ===

      // Valid index — returns element
      first <- numbers.getOrDefault(0, -1)
      stdout.println(`First: ${first}`)

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

      // Out of bounds — returns default
      outOfBounds <- numbers.getOrDefault(99, -1)
      stdout.println(`Out of bounds (default -1): ${outOfBounds}`)

      // === LENGTH CHECK ===

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

      if length numbers > 0
        stdout.println("List has items")

      // === EMPTY CHECK ===

      emptyList <- getList()
      if emptyList is empty
        stdout.println("Empty list detected")

      if ~numbers is empty
        stdout.println("Numbers is not empty")

      // === SAFE ITERATION ===

      for item in numbers
        stdout.println(`Item: ${item}`)

      // === STREAM HEAD FOR FIRST ELEMENT ===

      firstItems <- cat numbers | head 1 | collect as List of Integer
      stdout.println(`Head 1: ${firstItems}`)

      // Head on empty list yields empty result
      noItems <- cat emptyList | head 1 | collect as List of Integer
      stdout.println(`Head of empty: ${noItems}`)

Java: list.get(index) throws IndexOutOfBoundsException if out of bounds. Python: list[index] throws IndexError, negative indices wrap. Rust: vec[index] panics, vec.get(index) returns Option. Go: slice[index] panics if out of bounds. Kotlin: list[index] throws, list.getOrElse(index) { default } is safe. EK9: getOrDefault(index, default) always returns a value, no exceptions.

access array bounds default element exception getOrDefault guard index list null-safe safe

Explanation: List has no get(index) method that could throw on invalid indices. Use getOrDefault(index, default) which always returns a usable value. This prevents IndexOutOfBoundsException-style bugs. See ek9 -h E50060 for details.

Explanation: List has no elementAt() method. Use getOrDefault(index, default) which always returns a usable value. See ek9 -h E50060 for details.


Question: How do I safely unwrap an Optional value?

How do I get the value from an Optional in EK9?

What is the safe way to access an Optional in EK9?

Answer:
EK9 Optionals require a compiler-enforced guard before accessing the contained value. There is no .unwrap() that could crash at runtime. Multiple patterns exist for safe unwrapping.

GUARD WITH IF
Declare and check in one expression:
  if val <- getOptional()
    extracted <- val.get()
The variable only exists inside the if block. If unset, the block is skipped entirely.

EXPLICIT ? CHECK
Check with ? then access inside the guarded block:
  opt <- getOptional()
  if opt?
    extracted <- opt.get()
The compiler verifies ? was checked before allowing .get().

GETORDEFAULT
Extract the value with a fallback, no guard needed:
  name <- opt.getOrDefault("default")
Returns the contained value if set, otherwise returns the default.

TERNARY GUARD
Compact single-expression extraction:
  name <- opt? <- opt.get() else "default"
If set, evaluates to opt.get(); otherwise uses the default.

See Q47 for Optional basics. See Q84 for Optional operations. See Q85 for Optional in streams. See Q634 for guard-based safe access. See Q636 for chained guard access.

defines module qa.safeaccess.optionalsafe

  defines function

    getFilledOptional()
      <- rtn <- Optional("Hello")

    getEmptyOptional()
      <- rtn <- Optional() of String

  defines program
    OptionalUnwrapDemo()
      stdout <- Stdout()

      // === GUARD WITH IF (DECLARATION FORM) ===

      if val <- getFilledOptional()
        extracted <- val.get()
        stdout.println(`Guard unwrap: ${extracted}`)

      // Empty Optional — guard block not entered
      if val <- getEmptyOptional()
        stdout.println("Should not print")

      // === EXPLICIT ? CHECK ===

      opt <- getFilledOptional()
      if opt?
        checked <- opt.get()
        stdout.println(`? check unwrap: ${checked}`)

      // === GETORDEFAULT ===

      filled <- getFilledOptional()
      fromFilled <- filled.getOrDefault("fallback")
      stdout.println(`GetOrDefault (filled): ${fromFilled}`)

      emptyOpt <- getEmptyOptional()
      fromEmpty <- emptyOpt.getOrDefault("fallback")
      stdout.println(`GetOrDefault (empty): ${fromEmpty}`)

      // === TERNARY GUARD ===

      opt2 <- getFilledOptional()
      ternaryFilled <- opt2? <- opt2.get() else "default"
      stdout.println(`Ternary (filled): ${ternaryFilled}`)

      opt3 <- getEmptyOptional()
      ternaryEmpty <- opt3? <- opt3.get() else "default"
      stdout.println(`Ternary (empty): ${ternaryEmpty}`)

Java: Optional.get() throws NoSuchElementException if empty, Optional.orElse() for defaults. Python: no Optional type, None checks are manual. Rust: Option.unwrap() panics if None, unwrap_or() for defaults. Go: no Optional, manual nil checks. Kotlin: nullable T? with !! force-unwrap (throws NPE), ?: elvis for defaults. EK9: compiler-enforced guards, .get() requires ? check, getOrDefault() always safe, no crash paths.

absent access check default get getOrDefault guard isSet isset null null-safe optional safe unwrap

Explanation: Calling .get() on an Optional without first checking with ? triggers E08030 — has not been checked before access. The compiler enforces that you verify the Optional is set before extracting its value. See ek9 -h E08030 for details.

Explanation: Optional does not have a .get(String) method. Use getOrDefault() for safe extraction with a fallback. Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.


Question: How do I safely extract the ok or error value from a Result?

What is the safe way to access Result ok and error values?

How do I get the success or failure value from a Result in EK9?

Answer:
EK9 Results require compiler-enforced guards before accessing ok or error values. The isOk() guard unlocks .ok(), and the isError() guard unlocks .error(). Neither unlocks the other.

ISOK GUARD THEN OK
Check isOk() before accessing the ok value:
  if r.isOk()
    okVal <- r.ok()
The compiler verifies isOk() was checked before allowing .ok().

ISERROR GUARD THEN ERROR
Check isError() before accessing the error value:
  if r.isError()
    errVal <- r.error()

DUAL GUARD PATTERN
Handle both sides independently:
  if r.isOk()
    process(r.ok())
  else if r.isError()
    handleError(r.error())

OKORDEFAULT AND ERRORORDEFAULT
Extract with a fallback, no guard needed:
  okVal <- r.okOrDefault("fallback")
  errVal <- r.errorOrDefault(-1)
Always returns a usable value.

TERNARY GUARD
Compact single-expression extraction:
  okVal <- r.isOk() <- r.ok() else "default"
  errVal <- r.isError() <- r.error() else 0

See Q48 for Result basics. See Q86 for Result guard patterns. See Q87 for Result operations and callbacks.

defines module qa.safeaccess.resultsafe

  defines function

    getOkResult()
      <- rtn <- Result("Success", Integer())

    getErrorResult()
      <- rtn <- Result(String(), 42)

  defines program
    ResultExtractDemo()
      stdout <- Stdout()

      // === ISOK GUARD THEN OK ===

      r1 <- getOkResult()
      if r1.isOk()
        okVal <- r1.ok()
        stdout.println(`Ok value: ${okVal}`)

      // === ISERROR GUARD THEN ERROR ===

      r2 <- getErrorResult()
      if r2.isError()
        errVal <- r2.error()
        stdout.println(`Error value: ${errVal}`)

      // === DUAL GUARD PATTERN ===

      r3 <- getOkResult()
      if r3.isOk()
        stdout.println(`Dual ok: ${r3.ok()}`)
      else if r3.isError()
        stdout.println(`Dual error: ${r3.error()}`)

      // === ? OPERATOR (SHORTHAND FOR ISOK) ===

      r4 <- getOkResult()
      if r4?
        stdout.println(`? guard ok: ${r4.ok()}`)

      // === OKORDEFAULT / ERRORORDEFAULT ===

      r5 <- getOkResult()
      safeOk <- r5.okOrDefault("fallback")
      stdout.println(`okOrDefault (ok result): ${safeOk}`)

      r6 <- getErrorResult()
      safeOk2 <- r6.okOrDefault("fallback")
      stdout.println(`okOrDefault (error result): ${safeOk2}`)

      safeErr <- r6.errorOrDefault(-1)
      stdout.println(`errorOrDefault: ${safeErr}`)

      // === TERNARY GUARD ===

      r7 <- getOkResult()
      ternaryOk <- r7.isOk() <- r7.ok() else "default"
      stdout.println(`Ternary ok: ${ternaryOk}`)

      r8 <- getErrorResult()
      ternaryErr <- r8.isError() <- r8.error() else 0
      stdout.println(`Ternary error: ${ternaryErr}`)

Java: try-catch for error handling, no Result type, exceptions can be silently ignored. Python: try/except, no compile-time enforcement. Rust: Result.unwrap() panics, pattern matching enforced by compiler. Go: val, err := fn(); if err != nil is convention not enforcement. Kotlin: Result.getOrElse() for defaults, no compile-time guard enforcement. EK9: isOk()/isError() are compiler-enforced guards, okOrDefault()/errorOrDefault() for safe defaults, no escape hatches.

access debug default error extract guard isError isOk isset null-safe ok okOrDefault result safe unwrap

Explanation: Result does not have an isValid() method. The correct method for checking the success state is isOk(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.


Question: Why don't Dict and List have a .get() method in EK9?

Why does EK9 force me to use getOrDefault instead of get?

Why is there no .get() on Dict or List in EK9?

What is the design philosophy behind getOrDefault in EK9?

Answer:
EK9 deliberately omits .get() on Dict and List because it forces a fallback value, eliminating null returns entirely. This is a design philosophy choice, not an oversight.

THE PROBLEM WITH GET
In Java, map.get(key) returns null if the key is missing. This creates silent null propagation: the null travels through your code until it eventually causes a NullPointerException far from the original lookup. In Python, dict[key] throws KeyError. In Go, accessing a missing map key returns the zero value with no indication it was missing.

THE GETORDEFAULT SOLUTION
By requiring a default value, EK9 ensures every lookup returns a meaningful, usable result:
  ages.getOrDefault("Alice", 0)
You must think about the missing-key case at the point of access. The compiler cannot let you forget.

CONSISTENT ACROSS ALL TYPES
The same pattern works on Dict, List, Optional, and Result:
  dict.getOrDefault(key, default)
  list.getOrDefault(index, default)
  optional.getOrDefault(default)
  result.okOrDefault(default)
One mental model for safe access everywhere.

See Q46 for Dict basics. See Q45 for List basics. See Q47 for Optional. See Q48 for Result. See Q166 for the consistent safe-access pattern. See Q168 for fallback values. See Q169 for callbacks.

defines module qa.safeaccess.designphilosophy

  defines program
    WhyNoGetDemo()
      stdout <- Stdout()

      // === THE GETORDEFAULT APPROACH ===

      // Dict: always provide a fallback
      ages <- {"Alice": 30, "Bob": 25}
      aliceAge <- ages.getOrDefault("Alice", 0)
      stdout.println(`Alice: ${aliceAge}`)

      unknownAge <- ages.getOrDefault("Unknown", 0)
      stdout.println(`Unknown (default 0): ${unknownAge}`)

      // List: always provide a fallback
      numbers <- [10, 20, 30]
      first <- numbers.getOrDefault(0, -1)
      stdout.println(`First: ${first}`)

      outOfBounds <- numbers.getOrDefault(99, -1)
      stdout.println(`Out of bounds (default -1): ${outOfBounds}`)

      // Optional: always provide a fallback
      opt <- Optional("Hello")
      optVal <- opt.getOrDefault("default")
      stdout.println(`Optional: ${optVal}`)

      emptyOpt <- Optional() of String
      emptyVal <- emptyOpt.getOrDefault("default")
      stdout.println(`Empty optional: ${emptyVal}`)

      // Result: always provide a fallback
      okResult <- Result("Success", Integer())
      okVal <- okResult.okOrDefault("fallback")
      stdout.println(`Result ok: ${okVal}`)

      errResult <- Result(String(), 42)
      errOk <- errResult.okOrDefault("fallback")
      stdout.println(`Error result okOrDefault: ${errOk}`)

Java: map.get() returns null (NPE risk), map.getOrDefault() added in Java 8 but optional. Python: dict[key] throws KeyError, dict.get(key, default) is the safe form. Rust: HashMap.get() returns Option, forces handling. Go: map[key] returns zero value silently, comma-ok idiom for detection. JavaScript: obj[key] returns undefined, no built-in safe access. EK9: no .get() exists, getOrDefault() is the only access method, forces explicit handling of missing keys.

access crash design dict exception function get getOrDefault guard list migrate missing null null-safe philosophy safe why

Explanation: Dict does not have a .get() method — only getOrDefault(). Calling .get() triggers E50060 — method not resolved. This is by design: getOrDefault forces you to handle the missing-key case at the point of access. See ek9 -h E50060 for details.

Explanation: Optional does have .get() but it requires a ? guard check first. Calling .get() without checking triggers E08030 — has not been checked before access. Use getOrDefault() for guard-free access. See ek9 -h E08030 for details.


Question: What is the consistent safe-access pattern across all EK9 types?

How do I use the same safe access pattern on Dict, List, Optional, and Result?

Is there a unified way to safely access values in EK9?

How does getOrDefault work across different EK9 types?

Answer:
EK9 provides a consistent safe-access pattern across Dict, List, Optional, and Result. The same mental model applies everywhere: provide a default, get a usable value.

DICT: GETORDEFAULT WITH KEY
  dict.getOrDefault(key, default)
Lookup by key with fallback.

LIST: GETORDEFAULT WITH INDEX
  list.getOrDefault(index, default)
Lookup by position with fallback.

OPTIONAL: GETORDEFAULT
  optional.getOrDefault(default)
Extract contained value or use fallback.

RESULT: OKORDEFAULT / ERRORORDEFAULT
  result.okOrDefault(default)
  result.errorOrDefault(default)
Extract ok or error value with fallback.

GUARD PATTERN
All types support the ? operator for guard checks:
  if dict contains key then access
  if opt? then opt.get()
  if result.isOk() then result.ok()

WHY CONSISTENCY MATTERS
One pattern to learn, works everywhere. No need to remember different APIs for different container types. New developers learn the pattern once and apply it across the entire language.

See Q161 for Dict safe access. See Q162 for List safe access. See Q163 for Optional unwrap. See Q164 for Result extraction. See Q165 for why there is no .get() method.

defines module qa.safeaccess.consistentpattern

  defines program
    ConsistentPatternDemo()
      stdout <- Stdout()

      // === DICT: getOrDefault(key, default) ===

      config <- {"host": "localhost", "port": "8080"}
      host <- config.getOrDefault("host", "127.0.0.1")
      timeout <- config.getOrDefault("timeout", "30")
      stdout.println(`Host: ${host}`)
      stdout.println(`Timeout (default): ${timeout}`)

      // === LIST: getOrDefault(index, default) ===

      args <- ["run", "test", "verbose"]
      command <- args.getOrDefault(0, "help")
      missing <- args.getOrDefault(10, "none")
      stdout.println(`Command: ${command}`)
      stdout.println(`Missing arg: ${missing}`)

      // === OPTIONAL: getOrDefault(default) ===

      userName <- Optional("Steve")
      displayName <- userName.getOrDefault("Anonymous")
      stdout.println(`User: ${displayName}`)

      noUser <- Optional() of String
      fallbackName <- noUser.getOrDefault("Anonymous")
      stdout.println(`No user: ${fallbackName}`)

      // === RESULT: okOrDefault(default) ===

      okResult <- Result("Data loaded", Integer())
      msg <- okResult.okOrDefault("No data")
      stdout.println(`Result msg: ${msg}`)

      errResult <- Result(String(), 404)
      errMsg <- errResult.okOrDefault("No data")
      stdout.println(`Error result msg: ${errMsg}`)

      errCode <- errResult.errorOrDefault(0)
      stdout.println(`Error code: ${errCode}`)

Java: different APIs per type (Map.get, List.get, Optional.orElse, no Result). Python: dict.get(), list[i] (throws), no Optional. Rust: HashMap.get() returns Option, Vec.get() returns Option, unified Option/Result but different APIs. Go: comma-ok for maps, panic for slices, no Optional/Result. Kotlin: different null-safe operators per context. EK9: getOrDefault() everywhere, one pattern for all container types.

absent access consistent dict error getOrDefault guard isset list null-safe ok optional pattern result safe unified

Explanation: Dict does not have a .get() method. Use getOrDefault() with a fallback value. Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.

Explanation: Optional.get() requires a ? guard check first. Calling .get() without checking triggers E08030 — has not been checked before access. Use getOrDefault() for guard-free access. See ek9 -h E08030 for details.


Question: How do I iterate over Dict entries safely without worrying about missing keys?

How do I loop over all key-value pairs in an EK9 Dict?

How do I iterate over Dict keys and values separately in EK9?

Answer:
EK9 Dicts provide three iteration approaches: for-in over entries, .keys() iterator, and .values() iterator. All are safe because every yielded item is guaranteed to be set.

FOR-IN OVER ENTRIES
The simplest and most common pattern:
  for entry in dict
    stdout.println(`${entry.key()}: ${entry.value()}`)
Each entry is a DictEntry with .key() and .value() accessors.

KEYS ITERATOR
Iterate over just the keys:
  keyIter <- dict.keys()
  while keyIter?
    stdout.println(keyIter.next())

VALUES ITERATOR
Iterate over just the values:
  valIter <- dict.values()
  while valIter?
    stdout.println($valIter.next())

STREAM PIPELINE
Use cat with a Dict for stream processing:
  cat dict | map with entryToString | collect as List of String
Streams over DictEntry items.

See Q46 for Dict basics. See Q90 for Dict operations including keys/values. See Q89 for stream pipelines.

See Q46 for dict. See Q166 for consistent safe pattern.

defines module qa.safeaccess.dictiteration

  defines function

    entryToString() as pure
      -> entry as DictEntry of (String, Integer)
      <- rtn as String: `${entry.key()} = ${entry.value()}`

  defines program
    DictIterationDemo()
      stdout <- Stdout()

      scores <- {"Alice": 95, "Bob": 87, "Charlie": 92}

      // === FOR-IN OVER ENTRIES ===

      stdout.println("All entries:")
      for entry in scores
        stdout.println(`  ${entry.key()}: ${entry.value()}`)

      // === KEYS ITERATOR ===

      stdout.println("Keys:")
      keyIter <- scores.keys()
      while keyIter?
        stdout.println(`  ${keyIter.next()}`)

      // === VALUES ITERATOR ===

      stdout.println("Values:")
      valIter <- scores.values()
      while valIter?
        stdout.println(`  ${valIter.next()}`)

      // === STREAM PIPELINE ===

      formatted <- cat scores | map with entryToString | collect as List of String
      stdout.println(`Formatted: ${formatted}`)

      // === SAFE: EMPTY DICT ITERATION ===

      emptyDict <- Dict() of (String, Integer)
      for entry in emptyDict
        stdout.println("Should not print")
      stdout.println("Empty dict iteration completed safely")

Java: map.entrySet().forEach(), map.keySet(), map.values(). Python: for k, v in dict.items(), dict.keys(), dict.values(). Rust: for (k, v) in map.iter(). Go: for k, v := range m. JavaScript: Object.entries(), Object.keys(), Object.values(). EK9: for entry in dict yields DictEntry, .keys()/.values() return iterators, cat dict for stream pipelines.

DictEntry access dict entries for guard iterate keys loop null-safe safe stream values

Explanation: DictEntry requires two type parameters (key and value types). Providing only one triggers E06020 — incorrect number of parameters supplied. Use DictEntry of (KeyType, ValueType). See ek9 -h E06020 for details.


Question: How do I provide a fallback value when something might be unset?

How do I set a default value for an unset variable in EK9?

What is the guarded assignment operator in EK9?

Answer:
EK9 provides several mechanisms for fallback values: the :=? guarded assignment, getOrDefault(), and the ternary guard pattern.

GUARDED ASSIGNMENT (:=?)
Only assigns if the target is currently unset:
  name <- String()
  name :=? "default"
If name was unset, it becomes "default". If already set, unchanged. This is ideal for layered defaults.

LAYERED DEFAULTS
Apply multiple fallback layers:
  setting <- String()
  setting :=? userPreference
  setting :=? systemDefault
  setting :=? "hardcoded"
Each :=? only applies if the variable is still unset.

GETORDEFAULT
Extract from containers with a fallback:
  dict.getOrDefault(key, default)
  list.getOrDefault(index, default)
  optional.getOrDefault(default)

TERNARY GUARD
Single-expression fallback:
  name <- opt? <- opt.get() else "default"

GUARD IN IF
Conditional processing with fallback in else:
  if val <- getOptional()
    stdout.println(val.get())
  else
    stdout.println("No value available")

See Q22 for variable declaration. See Q29 for tri-state semantics. See Q79 for guarded assignment. See Q84 for Optional ternary guard.

See Q29 for unset variables. See Q166 for consistent safe pattern. See Q243 for coalescing operators.

defines module qa.safeaccess.fallback

  defines program
    FallbackValuesDemo()
      stdout <- Stdout()

      // === GUARDED ASSIGNMENT (:=?) ===

      name <- String()
      stdout.println(`Before guard: isSet=${name?}`)

      name :=? "default"
      stdout.println(`After first guard: ${name}`)

      name :=? "other"
      stdout.println(`After second guard (unchanged): ${name}`)

      // === LAYERED DEFAULTS ===

      // Simulate layered configuration
      userPref <- String()
      systemDefault <- String()
      hardcoded <- "en-US"

      locale <- String()
      locale :=? userPref
      locale :=? systemDefault
      locale :=? hardcoded
      stdout.println(`Locale (from hardcoded): ${locale}`)

      // Now with user preference set
      locale2 <- String()
      userPref2 <- "fr-FR"
      locale2 :=? userPref2
      locale2 :=? "en-US"
      stdout.println(`Locale (from user): ${locale2}`)

      // === GETORDEFAULT ON CONTAINERS ===

      config <- {"debug": "true"}
      debugMode <- config.getOrDefault("debug", "false")
      verboseMode <- config.getOrDefault("verbose", "false")
      stdout.println(`Debug: ${debugMode}`)
      stdout.println(`Verbose: ${verboseMode}`)

      items <- [100, 200, 300]
      firstItem <- items.getOrDefault(0, -1)
      missingItem <- items.getOrDefault(99, -1)
      stdout.println(`First: ${firstItem}`)
      stdout.println(`Missing: ${missingItem}`)

      // === TERNARY GUARD ===

      opt <- Optional("Hello")
      greeting <- opt? <- opt.get() else "Hi"
      stdout.println(`Greeting: ${greeting}`)

      emptyOpt <- Optional() of String
      fallbackGreeting <- emptyOpt? <- emptyOpt.get() else "Hi"
      stdout.println(`Fallback greeting: ${fallbackGreeting}`)

Java: ternary operator (x != null ? x : default), Optional.orElse(), no guarded assignment. Python: x = x or default (falsy gotcha), x if x is not None else default. Rust: unwrap_or(), unwrap_or_else() for lazy defaults. Go: if x == nil { x = default }. Kotlin: ?: elvis operator for null defaults. Swift: ?? nil coalescing for optional defaults, if let with else for guarded fallback. JavaScript: ?? nullish coalescing, || logical or. EK9: :=? guarded assignment for layered defaults, getOrDefault() on containers, ternary guard pattern.

access assignment chain default fallback getOrDefault guarded isset layered null-safe priority safe swift ternary unset

Explanation: Guarded assignment :=? requires the value type to match the variable type. Assigning an Integer to a String variable triggers E50030 — types are not compatible. Ensure the fallback value matches the declared type. See ek9 -h E50030 for details.


Question: How do I process an Optional or Result value using callbacks?

How do whenPresent and whenOk work in EK9?

How do I use callbacks with Optional and Result in EK9?

What is the reactive pattern for Optional and Result in EK9?

Answer:
EK9 Optional and Result support callback-based processing using whenPresent, whenOk, and whenError. These accept Consumer (pure, read-only) or Acceptor (non-pure, can mutate) functions.

OPTIONAL WHENPRESENT
Process the contained value only if present:
  optional.whenPresent(myConsumer)
The callback is never invoked if the Optional is empty. Safe by design.

RESULT WHENOK AND WHENERROR
Process ok or error values independently:
  result.whenOk(okConsumer)
  result.whenError(errorConsumer)
Each callback fires only if its corresponding value is present.

CONSUMER VS ACCEPTOR
Consumer is pure (read-only, no side effects). Acceptor can perform I/O or mutation:
  optional.whenPresent(pureProcessor)      Consumer
  result.whenOk(sideEffectProcessor)       Acceptor (when non-pure)

WHEN TO USE CALLBACKS VS GUARDS
Callbacks are cleaner when you just need to process the value. Guards are better when you need the value for further computation.

See Q54 for Consumer and Acceptor function types. See Q84 for Optional operations. See Q87 for Result operations.

See Q52 for dynamic functions. See Q51 for abstract functions.

defines module qa.safeaccess.callbacks

  defines function

    logString() as pure
      -> content as String
      require content?

    logInteger() as pure
      -> code as Integer
      require code?

  defines program
    CallbacksDemo()
      stdout <- Stdout()

      // === OPTIONAL WHENPRESENT ===

      filled <- Optional("Hello")
      filled.whenPresent(logString)
      stdout.println("whenPresent on filled Optional: callback fired")

      emptyOpt <- Optional() of String
      emptyOpt.whenPresent(logString)
      stdout.println("whenPresent on empty Optional: callback NOT fired")

      // === RESULT WHENOK ===

      okResult <- Result("Success", Integer())
      okResult.whenOk(logString)
      stdout.println("whenOk on ok Result: callback fired")

      errResult <- Result(String(), 42)
      errResult.whenOk(logString)
      stdout.println("whenOk on error Result: callback NOT fired")

      // === RESULT WHENERROR ===

      errResult.whenError(logInteger)
      stdout.println("whenError on error Result: callback fired")

      okResult.whenError(logInteger)
      stdout.println("whenError on ok Result: callback NOT fired")

      // === BOTH CALLBACKS ON DUAL-VALUE RESULT ===

      bothResult <- Result("Partial", -1)
      bothResult.whenOk(logString)
      bothResult.whenError(logInteger)
      stdout.println("Both callbacks on dual-value Result")

      // === EMPTY RESULT — NEITHER FIRES ===

      emptyResult <- Result() of (String, Integer)
      emptyResult.whenOk(logString)
      emptyResult.whenError(logInteger)
      stdout.println("Empty Result: neither callback fires")

Java: Optional.ifPresent(consumer), no Result type. Python: no callbacks on Optional (no Optional type). Rust: Option.map(), Result.map()/map_err() for transformation, no direct callback. Go: no Optional/Result, manual if checks. Kotlin: let/also/run scope functions, Result.onSuccess()/onFailure(). EK9: whenPresent for Optional, whenOk/whenError for Result, Consumer (pure) vs Acceptor (non-pure).

absent acceptor access callback consumer error guard null-safe ok optional process reactive result safe whenError whenOk whenPresent

Explanation: Optional does not have an ifPresent() method. The correct method name is whenPresent(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.


Question: How do I get the length of a string in EK9?

How do I count the characters in a string in EK9?

What is the EK9 equivalent of string.length()?

How do I check if a string is empty in EK9?

Answer:
EK9 provides two ways to get string length and a dedicated empty check.

LENGTH OPERATOR
Prefix operator syntax:
  len <- length myString
Returns the number of characters.

METHOD SYNTAX
  len <- myString.length()
Equivalent to the operator form.

EMPTY CHECK
Test whether a string has no characters:
  if myString is empty
    stdout.println("No content")
Note: an empty string (length 0) IS set. An unset string is different from an empty string.

See Q37 for comprehensive string operations. See Q29 for the distinction between empty and unset. See Q242 for conversion and introspection operators including length.

defines module qa.stringops.length

  defines program
    StringLengthDemo()
      stdout <- Stdout()

      greeting <- "Hello World"

      // === LENGTH OPERATOR ===

      len1 <- length greeting
      stdout.println(`Length (operator): ${len1}`)

      // === METHOD SYNTAX ===

      len2 <- greeting.length()
      stdout.println(`Length (method): ${len2}`)

      // === EMPTY CHECK ===

      emptyStr <- ""
      stdout.println(`Empty string length: ${length emptyStr}`)
      stdout.println(`Is empty: ${emptyStr is empty}`)

      // Empty string IS set (not unset)
      require emptyStr?
      stdout.println(`Empty string isSet: ${emptyStr?}`)

      // Non-empty check
      if ~greeting is empty
        stdout.println("Greeting has content")

      // === UNSET STRING ===

      unsetStr <- String()
      stdout.println(`Unset string isSet: ${unsetStr?}`)

Java: str.length(), str.isEmpty(). Python: len(str), not str for empty check. Rust: str.len(), str.is_empty(). Go: len(str). JavaScript: str.length property. Kotlin: str.length, str.isEmpty(). EK9: length str or str.length(), str is empty.

characters count empty length size string text

Explanation: String has no size() method. Use the length prefix operator or .length() method. See ek9 -h E50060 for details.


Question: How do I check if a string contains a substring in EK9?

How do I search for text within a string in EK9?

What is the EK9 equivalent of string.contains()?

How do I check if a string includes a substring in EK9?

Answer:
EK9 uses the contains operator for substring checks and matches for regex pattern matching.

CONTAINS OPERATOR
Check if a string contains a substring:
  if sentence contains "fox"
    stdout.println("Found fox")
Returns Boolean. Case-sensitive.

IS IN / IS NOT IN
Natural language syntax:
  if "fox" is in sentence
    stdout.println("Found")
  if "wolf" is not in sentence
    stdout.println("Not found")

MATCHES (REGEX)
For pattern-based searching:
  if sentence matches /[Ff]ox/
    stdout.println("Matches fox pattern")

See Q37 for string basics. See Q33 for regular expressions.

defines module qa.stringops.contains

  defines program
    StringContainsDemo()
      stdout <- Stdout()

      sentence <- "The Quick Brown Fox Jumps"

      // === CONTAINS OPERATOR ===

      stdout.println(`Contains 'Brown': ${sentence contains "Brown"}`)
      stdout.println(`Contains 'wolf': ${sentence contains "wolf"}`)

      // === IS IN / IS NOT IN ===

      if "Quick" is in sentence
        stdout.println("Found Quick")

      if "wolf" is not in sentence
        stdout.println("wolf not found")

      // === MATCHES (REGEX) ===

      if sentence matches /.*Fox.*/
        stdout.println("Matches Fox pattern")

      if sentence matches /.*[Bb]rown.*/
        stdout.println("Matches Brown pattern (case flexible)")

      // Case-sensitive contains
      stdout.println(`Contains 'quick' (lowercase): ${sentence contains "quick"}`)
      stdout.println(`Contains 'Quick' (correct case): ${sentence contains "Quick"}`)

Java: str.contains(sub), str.matches(regex). Python: sub in str, re.search(). Rust: str.contains(sub). Go: strings.Contains(str, sub). JavaScript: str.includes(sub), str.match(regex). Kotlin: sub in str, str.contains(sub). EK9: str contains sub, sub is in str, str matches /pattern/.

check contains find includes indexOf match search string substring text

Explanation: String has no indexOf() method. Use the contains operator for substring checks. See ek9 -h E50060 for details.


Question: How do I convert a string to uppercase or lowercase in EK9?

What is the EK9 equivalent of toUpperCase()?

How do I make a string all caps or all lowercase in EK9?

Answer:
EK9 strings have .upperCase() and .lowerCase() methods that return new strings.

UPPERCASE
Convert to all uppercase letters:
  upper <- myString.upperCase()
Returns a new string. The original is unchanged.

LOWERCASE
Convert to all lowercase letters:
  lower <- myString.lowerCase()

CASE-INSENSITIVE COMPARISON
Convert both strings to the same case before comparing:
  if input.lowerCase() == "yes"
    stdout.println("Confirmed")

See Q37 for comprehensive string operations.

See Q37 for strings. See Q170 for string length.

defines module qa.stringops.caseconversion

  defines program
    StringCaseDemo()
      stdout <- Stdout()

      mixed <- "Hello World"

      // === UPPERCASE ===

      upper <- mixed.upperCase()
      stdout.println(`Upper: ${upper}`)

      // === LOWERCASE ===

      lower <- mixed.lowerCase()
      stdout.println(`Lower: ${lower}`)

      // Original unchanged
      stdout.println(`Original: ${mixed}`)

      // === CASE-INSENSITIVE COMPARISON ===

      input <- "YES"
      if input.lowerCase() == "yes"
        stdout.println("Confirmed (case-insensitive)")

      userInput <- "hello"
      if userInput.upperCase() == "HELLO"
        stdout.println("Match (case-insensitive)")

      // === PRACTICAL: NORMALIZE BEFORE LOOKUP ===

      config <- {"debug": "true", "verbose": "false"}
      key <- "DEBUG"
      normalized <- config.getOrDefault(key.lowerCase(), "false")
      stdout.println(`Config ${key}: ${normalized}`)

Java: str.toUpperCase(), str.toLowerCase(). Python: str.upper(), str.lower(). Rust: str.to_uppercase(), str.to_lowercase(). Go: strings.ToUpper(), strings.ToLower(). JavaScript: str.toUpperCase(), str.toLowerCase(). Kotlin: str.uppercase(), str.lowercase(). EK9: str.upperCase(), str.lowerCase().

case convert lower lowercase string text toLowerCase toUpperCase upper uppercase

Explanation: String has no toUpperCase() method. The correct method name in EK9 is upperCase(). See ek9 -h E50060 for details.

stdout.println("Config " + key + ": " + normalized)

Explanation: Concatenating 3 or more string parts with + triggers E11068. Use backtick interpolation instead. See ek9 -h E11068 for details.

Explanation: String has no toUpper() method. The correct EK9 method is upperCase(). Use 'ek9 -h String' to see the full API. See ek9 -h E50060 for details.


Question: How do I trim whitespace from a string in EK9?

How do I remove leading and trailing spaces from a string in EK9?

What is the EK9 equivalent of string.trim()?

How do I strip whitespace from a string in EK9?

Answer:
EK9 strings have .trim() for whitespace removal and .trim(char) for removing a specific character.

TRIM WHITESPACE
Remove leading and trailing whitespace:
  cleaned <- myString.trim()
Returns a new string with spaces, tabs, and newlines removed from both ends.

TRIM SPECIFIC CHARACTER
Remove a specific character from both ends:
  unquoted <- myString.trim('"')
Useful for removing surrounding quotes or delimiters.

COMBINE WITH PADDING
Trim is the complement of padding:
  padded <- myString.rightPadded(30)
  trimmed <- padded.trim()

See Q37 for comprehensive string operations. See Q176 for string padding. See Q172 for string case. See Q174 for string concat. See Q175 for string transform.

defines module qa.stringops.trim

  defines program
    StringTrimDemo()
      stdout <- Stdout()

      // === TRIM WHITESPACE ===

      padded <- "  Hello World  "
      trimmed <- padded.trim()
      stdout.println(`Before: [${padded}]`)
      stdout.println(`After trim: [${trimmed}]`)

      // === TRIM SPECIFIC CHARACTER ===

      quoted <- "\"Hello World\""
      unquoted <- quoted.trim('"')
      stdout.println(`Quoted: ${quoted}`)
      stdout.println(`Unquoted: ${unquoted}`)

      dashed <- "---Title---"
      undashed <- dashed.trim('-')
      stdout.println(`Dashed: ${dashed}`)
      stdout.println(`Undashed: ${undashed}`)

      // === COMPLEMENT OF PADDING ===

      original <- "Test"
      rightPad <- original.rightPadded(20)
      stdout.println(`Right padded: [${rightPad}]`)

      backToOriginal <- rightPad.trim()
      stdout.println(`Trimmed back: [${backToOriginal}]`)

Java: str.trim(), str.strip() (Java 11+). Python: str.strip(), str.strip(chars). Rust: str.trim(), str.trim_matches(char). Go: strings.TrimSpace(), strings.Trim(). JavaScript: str.trim(). Kotlin: str.trim(), str.trim(char). EK9: str.trim(), str.trim(char).

clean leading spaces string strip text trailing trim whitespace

Explanation: String has no strip() method. The correct method name in EK9 is trim(). See ek9 -h E50060 for details.

Explanation: String has no trimLeft() method. EK9 provides trim() for both ends and trim(char) for a specific character. Use 'ek9 -h String' to see the full API. See ek9 -h E50060 for details.


Question: How do I join or concatenate strings in EK9?

How do I combine strings together in EK9?

What is the EK9 equivalent of string concatenation?

How do I embed expressions inside EK9 strings?

Answer:
EK9 provides several ways to combine strings: backtick interpolation (preferred), the + operator (for simple cases), += append, and stream collect.

BACKTICK INTERPOLATION (preferred)
Embed expressions in backtick strings:
  stdout.println(`Welcome ${name}, you are ${age} years old`)
Any expression inside ${...} is evaluated and converted to string. Interpolation compiles to a single allocation via a bespoke STRING_INTERPOLATION IR instruction. Always prefer this for 3+ parts.

PLUS OPERATOR (2-part only)
Concatenate two strings with +:
  full <- first + second
Creates a new string from two parts. Chains of 3 or more parts (e.g. a + b + c) trigger compiler error E11068 because each + creates an intermediate String object. Use interpolation instead:
  BAD:  result <- first + " " + second
  GOOD: result <- `${first} ${second}`

APPEND (+=)
Mutate a string variable by appending:
  greeting <- "Hello"
  greeting += " World"

STREAM COLLECT
Join a list of strings via stream pipeline:
  combined <- cat ["Hello", " ", "World"] | collect as String

See Q37 for string basics. See Q43 for escape sequences in interpolation. See Q309 for the E11068 compiler error details.

defines module qa.stringops.concat

  defines program
    StringConcatDemo()
      stdout <- Stdout()

      first <- "Hello"
      second <- "World"

      // === BACKTICK INTERPOLATION (preferred) ===

      // Interpolation compiles to a single allocation
      combined <- `${first} ${second}`
      stdout.println(`Interpolated: ${combined}`)

      name <- "Steve"
      age <- 30
      stdout.println(`Name: ${name}, Age: ${age}`)

      // Expressions inside interpolation
      stdout.println(`Upper: ${name.upperCase()}`)
      stdout.println(`Length: ${length name}`)

      // === PLUS OPERATOR (2-part only) ===

      // Two-part concatenation is allowed
      twoPartConcat <- first + second
      stdout.println(`Two-part: ${twoPartConcat}`)

      // Three or more parts would trigger E11068:
      //   result <- first + " " + second   // Error!
      // Use interpolation instead:
      //   result <- `${first} ${second}`    // Correct

      // === APPEND (+=) ===

      greeting <- "Hello"
      greeting += " "
      greeting += "World"
      stdout.println(`Appended: ${greeting}`)

      // === STREAM COLLECT ===

      parts <- ["Hello", " ", "World", "!"]
      joined <- cat parts | collect as String
      stdout.println(`Stream joined: ${joined}`)

      // === BUILDING FROM MULTIPLE VALUES ===

      host <- "localhost"
      port <- 8080
      url <- `http://${host}:${port}`
      stdout.println(`URL: ${url}`)

      // Interpolation is cleaner for mixed types
      stdout.println(`URL: http://${host}:${port}`)

Java: + operator, String.concat(), StringBuilder, String.format(). Python: + operator, f-strings, .join(). Rust: format!() macro, + operator, .push_str(). Go: + operator, fmt.Sprintf(). JavaScript: + operator, template literals. Kotlin: + operator, string templates. EK9: backtick interpolation (preferred), + operator (2-part only, 3+ is E11068), += append, stream collect.

E11068 append combine concat concatenate interpolation join merge plus string text


Question: How do I replace or transform text in a string in EK9?

How do I substitute text in a string in EK9?

How do I transform characters in a string in EK9?

Answer:
EK9 strings do not have a .replace() method. Instead, use split + stream rejoin for pattern-based replacement, or stream characters with map/filter for character-level transformations.

SPLIT AND REJOIN
Break at a pattern and collect back together:
  parts <- sentence.split(/,/)
Split returns a List of String. Rejoin the parts using stream collect:
  rejoined <- cat parts | collect as String

CHARACTER STREAMING
Strings are streamable as character sequences. Transform each character:
  cat myString | map with toUpper | collect as String
Filter out specific characters:
  cat myString | filter by isNotSpace | collect as String

WHY NO REPLACE
EK9's philosophy is that strings are streamable sequences. Rather than adding dozens of specialized methods, EK9 leverages the universal stream pipeline for transformations.

See Q37 for string streaming. See Q33 for regular expressions. See Q89 for stream pipelines.

defines module qa.stringops.transform

  defines function

    isNotComma() as pure
      -> ch as Character
      <- rtn as Boolean: ch <> ','

    charToUpper() as pure
      -> ch as Character
      <- rtn as String: $ch.upperCase()

    isLetter() as pure
      -> ch as Character
      <- rtn as Boolean: $ch matches /[A-Za-z]/

  defines program
    StringTransformDemo()
      stdout <- Stdout()

      // === SPLIT AND REJOIN ===

      csv <- "Alice,Bob,Charlie"
      parts <- csv.split(/,/)
      stdout.println(`Split: ${parts}`)

      // Rejoin without commas
      rejoined <- cat parts | collect as String
      stdout.println(`Rejoined: ${rejoined}`)

      // === CHARACTER STREAMING — FILTER ===

      // Remove commas by filtering characters
      noCommas <- cat csv | filter by isNotComma | collect as String
      stdout.println(`No commas: ${noCommas}`)

      // === CHARACTER STREAMING — MAP ===

      // Transform to uppercase via character stream
      lower <- "hello world"
      upper <- cat lower | map with charToUpper | collect as String
      stdout.println(`Uppercased via stream: ${upper}`)

      // === EXTRACT ONLY LETTERS ===

      messy <- "h3ll0 w0rld!"
      lettersOnly <- cat messy | filter by isLetter | collect as String
      stdout.println(`Letters only: ${lettersOnly}`)

      // === SPLIT WITH REGEX ===

      sentence <- "one  two   three"
      words <- sentence.split(/ +/)
      stdout.println(`Words: ${words}`)

Java: str.replace(), str.replaceAll(regex). Python: str.replace(), re.sub(). Rust: str.replace(), str.replacen(). Go: strings.Replace(), strings.ReplaceAll(). JavaScript: str.replace(), str.replaceAll(). Kotlin: str.replace(), str.replace(regex). EK9: no .replace(), use split(regex) + collect, or stream characters with map/filter.

characters filter map regex replace split stream string substitute text transform

Explanation: String has no replace() method. Use split + stream collect, or stream characters with filter/map for transformations. See ek9 -h E50060 for details.


Question: How do I pad a string to a fixed width in EK9?

How do I align text to a fixed column width in EK9?

What is the EK9 equivalent of string padding?

How do I right-align or left-align text in EK9?

Answer:
EK9 strings have .leftPadded(n) and .rightPadded(n) methods for padding to a fixed width.

RIGHT PADDED
Pad with spaces on the right to reach width n:
  padded <- myString.rightPadded(20)
Useful for left-aligned columns. If the string is already longer than n, it is returned unchanged.

LEFT PADDED
Pad with spaces on the left to reach width n:
  padded <- myString.leftPadded(20)
Useful for right-aligned columns.

FORMATTING COLUMNS
Combine padding for tabular output:
  stdout.println(`${name.rightPadded(15)} ${score.leftPadded(5)}`)

See Q37 for comprehensive string operations. See Q173 for trim (the complement of padding).

defines module qa.stringops.padding

  defines program
    StringPaddingDemo()
      stdout <- Stdout()

      // === RIGHT PADDED (left-aligned) ===

      word <- "Hello"
      rightPad <- word.rightPadded(20)
      stdout.println(`Right padded: [${rightPad}]`)

      // === LEFT PADDED (right-aligned) ===

      leftPad <- word.leftPadded(20)
      stdout.println(`Left padded: [${leftPad}]`)

      // === FORMATTING COLUMNS ===

      names <- ["Alice", "Bob", "Charlie"]
      amounts <- ["1234", "56", "789"]

      idx <- 0
      for name in names
        amount <- amounts.getOrDefault(idx, "0")
        stdout.println(`${name.rightPadded(12)} ${amount.leftPadded(8)}`)
        idx := idx + 1

      // === STRING LONGER THAN WIDTH ===

      longStr <- "A very long string"
      padded <- longStr.rightPadded(5)
      stdout.println(`Long string padded to 5: [${padded}]`)

Java: String.format("%-20s", str) for left-align, String.format("%20s", str) for right-align. Python: str.ljust(20), str.rjust(20). Rust: format!("{:<20}", str), format!("{:>20}", str). Go: fmt.Sprintf("%-20s", str). JavaScript: str.padEnd(20), str.padStart(20). Kotlin: str.padEnd(20), str.padStart(20). EK9: str.rightPadded(20), str.leftPadded(20).

align column fixed format left pad padding right string text width

Explanation: String has no padEnd() method. The correct method names in EK9 are rightPadded() and leftPadded(). See ek9 -h E50060 for details.

stdout.println(name.rightPadded(12) + " " + amount.leftPadded(8))


Question: How do I check if a string starts or ends with a value in EK9?

How do I check for a string prefix in EK9?

How do I check for a string suffix in EK9?

Answer:
EK9 does not have Swift/Go-style .hasPrefix() or .hasSuffix() methods. Use the matches operator with regex anchors for prefix and suffix checks.

STARTS WITH (PREFIX)
Use regex with ^ anchor:
  if myString matches /^Hello.*/
    stdout.println("Starts with Hello")

ENDS WITH (SUFFIX)
Use regex with $ anchor:
  if myString matches /.*World$/
    stdout.println("Ends with World")

FIRST AND LAST CHARACTER
For single character checks, use the first() and last() methods:
  firstChar <- myString.first()
  lastChar <- myString.last()

WHY NO HASPREFIX/HASSUFFIX
EK9 keeps the String API focused. The matches operator with regex provides the same functionality with more flexibility: you can match complex patterns, not just fixed prefixes.

See Q37 for string methods. See Q33 for regular expressions. See Q178 for first/last character access.

See Q37 for strings. See Q171 for string contains.

defines module qa.stringops.startsends

  defines program
    StringStartsEndsDemo()
      stdout <- Stdout()

      greeting <- "Hello World"

      // === STARTS WITH (PREFIX) ===

      if greeting matches /^Hello.*/
        stdout.println("Starts with Hello")

      if greeting matches /^Goodbye.*/
        stdout.println("Should not print")

      // === ENDS WITH (SUFFIX) ===

      if greeting matches /.*World$/
        stdout.println("Ends with World")

      if greeting matches /.*Earth$/
        stdout.println("Should not print")

      // === FIRST AND LAST CHARACTER ===

      firstCh <- greeting.first()
      lastCh <- greeting.last()
      stdout.println(`First character: ${firstCh}`)
      stdout.println(`Last character: ${lastCh}`)

      // === PRACTICAL: FILE EXTENSION CHECK ===

      filename <- "report.csv"
      if filename matches /.*\.csv$/
        stdout.println("CSV file detected")

      // === PRACTICAL: PROTOCOL CHECK ===

      url <- "https://example.com"
      if url matches /^https.*/
        stdout.println("Secure URL")

Java: str.startsWith(prefix), str.endsWith(suffix). Python: str.startswith(prefix), str.endswith(suffix). Rust: str.starts_with(prefix), str.ends_with(suffix). Go: strings.HasPrefix(), strings.HasSuffix(). JavaScript: str.startsWith(), str.endsWith(). Kotlin: str.startsWith(), str.endsWith(). EK9: str matches /^prefix.*/, str matches /.*suffix$/, no hasPrefix()/hasSuffix() methods.

begins ends endsWith matches prefix regex starts startsWith string suffix text

Explanation: String has no hasPrefix() or hasSuffix() methods (common in Swift and Go). Use the matches operator with regex anchors (^, $) for prefix and suffix checks. See ek9 -h E50060 for details.


Question: How do I get the first or last character of a string in EK9?

How do I extract a single character from a string in EK9?

How do I access characters by position in EK9?

Answer:
EK9 provides .first() and .last() methods for accessing the first and last characters. For characters at other positions, use stream skip and head.

FIRST CHARACTER
  firstCh <- myString.first()
Returns a Character. Returns unset Character if the string is empty.

LAST CHARACTER
  lastCh <- myString.last()
Returns the last Character.

CHARACTER AT POSITION
Use stream skip and head to extract a character at a specific index:
  thirdChar <- cat myString | skip 2 | head 1 | collect as String
Skip 2, take 1 gives the character at index 2 (zero-based).

SUBSTRING EXTRACTION
Use skip and head for any substring:
  sub <- cat myString | skip 6 | head 5 | collect as String

See Q37 for streamable strings. See Q38 for the Character type. See Q177 for string starts/ends. See Q179 for string fuzzy match.

defines module qa.stringops.firstlast

  defines program
    StringFirstLastDemo()
      stdout <- Stdout()

      greeting <- "Hello World"

      // === FIRST CHARACTER ===

      firstCh <- greeting.first()
      stdout.println(`First: ${firstCh}`)

      // === LAST CHARACTER ===

      lastCh <- greeting.last()
      stdout.println(`Last: ${lastCh}`)

      // === CHARACTER AT POSITION (via stream) ===

      // Get character at index 6 (zero-based)
      charAtSix <- cat greeting | skip 6 | head 1 | collect as String
      stdout.println(`Char at 6: ${charAtSix}`)

      // === SUBSTRING EXTRACTION ===

      // Extract "World" (skip 6, take 5)
      world <- cat greeting | skip 6 | head 5 | collect as String
      stdout.println(`Substring: ${world}`)

      // Extract first 5 characters
      hello <- cat greeting | head 5 | collect as String
      stdout.println(`First 5: ${hello}`)

      // === EMPTY STRING SAFETY ===

      emptyStr <- ""
      emptyFirst <- emptyStr.first()
      emptyLast <- emptyStr.last()
      stdout.println(`Empty first isSet: ${emptyFirst?}`)
      stdout.println(`Empty last isSet: ${emptyLast?}`)

Java: str.charAt(0), str.charAt(str.length()-1). Python: str[0], str[-1]. Rust: str.chars().next(), str.chars().last(). Go: str[0] (byte, not char). JavaScript: str[0], str.at(-1). Kotlin: str.first(), str.last(). EK9: str.first(), str.last(), stream skip+head for any position.

char character extract first head index last position string tail text

Explanation: String has no charAt() method. Use .first() for the first character, .last() for the last, or stream skip+head for arbitrary positions. See ek9 -h E50060 for details.


Question: How do I compare strings for fuzzy or approximate matching in EK9?

How do I detect typos or approximate matches in EK9?

Answer:
EK9 has a built-in Levenshtein distance operator for fuzzy string comparison. The <~> operator returns the edit distance between two strings as an Integer.

LEVENSHTEIN DISTANCE (<~>)
Compute edit distance between two strings:
  dist <- str1 <~> str2
Returns the minimum number of single-character edits (insertions, deletions, substitutions) needed to transform one string into the other.

IDENTICAL STRINGS
  "hello" <~> "hello" returns 0

SINGLE EDIT
  "kitten" <~> "sitten" returns 1 (one substitution)

PRACTICAL USE: TYPO DETECTION
Use a threshold to detect likely typos:
  if (input <~> expected) <= 2
    stdout.println("Close enough")

REGEX FOR PATTERN MATCHING
For pattern-based matching rather than distance:
  if myString matches /pattern/

See Q37 for string basics. See Q33 for regular expressions.

See Q37 for strings. See Q33 for regular expressions. See Q239 for the <~> fuzzy comparison operator used in ordering and matching.

defines module qa.stringops.fuzzymatch

  defines program
    FuzzyMatchDemo()
      stdout <- Stdout()

      // === LEVENSHTEIN DISTANCE (<~>) ===

      // Identical strings — distance 0
      greeting <- "hello"
      sameGreeting <- "hello"
      dist0 <- greeting <~> sameGreeting
      stdout.println(`hello <~> hello = ${dist0}`)

      // One substitution — distance 1
      original <- "kitten"
      dist1 <- original <~> "sitten"
      stdout.println(`kitten <~> sitten = ${dist1}`)

      // Multiple edits
      dist3 <- original <~> "sitting"
      stdout.println(`kitten <~> sitting = ${dist3}`)

      // Empty string
      planet <- "world"
      distEmpty <- planet <~> ""
      stdout.println(`world <~> empty = ${distEmpty}`)

      // === PRACTICAL: TYPO DETECTION ===

      expected <- "function"
      typo <- "funciton"
      typoDistance <- expected <~> typo
      stdout.println(`Typo distance: ${typoDistance}`)

      typoThreshold <- 2
      if typoDistance <= typoThreshold
        stdout.println("Close enough - likely a typo")

      // === PRACTICAL: FIND CLOSEST MATCH ===

      input <- "colr"
      target1 <- "color"
      target2 <- "collar"
      target3 <- "column"

      d1 <- input <~> target1
      d2 <- input <~> target2
      d3 <- input <~> target3
      stdout.println(`colr <~> color = ${d1}`)
      stdout.println(`colr <~> collar = ${d2}`)
      stdout.println(`colr <~> column = ${d3}`)

      // === REGEX FOR PATTERN MATCHING ===

      testPhrase <- "Hello World"
      if testPhrase matches /.*World$/
        stdout.println("Pattern match: ends with World")

Java: no built-in Levenshtein, requires Apache Commons or custom implementation. Python: no built-in, requires python-Levenshtein or difflib.SequenceMatcher. Rust: no built-in, requires strsim crate. Go: no built-in, requires third-party packages. JavaScript: no built-in, requires libraries. Kotlin: no built-in. EK9: built-in <~> operator returns Levenshtein distance as Integer, no libraries needed.

approximate compare distance fuzzy levenshtein match regex similar string text typo

Explanation: String has no levenshtein() method. Use the <~> operator for Levenshtein distance. See ek9 -h E50060 for details.


Question: How do I add an item to a list in EK9?

How do I append an element to a list in EK9?

What is the EK9 equivalent of list.add()?

How do I push an item onto a list in EK9?

Answer:
EK9 provides two addition operators: += for mutating the list and + for creating a new list.

MUTATING ADD (+=)
Add an item to the existing list:
  numbers += 4
Modifies the list in place.

NEW LIST (+)
Create a new list with the item added:
  extended <- numbers + 4
The original list is unchanged.

ADD MULTIPLE ITEMS
Merge another list using :~: or +:
  numbers :~: moreNumbers
  combined <- numbers + moreNumbers

See Q45 for List basics. See Q88 for List operations. See Q186 for merging collections. See Q181 for list remove.

defines module qa.collectiontasks.listadd

  defines program
    ListAddDemo()
      stdout <- Stdout()

      // === MUTATING ADD (+=) ===

      numbers <- [1, 2, 3]
      stdout.println(`Before: ${numbers}`)

      numbers += 4
      stdout.println(`After += 4: ${numbers}`)

      numbers += 5
      stdout.println(`After += 5: ${numbers}`)

      // === NEW LIST (+) ===

      original <- [10, 20]
      extended <- original + 30
      stdout.println(`Original: ${original}`)
      stdout.println(`Extended: ${extended}`)

      // === ADD MULTIPLE ITEMS (:~:) ===

      base <- [1, 2, 3]
      extras <- [4, 5, 6]
      base :~: extras
      stdout.println(`After merge: ${base}`)

      // === ADD MULTIPLE WITH + ===

      left <- [1, 2]
      right <- [3, 4]
      combined <- left + right
      stdout.println(`Combined: ${combined}`)
      stdout.println(`Left unchanged: ${left}`)

      // === BUILD FROM EMPTY ===

      built <- List() of String
      built += "first"
      built += "second"
      built += "third"
      stdout.println(`Built: ${built}`)

Java: list.add(item), Collections.unmodifiableList for immutable. Python: list.append(item), list.extend(). Rust: vec.push(item). Go: append(slice, item). JavaScript: arr.push(item), [...arr, item] for new array. Kotlin: mutableList.add(item), list + item. EK9: list += item for mutating, list + item for new list.

add append collection element insert item list mutate plus push task

Explanation: EK9 uses the += operator to append items to a list, not an add() method. The operator syntax is consistent across all collection types. See ek9 -h E50060 for details.

Explanation: EK9 uses the :~: merge operator to add all items from one list to another, not an addAll() method. See ek9 -h E50060 for details.


Question: How do I remove an item from a list in EK9?

How do I delete an element from a list in EK9?

What is the EK9 equivalent of list.remove()?

How do I drop an item from a list in EK9?

Answer:
EK9 provides two removal operators: -= for mutating the list and - for creating a new list without the item.

MUTATING REMOVE (-=)
Remove the first occurrence of an item:
  numbers -= 3
Modifies the list in place. If the item is not found, no change.

NEW LIST (-)
Create a new list without the item:
  filtered <- numbers - 3
The original list is unchanged.

STREAM FILTER FOR COMPLEX REMOVAL
Remove items matching a condition using stream pipeline:
  cat numbers | filter by isPositive | collect as List of Integer

See Q45 for List basics. See Q88 for List operations. See Q89 for stream pipelines.

defines module qa.collectiontasks.listremove

  defines function

    isGreaterThanTwo() as pure
      -> num as Integer
      <- rtn <- Boolean()
      expectedSize <- 2
      rtn: num > expectedSize

  defines program
    ListRemoveDemo()
      stdout <- Stdout()

      // === MUTATING REMOVE (-=) ===

      numbers <- [1, 2, 3, 4, 5]
      stdout.println(`Before: ${numbers}`)

      numbers -= 3
      stdout.println(`After -= 3: ${numbers}`)

      // Remove item not in list — no change
      numbers -= 99
      stdout.println(`After -= 99 (no change): ${numbers}`)

      // === NEW LIST (-) ===

      original <- [10, 20, 30, 40]
      without30 <- original - 30
      stdout.println(`Original: ${original}`)
      stdout.println(`Without 30: ${without30}`)

      // === STREAM FILTER FOR COMPLEX REMOVAL ===

      // Keep only items greater than 2
      mixed <- [1, 2, 3, 4, 5]
      filtered <- cat mixed | filter by isGreaterThanTwo | collect as List of Integer
      stdout.println(`Filtered (> 2): ${filtered}`)

      // === REMOVE FROM STRINGS ===

      names <- ["Alice", "Bob", "Charlie"]
      names -= "Bob"
      stdout.println(`After removing Bob: ${names}`)

Java: list.remove(item), list.removeIf(predicate). Python: list.remove(item), list comprehension for filtered. Rust: vec.retain(predicate), vec.remove(index). Go: manual loop and slice manipulation. JavaScript: arr.filter(), splice for in-place. Kotlin: list.minus(item), mutableList.remove(). EK9: list -= item for mutating, list - item for new list, stream filter for complex removal.

collection delete drop element filter item list minus mutate remove task

Explanation: EK9 uses the -= operator to remove items from a list, not a remove() method. See ek9 -h E50060 for details.


Question: How do I check if a list or dict is empty in EK9?

How do I test if a collection has no elements in EK9?

What is the EK9 equivalent of isEmpty()?

How do I check the size of a collection in EK9?

Answer:
EK9 provides the is empty operator and the length operator for checking collection emptiness and size.

IS EMPTY
Test whether a collection has no elements:
  if myList is empty
    stdout.println("No items")
Works on List, Dict, and String.

LENGTH
Get the number of elements:
  count <- length myList
Returns Integer.

IS NOT EMPTY
Negate the empty check:
  if ~myList is empty
    stdout.println("Has items")

CRITICAL: EMPTY IS SET
An empty collection IS set. List() of T creates a valid, set, empty list. Empty does not mean unset. The ? operator returns true for empty collections because they are meaningful values.

See Q29 for tri-state semantics. See Q45 for List basics. See Q46 for Dict basics.

defines module qa.collectiontasks.emptycheck

  defines function
    getList()
      <- rtn as List of Integer: List() of Integer

  defines program
    CollectionEmptyDemo()
      stdout <- Stdout()

      // === LIST IS EMPTY ===

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

      filledList <- [1, 2, 3]
      stdout.println(`Filled list is empty: ${filledList is empty}`)
      stdout.println(`Filled list length: ${length filledList}`)

      // === DICT IS EMPTY ===

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

      filledDict <- {"a": 1, "b": 2}
      stdout.println(`Filled dict is empty: ${filledDict is empty}`)
      stdout.println(`Filled dict length: ${length filledDict}`)

      // === IS NOT EMPTY ===

      if ~filledList is empty
        stdout.println("Filled list has items")

      if emptyList is empty
        stdout.println("Empty list has no items")

      // === CRITICAL: EMPTY IS SET ===

      // Empty list IS set — it is a valid, meaningful value
      require emptyList?
      stdout.println(`Empty list isSet: ${emptyList?}`)

      // Empty dict IS set
      require emptyDict?
      stdout.println(`Empty dict isSet: ${emptyDict?}`)

      // === STRING EMPTY CHECK ===

      emptyStr <- ""
      fullStr <- "Hello"
      stdout.println(`Empty string is empty: ${emptyStr is empty}`)
      stdout.println(`Full string is empty: ${fullStr is empty}`)

Java: list.isEmpty(), list.size(). Python: len(list) == 0, not list for truthiness. Rust: vec.is_empty(), vec.len(). Go: len(slice) == 0. JavaScript: arr.length === 0. Kotlin: list.isEmpty(), list.size. EK9: list is empty, length list, empty collections are set (not unset).

check collection count dict empty length list set size task unset zero

Explanation: EK9 uses the 'is empty' operator to check if a collection has no elements, not an isEmpty() method. See ek9 -h E50060 for details.

Explanation: EK9 uses the prefix 'length' operator to get collection size, not a size() method. See ek9 -h E50060 for details.


Question: How do I get the first or last item from a list in EK9?

How do I peek at the first element of a list in EK9?

How do I access the front or back of a list in EK9?

What is the EK9 equivalent of list.first() or list.last()?

Answer:
EK9 provides getOrDefault(0, default) for the first element and getOrDefault(length-1, default) for the last. Stream head and tail provide alternative approaches.

FIRST ELEMENT
Safe access with getOrDefault:
  first <- myList.getOrDefault(0, defaultVal)
Returns the first element or the default if the list is empty.

LAST ELEMENT
Access the last element by index:
  last <- myList.getOrDefault(length myList - 1, defaultVal)

STREAM HEAD FOR FIRST
Use head 1 in a stream pipeline:
  firstItems <- cat myList | head 1 | collect as List of Integer
Returns a list with 0 or 1 elements.

STREAM TAIL FOR LAST
Use tail 1 in a stream pipeline:
  lastItems <- cat myList | tail 1 | collect as List of Integer

See Q45 for List basics. See Q162 for safe list access. See Q125 for head/tail/skip in streams.

defines module qa.collectiontasks.listfirstlast

  defines program
    ListFirstLastDemo()
      stdout <- Stdout()

      numbers <- [10, 20, 30, 40, 50]

      // === FIRST ELEMENT ===

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

      // === LAST ELEMENT ===

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

      // === STREAM HEAD FOR FIRST ===

      firstViaStream <- cat numbers | head 1 | collect as List of Integer
      stdout.println(`Head 1: ${firstViaStream}`)

      // === STREAM TAIL FOR LAST ===

      lastViaStream <- cat numbers | tail 1 | collect as List of Integer
      stdout.println(`Tail 1: ${lastViaStream}`)

      // === EMPTY LIST SAFETY ===

      emptyList <- List() of Integer
      emptyFirst <- emptyList.getOrDefault(0, -1)
      stdout.println(`Empty first (default -1): ${emptyFirst}`)

      emptyHead <- cat emptyList | head 1 | collect as List of Integer
      stdout.println(`Empty head: ${emptyHead}`)

      // === SECOND ELEMENT ===

      second <- numbers.getOrDefault(1, -1)
      stdout.println(`Second: ${second}`)

Java: list.get(0) throws if empty, list.getFirst()/getLast() in Java 21+. Python: list[0], list[-1] throw IndexError if empty. Rust: vec.first(), vec.last() return Option. Go: slice[0] panics if empty. JavaScript: arr[0], arr.at(-1). Kotlin: list.first(), list.last() throw, list.firstOrNull(). EK9: getOrDefault(0, default) always safe, stream head/tail for pipeline approach.

back collection element first front getOrDefault head last list peek tail task

Explanation: EK9 List has no get() method. Use getOrDefault(index, default) for safe indexed access that always returns a value. See ek9 -h E50060 for details.

Explanation: EK9 List has no first() method. Use getOrDefault(0, default) or stream head 1 to safely access the first element. See ek9 -h E50060 for details.


Question: How do I find the first item matching a condition in EK9?

What is the EK9 equivalent of find() or findFirst()?

How do I search a list for the first match in EK9?

How do I filter a list and take the first result in EK9?

Answer:
EK9 uses stream pipelines with filter and head 1 to find the first matching item.

FILTER + HEAD 1
The standard pattern for finding the first match:
  results <- cat items | filter by predicate | head 1 | collect as List of Integer
Returns a list with 0 or 1 elements. Check if empty to determine whether a match was found.

CHECK RESULT
  if ~results is empty
    found <- results.getOrDefault(0, defaultVal)
    stdout.println(`Found: ${found}`)

WHY HEAD 1
head 1 stops processing after the first match is found. This is EK9's equivalent of early exit: the stream terminates as soon as one element passes through.

See Q89 for stream pipelines. See Q125 for head/tail/skip. See Q162 for safe list access. See Q185 for any/all match.

defines module qa.collectiontasks.findfirst

  defines function

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

    isLong() as pure
      -> str as String
      <- rtn <- Boolean()
      expectedValue <- 4
      rtn: length str > expectedValue

  defines program
    FindFirstMatchDemo()
      stdout <- Stdout()

      // === FILTER + HEAD 1 ===

      numbers <- [1, 3, 5, 4, 6, 8]
      firstEven <- cat numbers | filter by isEven | head 1 | collect as List of Integer
      stdout.println(`First even: ${firstEven}`)

      // === CHECK IF FOUND ===

      if ~firstEven is empty
        found <- firstEven.getOrDefault(0, 0)
        stdout.println(`Found: ${found}`)

      // === NO MATCH FOUND ===

      allOdd <- [1, 3, 5, 7, 9]
      noMatch <- cat allOdd | filter by isEven | head 1 | collect as List of Integer
      stdout.println(`No match result: ${noMatch}`)

      if noMatch is empty
        stdout.println("No even number found")

      // === FIND FIRST IN STRINGS ===

      names <- ["Bob", "Jo", "Alice", "Charlie"]
      longNames <- cat names | filter by isLong | head 1 | collect as List of String
      stdout.println(`First long name: ${longNames}`)

      // === ALL MATCHES (WITHOUT HEAD) ===

      allEvens <- cat numbers | filter by isEven | collect as List of Integer
      stdout.println(`All evens: ${allEvens}`)

Java: list.stream().filter(pred).findFirst() returns Optional. Python: next((x for x in items if pred(x)), default). Rust: iter.find(pred) returns Option. Go: manual loop with break. JavaScript: arr.find(pred). Kotlin: list.find(pred), list.firstOrNull(pred). EK9: cat list | filter by pred | head 1 | collect as List, no break needed.

collection condition filter find first head match pipeline predicate search stream task

Explanation: A predicate function used with 'filter by' must return Boolean. Returning String instead of Boolean triggers E07520 because the stream filter needs a true/false decision. See ek9 -h E07520 for details.


Question: How do I check if any or all items match a condition in EK9?

What is the EK9 equivalent of anyMatch() or allMatch()?

How do I check if every element satisfies a condition in EK9?

How do I check if at least one element matches in EK9?

Answer:
EK9 uses stream pipelines with filter and empty checks to implement any-match and all-match patterns.

ANY MATCH
Check if at least one item matches by filtering and checking for non-empty results:
  matches <- cat items | filter by predicate | head 1 | collect as List of Integer
  if ~matches is empty
    stdout.println("At least one matches")
head 1 stops as soon as one match is found.

ALL MATCH
Check if ALL items match by filtering for the negation and checking for empty results:
  failures <- cat items | filter by negate | head 1 | collect as List of Integer
  if failures is empty
    stdout.println("All items match")
If no items fail the condition, then all items pass.

NONE MATCH
Check if NO items match:
  anyFound <- cat items | filter by predicate | head 1 | collect as List of Integer
  if anyFound is empty
    stdout.println("None match")

See Q89 for stream pipelines. See Q184 for finding the first match. See Q125 for head/tail/skip.

defines module qa.collectiontasks.anyallmatch

  defines function

    isPositive() as pure
      -> num as Integer
      <- rtn as Boolean: num > 0

    isNotPositive() as pure
      -> num as Integer
      <- rtn as Boolean: num <= 0

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

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

  defines program
    AnyAllMatchDemo()
      stdout <- Stdout()

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

      // === ANY MATCH ===

      // Check if any number is even
      anyEven <- cat numbers | filter by isEven | head 1 | collect as List of Integer
      if ~anyEven is empty
        stdout.println("At least one even number found")

      // === ALL MATCH ===

      // Check if ALL numbers are positive
      notPositive <- cat numbers | filter by isNotPositive | head 1 | collect as List of Integer
      if notPositive is empty
        stdout.println("All numbers are positive")

      // === NONE MATCH ===

      // Check if NONE are negative (all positive)
      anyNegative <- cat numbers | filter by isNotPositive | head 1 | collect as List of Integer
      if anyNegative is empty
        stdout.println("No negative numbers found")

      // === ALL EVEN CHECK (will fail) ===

      notEven <- cat numbers | filter by isNotEven | head 1 | collect as List of Integer
      if notEven is empty
        stdout.println("All even")
      else
        stdout.println("Not all numbers are even")

      // === ANY MATCH ON EMPTY LIST ===

      emptyList <- List() of Integer
      anyInEmpty <- cat emptyList | filter by isEven | head 1 | collect as List of Integer
      if anyInEmpty is empty
        stdout.println("Empty list: no matches (any returns false)")

Java: stream.anyMatch(pred), stream.allMatch(pred), stream.noneMatch(pred). Python: any(pred(x) for x in items), all(pred(x) for x in items). Rust: iter.any(pred), iter.all(pred). Go: manual loops. JavaScript: arr.some(pred), arr.every(pred). Kotlin: list.any(pred), list.all(pred), list.none(pred). EK9: filter + head 1 + empty check for any, filter by negation + empty check for all.

all any check collection condition every filter match none predicate some stream task

stdout.display("At least one even number found")

Explanation: Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.


Question: How do I merge two lists or two dicts in EK9?

How do I combine two collections in EK9?

What is the EK9 equivalent of addAll() or putAll()?

How do I join or concatenate two lists in EK9?

Answer:
EK9 provides the :~: merge operator for in-place merging and the + operator for creating new merged collections.

LIST MERGE (:~:)
Append all items from source to target:
  target :~: source
Modifies target in place.

LIST PLUS (+)
Create a new combined list:
  combined <- list1 + list2
Both originals unchanged.

DICT MERGE (:~:)
Merge entries from source into target:
  target :~: source
When keys overlap, the source values win.

DICT PLUS (+)
Create a new merged dict:
  combined <- dict1 + dict2

COPY (:=:)
Deep copy one collection into another:
  target :=: source
Replaces the target contents entirely.

See Q88 for List operations. See Q90 for Dict operations. See Q130 for mutating vs non-mutating operators.

defines module qa.collectiontasks.mergecollections

  defines program
    MergeCollectionsDemo()
      stdout <- Stdout()

      // === LIST MERGE (:~:) ===

      list1 <- [1, 2, 3]
      list2 <- [4, 5, 6]
      list1 :~: list2
      stdout.println(`List merge: ${list1}`)

      // === LIST PLUS (+) ===

      left <- [10, 20]
      right <- [30, 40]
      combined <- left + right
      stdout.println(`List +: ${combined}`)
      stdout.println(`Left unchanged: ${left}`)

      // === DICT MERGE (:~:) ===

      d1 <- {"a": 1, "b": 2}
      d2 <- {"b": 99, "c": 3}
      d1 :~: d2
      stdout.println(`Dict merge: ${d1}`)

      // === DICT PLUS (+) ===

      base <- {"x": 10}
      extra <- {"y": 20, "z": 30}
      merged <- base + extra
      stdout.println(`Dict +: ${merged}`)
      stdout.println(`Base unchanged: ${base}`)

      // === COPY (:=:) ===

      source <- [100, 200, 300]
      target <- List() of Integer
      target :=: source
      stdout.println(`Copied list: ${target}`)

      srcDict <- {"key": 42}
      tgtDict <- Dict() of (String, Integer)
      tgtDict :=: srcDict
      stdout.println(`Copied dict: ${tgtDict}`)

      // === MERGE SINGLE ITEM ===

      names <- ["Alice", "Bob"]
      names :~: "Charlie"
      stdout.println(`After merge single: ${names}`)

Java: list.addAll(other), map.putAll(other), no operator syntax. Python: list.extend(other), dict.update(other), | for dict union. Rust: vec.extend(other), map.extend(other). Go: append(slice1, slice2...), manual loop for maps. JavaScript: [...arr1, ...arr2], Object.assign(). Kotlin: list + other, map + other. EK9: :~: merge operator, + for new combined collection, :=: for deep copy.

append collection combine concatenate copy dict join list merge task union

Explanation: EK9 uses the :~: merge operator for in-place collection merging, not an addAll() method. See ek9 -h E50060 for details.

Explanation: EK9 uses the :~: merge operator for merging Dict entries, not a putAll() method. When keys overlap, source values win. See ek9 -h E50060 for details.


Question: How do I reverse a list in EK9?

How do I flip the order of a list in EK9?

What is the EK9 equivalent of list.reverse()?

How do I invert the order of elements in a list in EK9?

Answer:
EK9 Lists have a .reverse() method that returns a new reversed list. The original list is unchanged.

REVERSE
Create a new list in reverse order:
  reversed <- myList.reverse()
The original list is not modified.

IMMUTABLE BY DEFAULT
Like all EK9 non-mutating operations, reverse() returns a new value. To update the variable, assign the result:
  myList :=: myList.reverse()

See Q88 for List operations. See Q120 for sorting.

defines module qa.collectiontasks.reverselist

  defines program
    ReverseListDemo()
      stdout <- Stdout()

      // === REVERSE ===

      numbers <- [1, 2, 3, 4, 5]
      reversed <- numbers.reverse()
      stdout.println(`Original: ${numbers}`)
      stdout.println(`Reversed: ${reversed}`)

      // Original is unchanged
      require numbers <> reversed

      // === REVERSE STRINGS ===

      names <- ["Alice", "Bob", "Charlie"]
      reversedNames <- names.reverse()
      stdout.println(`Names: ${names}`)
      stdout.println(`Reversed names: ${reversedNames}`)

      // === DOUBLE REVERSE ===

      doubleReversed <- reversed.reverse()
      stdout.println(`Double reversed: ${doubleReversed}`)
      require doubleReversed == numbers

      // === EMPTY LIST REVERSE ===

      emptyList <- List() of Integer
      emptyReversed <- emptyList.reverse()
      stdout.println(`Empty reversed: ${emptyReversed}`)
      require emptyReversed is empty

      // === SINGLE ITEM ===

      single <- [42]
      singleReversed <- single.reverse()
      stdout.println(`Single reversed: ${singleReversed}`)
      require single == singleReversed

Java: Collections.reverse(list) modifies in place, no built-in immutable reverse. Python: list.reverse() modifies in place, reversed(list) returns iterator, list[::-1] for new reversed list. Rust: vec.reverse() modifies in place, .iter().rev() for lazy reverse. Go: manual loop, no built-in reverse. JavaScript: arr.reverse() modifies in place, [...arr].reverse() for new. Kotlin: list.reversed() returns new list. EK9: list.reverse() returns new list, original unchanged.

backwards collection flip invert list order reverse sort task

Explanation: List does not have a flip() method. The correct method for reversing is reverse(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.


Question: How does EK9 handle JSON as a built-in type?

What JSON operations does EK9 support natively?

Answer:
JSON is a first-class built-in type in EK9. You create JSON values directly with constructors and the $$ operator, without importing external libraries.

JSON CONSTRUCTORS
Create JSON values from literals:
  jsonNum <- JSON(42)
  jsonStr <- JSON("hello")
Create JSON objects with key-value pairs:
  jsonObj <- JSON("name", JSON("Alice"))

NATURE CHECKS
JSON values have a nature you can query:
  jsonObj.objectNature()   checks if JSON object
  jsonArr.arrayNature()    checks if JSON array
  jsonVal.valueNature()    checks if JSON primitive

TO-JSON OPERATOR ($$)
The $$ operator converts any value to its JSON representation:
  colour <- #FFAACC
  jsonColour <- $$ colour
This works with all built-in types.

STRING CONVERSION ($)
Convert JSON to its string form:
  text <- $jsonObj

See Q189 for converting objects to JSON. See Q190 for parsing JSON strings. See Q191 for JSON with stream pipelines. See Q37 for String basics. See Q192 for JSON manipulate. See Q193 for JSON output.

defines module qa.jsondata.firstclass

  defines program

    JsonFirstClassDemo()
      stdout <- Stdout()

      // === JSON CONSTRUCTORS ===

      jsonNum <- JSON(42)
      stdout.println(`JSON number: ${jsonNum}`)

      jsonStr <- JSON("hello")
      stdout.println(`JSON string: ${jsonStr}`)

      // JSON object with key-value pair
      jsonObj <- JSON("name", JSON("Alice"))
      stdout.println(`JSON object: ${jsonObj}`)

      // === NATURE CHECKS ===

      stdout.println(`Is object nature: ${jsonObj.objectNature()}`)
      stdout.println(`Is value nature: ${jsonNum.valueNature()}`)

      // === TO-JSON OPERATOR $$ ===

      colour <- #FFAACC
      jsonColour <- $$ colour
      stdout.println(`Colour as JSON: ${jsonColour}`)

      anInt <- 99
      jsonInt <- $$ anInt
      stdout.println(`Integer as JSON: ${jsonInt}`)

      // === STRING CONVERSION $ ===

      text <- $jsonObj
      stdout.println(`JSON as string: ${text}`)

Java: JSON requires external libraries (Jackson, Gson). Python: json module in stdlib but returns dict/list. Rust: serde_json crate. Go: encoding/json package. Kotlin: kotlinx.serialization. EK9: JSON is a built-in type with constructors, operators, and stream integration. No imports needed.

array built-in constructor create data first-class json nature object parse serialize type

Explanation: JSON constructor for key-value pairs requires both arguments to be JSON values. Passing a raw String as the value instead of JSON(String) triggers E50060 — constructor not resolved. Wrap the value with JSON(). See ek9 -h E50060 for details.


Question: How do I convert an EK9 object to JSON?

How does the $$ operator work for JSON serialization?

How do I serialize a record to JSON in EK9?

How do I convert EK9 values to JSON format?

Answer:
EK9 uses the $$ (to-JSON) operator to convert values to JSON. Records with 'default operator $$' automatically serialize all fields to a JSON object.

TO-JSON ON BUILT-IN TYPES
All built-in types support $$:
  jsonInt <- $$ 42
  jsonStr <- $$ "hello"
  jsonBool <- $$ true

TO-JSON ON RECORDS
Records with 'default operator $$' serialize all fields:
  defines record
    Person
      name as String: String()
      age as Integer: Integer()
      default operator $$
Then: jsonPerson <- $$ person
The result is a JSON object with properties for each field.

UNSET FIELDS
Unset fields in a record become JSON null values. EK9's tri-state model maps naturally to JSON:
  set value maps to JSON value
  unset value maps to JSON null
  absent value maps to JSON null

See Q188 for JSON as a first-class type. See Q97 for records. See Q29 for unset variables.

defines module qa.jsondata.tojson

  defines record

    Person
      name as String: String()
      age as Integer: Integer()

      Person()
        ->
          n as String
          a as Integer
        name: n
        age: a

      default operator ?
      default operator $$

  defines program

    JsonToJsonDemo()
      stdout <- Stdout()

      // === $$ ON BUILT-IN TYPES ===

      jsonInt <- $$ 42
      stdout.println(`Integer as JSON: ${jsonInt}`)

      jsonStr <- $$ "hello"
      stdout.println(`String as JSON: ${jsonStr}`)

      jsonBool <- $$ true
      stdout.println(`Boolean as JSON: ${jsonBool}`)

      // === $$ ON RECORDS ===

      person <- Person("Alice", 30)
      jsonPerson <- $$ person
      stdout.println(`Person as JSON: ${jsonPerson}`)

      // === UNSET FIELDS BECOME NULL ===

      partial <- Person()
      jsonPartial <- $$ partial
      stdout.println(`Partial person JSON: ${jsonPartial}`)

Java: Jackson @JsonSerialize or Gson.toJson() for serialization. Python: json.dumps() with custom encoder. Rust: serde_json::to_string(). Go: json.Marshal(). Kotlin: kotlinx.serialization @Serializable. EK9: 'default operator $$' on records for automatic JSON serialization, $$ operator on any value.

class convert data field json null operator record serialize unset

Explanation: Record fields must be initialized inline with a default value. Declaring a field without an initializer triggers E08180. Use 'String()' for an unset default or provide a literal default value. See ek9 -h E08180 for details.

Explanation: EK9 types do not have a 'toJSON()' method. Use the $$ operator to convert any value to its JSON representation. For records, 'default operator $$' auto-generates JSON serialization of all fields. See ek9 -h E50060 for details.