Question: How do I create a singleton in EK9?

How do I ensure only one instance of a class exists?

Answer:
EK9 has no traditional singleton pattern. There is no private constructor trick, no static getInstance() method, and no object keyword. The ONLY way to have a single shared instance is through Dependency Injection and Inversion of Control using the component and application registration system.

WHY NO TRADITIONAL SINGLETONS
Traditional singletons create hidden global state, tight coupling, and testing nightmares. EK9 enforces that shared instances are explicitly registered and injected, making dependencies visible and testable.

HOW SINGLE INSTANCES WORK
Register a component in an application definition. All injection points receive the same instance:
  defines component
    AppConfig as abstract
      setting() as abstract
        <- rtn as String?
      default operator ?
  defines application
    MyApp
      register ProductionConfig() as AppConfig
The '!' suffix on a type declaration triggers injection: config as AppConfig!

LIFECYCLE
The registered component instance is created once during application startup. Every injection point across the application receives the same instance.

THREAD SAFETY
Since components are shared, use immutable state or MutexLock for mutable data. Pure methods ensure thread-safe reads.

See Q111 for components and DI. See Q114 for aspects. See Q119 for constructs overview. See Q234 for component lifecycle details.

See Q330 for why EK9 uses singleton-only DI scope.

defines module qa.oop.singleton

  defines component

    AppConfig as abstract
      setting() as abstract
        <- rtn as String?
      default operator ?

    ProductionConfig extends AppConfig
      override setting()
        <- rtn as String: "production-mode"
      default operator ?

  defines application

    SingletonApp
      register ProductionConfig() as AppConfig

  defines program

    SingletonDemo() with application of SingletonApp
      stdout <- Stdout()

      // === SINGLETON via component injection ===

      config1 as AppConfig!
      config2 as AppConfig!

      // Both reference the same registered instance
      stdout.println(`Config1: ${config1.setting()}`)
      stdout.println(`Config2: ${config2.setting()}`)

      // === Same value from both injection points ===

      stdout.println(`Same: ${config1.setting() == config2.setting()}`)

Java: private constructor + static getInstance(), or Spring @Singleton/@Scope. Python: module-level instances, or __new__ override. Rust: lazy_static! or once_cell for global state. Go: sync.Once for lazy initialisation, package-level variables. Kotlin: 'object' keyword creates a singleton. EK9: component registration provides singleton lifecycle, injection with '!' suffix, no explicit singleton pattern needed, thread safety through pure methods or MutexLock.

component define inject instance lifecycle object-oriented pattern register safety shared singleton thread

Explanation: When implementing an abstract method from the parent component AppConfig, the 'override' keyword is mandatory. ProductionConfig must use 'override setting()'. See ek9 -h E05120 for details.

Explanation: Without 'as abstract', AppConfig has an abstract method 'setting()' but the component itself is not marked abstract. The compiler requires types with abstract methods to be declared 'as abstract'. See ek9 -h E50040 for details.

Explanation: EK9 does not use Java-style getter naming. The method is 'setting()' not 'getSetting()'. See ek9 -h E50060 for details.

Explanation: EK9 does not have a toString() method. Use string interpolation or the $ operator instead. See ek9 -h E50060 for details.

Oracle can generate a singleton pattern class with private constructor and static accessor.


Question: How do I implement the builder pattern in EK9?

How do I build complex objects step by step in EK9?

How do I use record merge for building objects?

Answer:
EK9 supports builder-like patterns through record merge operations and multiple constructors. The merge operator ':~:' is particularly useful for incremental object construction.

BUILDER IN EK9
Use a record with the merge operator to build up an object:
  config <- ServerConfig()
  config :~: partial1
  config :~: partial2
Each merge adds only the SET fields from the source.

FLUENT API PATTERN
Define builder methods that return the modified object:
  withHost()
    -> host as String
    <- rtn as ServerConfig: ServerConfig()
    rtn :=: this
    rtn.host: host

USING RECORDS AS BUILDERS
Records with public fields and merge operator serve as natural builders. Create partial records and merge them together.

ALTERNATIVE NAMED CONSTRUCTOR PARAMETERS
EK9 supports named parameters in constructors:
  config <- ServerConfig(host: "localhost", port: 8080)
This can replace simple builders.

See Q94 for constructors. See Q97 for records. See Q98 for record operators.

defines module qa.oop.builder

  defines record

    ServerConfig
      host <- String()
      port <- Integer()
      maxConnections <- Integer()

      ServerConfig()
        ->
          host as String
          port as Integer
          maxConnections as Integer
        this.host: host
        this.port: port
        this.maxConnections: maxConnections

      operator :=:
        -> from as ServerConfig
        host :=: from.host
        port :=: from.port
        maxConnections :=: from.maxConnections

      operator :~:
        -> from as ServerConfig
        if from.host?
          host :=: from.host
        if from.port?
          port :=: from.port
        if from.maxConnections?
          maxConnections :=: from.maxConnections

      operator $ as pure
        <- rtn as String: `${host}:${port} (max:${maxConnections})`

      default operator ?

  defines program

    BuilderDemo()
      stdout <- Stdout()

      // === NAMED CONSTRUCTOR PARAMETERS ===

      config1 <- ServerConfig("localhost", 8080, 100)
      stdout.println(`Direct: ${config1}`)

      // === BUILD WITH MERGE ===

      config2 <- ServerConfig()
      partial1 <- ServerConfig()
      partial1.host: "production.example.com"
      config2 :~: partial1

      partial2 <- ServerConfig()
      partial2.port: 443
      partial2.maxConnections: 500
      config2 :~: partial2

      stdout.println(`Merged: ${config2}`)

      // === COPY AND MODIFY ===

      config3 <- ServerConfig()
      config3 :=: config1
      config3.port: 9090
      stdout.println(`Modified copy: ${config3}`)

Java: Builder pattern with nested static class, method chaining, .build() call. Lombok @Builder for auto-generation. Python: kwargs in constructors, or dataclass with defaults. Rust: builder pattern crate or struct update syntax with '..' operator. Go: functional options pattern with variadic args. Kotlin: named parameters and default values replace most builders, apply{} for mutation. EK9: record merge operator ':~:' for incremental construction, named constructor parameters, multiple constructor overloads.

builder compile construct fluent incremental merge named object-oriented parameter pattern record step

Explanation: Class fields are private and cannot be accessed externally. Changing the record to a class makes 'partial1.host: ...' trigger E06180 — not accessible from this context. Use records for builder patterns because record fields are public. See ek9 -h E06180 for details.

Explanation: The replace operator ':^:' is not defined on ServerConfig. Using ':^:' instead of ':~:' triggers E50060 — method/function not resolved. Define the operator or use the correct one. See ek9 -h E50060 for details.

Explanation: The replace operator ':^:' is not defined on ServerConfig. Using ':^:' instead of ':=:' triggers E50060 — method/function not resolved. Define the operator or use the correct one. See ek9 -h E50060 for details.

Explanation: EK9 does not have a Java-style builder with method chaining. Use constructors for direct creation or the merge operator ':~:' for incremental building. See ek9 -h E50001 for details.

operator :~:
        -> from as ServerConfig

operator :~:
        -> from as ServerConfig
        <- rtn as ServerConfig: ServerConfig()

Explanation: Mutation operators (:~:, :=:, :^:, +=, -=) mutate the object in place and must not have return values. Adding a return declaration triggers E07430. These operators modify 'this' directly. See ek9 -h E07430 for details.

Explanation: EK9 does not have a toString() method. Use the $ operator or string interpolation. The record's operator $ handles string conversion. See ek9 -h E50060 for details.


Question: What EK9 constructs are available and when should I use each?

What are all the construct types in EK9?

How do I choose between class record trait and component?

What is the complete list of EK9 type constructs?

Answer:
EK9 provides a rich set of constructs, each designed for specific purposes. Choosing the right construct leads to clearer, more maintainable code.

CONSTRUCT OVERVIEW
Class: Encapsulated behaviour, private properties, methods, constructors.
Record: Transparent data, public properties, ideal for DTOs and values.
Trait: Behaviour contracts, no mutable state, multiple implementation.
Component: DI-managed services, injection with '!', application registration.
Enumeration: Named constant values, auto-generated operators, iteration.
Text: Internationalized strings, locale-specific, compiler-validated.
Service: REST/HTTP endpoints, operator-to-HTTP mapping, URI binding.
Function: Standalone callable units, pure functions, higher-order.
Program: Entry points with optional application binding.
Application: DI container, component and service registration.
Type: Constrained types derived from built-in types.
Constant: Module-level immutable values.

DECISION GUIDE
Need behaviour with hidden state? Class.
Need transparent data? Record.
Need shared behaviour contract? Trait.
Need injectable service? Component.
Need fixed set of values? Enumeration.
Need localised text? Text.
Need HTTP endpoint? Service.

PHILOSOPHY
EK9 favours composition over inheritance (closed by default), behaviour through traits, and explicit DI through components. Each construct has a single clear purpose.

See Q93 for classes. See Q97 for records. See Q99 for enumerations. See Q106 for traits. See Q111 for components. See Q49 for functions. See Q17 for entry points. See Q1 for minimal program.

defines module qa.oop.overview

  defines type

    Rating
      Low
      Medium
      High

  defines record

    Item
      name <- String()
      rating <- Rating()

      Item()
        ->
          name as String
          rating as Rating
        this.name: name
        this.rating: rating

      default operator

  defines function

    describeItem() as pure
      -> item as Item
      <- rtn as String: `${item.name} [${item.rating}]`

  defines class

    Catalog
      items <- List() of Item

      add()
        -> item as Item
        items += item

      count() as pure
        <- rtn as Integer: length items

      listAll()
        <- rtn as List of Item: items

      default operator ?

  defines program

    OverviewDemo()
      stdout <- Stdout()

      // === ENUMERATION ===

      priority <- Rating.High
      stdout.println(`Rating: ${priority}`)

      // === RECORD ===

      item <- Item("Widget", Rating.Medium)
      stdout.println(`Item: ${item}`)

      // === FUNCTION ===

      stdout.println(describeItem(item))

      // === CLASS ===

      catalog <- Catalog()
      catalog.add(Item("Gadget", Rating.High))
      catalog.add(Item("Tool", Rating.Low))
      catalog.add(item)

      stdout.println(`Catalog size: ${catalog.count()}`)

      for entry in catalog.listAll()
        stdout.println(`  ${describeItem(entry)}`)

Java: class, interface, enum, record (Java 16), annotation, no built-in component/service/text constructs. Python: class, no enum until 3.4, no built-in DI or service constructs. Rust: struct, enum, trait, impl, no class or component concepts. Go: struct, interface, no class, enum, component, or service constructs. Kotlin: class, data class, interface, enum class, object, no built-in DI or service. EK9: twelve distinct constructs each with clear purpose - class, record, trait, component, enumeration, text, service, function, program, application, type, constant.

application class component constant construct enumeration function object-oriented oop overview program record service text trait

Explanation: Record fields like 'item.name' are public, but class fields like 'catalog.items' are private. Accessing a private class field from outside triggers E06180. Use the class's public methods instead. See ek9 -h E06180 for details.

Explanation: Item is a record — it has no 'describe' method. In EK9, behaviour belongs in standalone functions, not on records. Calling a non-existent method triggers E50060. Use describeItem(item) instead. See ek9 -h E50060 for details.

describe()
        <- rtn as String: name

Explanation: Records can only have constructors and operators — not named methods. Defining 'describe()' on Item triggers E07290. Use a standalone function like describeItem() instead. See ek9 -h E07290 for details.

Explanation: EK9 does not have toString(). Use the $ operator or string interpolation — the record's 'default operator' auto-generates $ for string conversion. See ek9 -h E50060 for details.

Explanation: EK9 enumeration values are case-sensitive. 'HIGH' does not match the defined value 'High'. Check the exact casing of enumeration values. See ek9 -h E50001 for details.


Question: How do I sort a collection in EK9?

How does the sort pipeline operation work in EK9?

How do I sort by a custom comparator in EK9?

Answer:
EK9 sorts collections through stream pipelines using the | sort operation. Natural sorting uses the <=> operator defined on the element type. Custom sorting uses a comparator function.

SORT IN STREAMS
Sorting is a pipeline stage, not a method call on the collection:
  sorted <- cat items | sort | collect as List of String
This creates a new sorted list. The original list is unchanged.

NATURAL SORT
| sort uses the <=> (comparison) operator defined on the element type:
  cat names | sort > stdout
For built-in types (String, Integer, Float, Date), <=> is already defined. For custom types, you must implement operator <=>.

SORT BY COMPARATOR
| sort by function uses a custom comparator:
  cat books | sort by comparingTitle | collect as List of Book
The comparator is a pure function taking two arguments and returning Integer (-1, 0, or 1).

IMPLEMENTING COMPARISON FOR CUSTOM TYPES
Define operator <=> on your record or class to enable natural sort:
  operator <=> as pure
    -> other as Product
    <- rtn as Integer: name <=> other.name

COMPARATOR AS DYNAMIC FUNCTION
Create comparators inline for one-off sorting:
  comparingPrice <- () extends Comparator of Product as pure function
    r:=? t1.price() <=> t2.price()
This creates a function that compares by price field.

See Q45 for List basics. See Q89 for basic stream pipelines. See Q96 for operator overloading. See Q121 for PriorityQueue (maintained order). See Q125 for head/tail/skip to limit sorted results.

defines module qa.collections.sort

  defines class

    Product
      name as String?
      price as Float?

      default private Product() as pure

      Product() as pure
        ->
          name as String
          price as Float
        this.name :=? name
        this.price :=? price

      name() as pure
        <- rtn as String: String(name)

      price() as pure
        <- rtn as Float: Float(price)

      operator <=> as pure
        -> other as Product
        <- rtn as Integer: name <=> other.name

      operator == as pure
        -> other as Product
        <- rtn as Boolean: name == other.name and price == other.price

      operator $ as pure
        <- rtn as String: `${name} \$${price}`

      operator #? as pure
        <- rtn as Integer: #?name

      override operator ? as pure
        <- rtn as Boolean: name? and price?

  defines function

    comparingPrice() as pure
      ->
        t1 as Product
        t2 as Product
      <-
        rtn as Integer: t1.price() <=> t2.price()

  defines program

    SortCollectionDemo()
      stdout <- Stdout()

      products <- [
        Product("Banana", 1.20),
        Product("Apple", 0.90),
        Product("Cherry", 3.50),
        Product("Date", 5.00)
        ]

      // === NATURAL SORT ===

      // Sort by name (uses operator <=> defined on Product)
      byName <- cat products | sort | collect as List of Product
      stdout.println(`By name: ${byName}`)

      // === SORT BY COMPARATOR ===

      // Sort by price using named comparator
      byPrice <- cat products | sort by comparingPrice | collect as List of Product
      stdout.println(`By price: ${byPrice}`)

      // === SORT WITH DYNAMIC COMPARATOR ===

      // Inline comparator for one-off sort
      reverseByName <- () extends Comparator of Product as pure function
        r:=? t2.name() <=> t1.name()

      descending <- cat products | sort by reverseByName | collect as List of Product
      stdout.println(`Descending: ${descending}`)

Java: Collections.sort() or list.stream().sorted(Comparator.comparing()). Python: sorted(list, key=lambda x: x.field). JavaScript: array.sort((a, b) => a.field - b.field). Rust: vec.sort() or vec.sort_by_key(). Go: sort.Slice() with less function. EK9: cat list | sort or cat list | sort by comparator in a pipeline, comparator defined as pure function or dynamic function extending Comparator of T.

ascending collection comparator comparison custom data-structure descending list natural order pipeline sort stream

Explanation: EK9 sorts through stream pipelines using '| sort', not method calls like .sort(). Sorting is a pipeline stage, not a method on the collection. See ek9 -h E50060 for details.

Explanation: A class with fields must define operator ? for tri-state semantics. Without it, guard expressions and safe access patterns cannot inspect field state. See ek9 -h E07235 for details.

Explanation: EK9 does not have toString(). Use the $ operator or string interpolation. See ek9 -h E50060 for details.

cat products | sort by Collections.comparing(Product.price) | collect as List of Product

Explanation: EK9 does not have Java-style Collections utility classes or method references. 'Collections' is not a known type. Use a named comparator function that takes two parameters and returns Integer. See ek9 -h E50001 for details.


Question: How does PriorityQueue work in EK9?

How do I create a bounded sorted collection in EK9?

How do I get the top N items from a collection in EK9?

Answer:
EK9 provides a generic PriorityQueue type that maintains elements in sorted order. It supports bounded size for top-N patterns and integrates with stream pipelines.

CREATION
Create a PriorityQueue with an initial element:
  pq <- PriorityQueue("first")
Or create an empty typed queue:
  pq <- PriorityQueue() of String

COMPARATOR SETUP
A PriorityQueue needs a comparator to determine order:
  comparator <- () extends Comparator of String as pure function (r:=? t1 <=> t2)
  pq <- PriorityQueue("Bill").withComparator(comparator)
The comparator is a pure function using the <=> operator.

ADDING ELEMENTS
Use += to add elements:
  pq += "Ted"
  pq += "Excellent"
Elements are automatically maintained in priority order.

BOUNDED QUEUES
Create a bounded queue that keeps only the top N items:
  topThree <- pq.withSize(3)
When more than N elements are added, the lowest-priority element is dropped. This is the efficient top-N pattern.

ACCESSING ORDERED RESULTS
Get elements as a sorted list:
  items <- pq.list()
Or reverse the order:
  reversed <- pq.list().reverse()

STREAM INTEGRATION
Use PriorityQueue as a pipeline terminal:
  cat items | filter by isGood | collect as PriorityQueue of String
Or as a pipeline source:
  cat pq | map with transform > stdout

See Q45 for List basics. See Q120 for sorting. See Q122 for collect as custom aggregation. See Q126 for choosing the right collection type.

defines module qa.collections.priorityqueue

  defines function

    comparingStrings() as pure
      ->
        t1 as String
        t2 as String
      <-
        rtn as Integer: t1 <=> t2

    suitableLength() as pure
      -> item as String
      <- rtn <- Boolean()
      minLength <- 3
      rtn: length item > minLength

  defines program

    PriorityQueueDemo()
      stdout <- Stdout()

      // === CREATION WITH COMPARATOR ===

      comparator <- () extends Comparator of String as pure function (r:=? t1 <=> t2)
      pq <- PriorityQueue("Bill").withComparator(comparator)

      // === ADDING ELEMENTS ===

      pq += "And"
      pq += "Ted"
      pq += "Excellent"
      pq += "Adventure"

      allEntries <- pq.list()
      stdout.println(`All entries: ${allEntries}`)

      // === BOUNDED QUEUE (TOP-N) ===

      topThree <- pq.withSize(3)
      limitedEntries <- topThree.list()
      stdout.println(`Top 3: ${limitedEntries}`)

      // === STREAM AS SOURCE ===

      cat allEntries | filter by suitableLength > stdout

      // === EMPTY QUEUE ===

      emptyQ <- PriorityQueue() of String
      require emptyQ?
      require emptyQ is empty
      stdout.println(`Empty PQ isSet: ${emptyQ?}`)

Java: PriorityQueue<T> with Comparator, no bounded size built-in (must manage manually). Python: heapq module, heapq.nlargest() for top-N. JavaScript: no built-in priority queue, use libraries. Rust: BinaryHeap<T> with Reverse wrapper. Go: container/heap interface. EK9: PriorityQueue(elem).withComparator(comp).withSize(n) fluent API, integrated with stream pipelines.

bounded collect collection comparator data-structure order priority queue sorted top withComparator withSize

Explanation: PriorityQueue does not have a Java-style peek() method. Use .list() to get all entries as a sorted List. See ek9 -h E50060 for details.

Explanation: PriorityQueue does not have a Java-style toArray() method. Use .list() to get entries as a List. See ek9 -h E50060 for details.

Explanation: PriorityQueue does not have a Java-style .add() method. Use the += operator to add elements. See ek9 -h E50060 for details.


Question: How do I use collect as to aggregate stream results in EK9?

How do I create a custom stream collector in EK9?

How does the operator | work for stream aggregation in EK9?

How do I reduce a stream to a single value in EK9?

What is the EK9 equivalent of fold or reduce?

How do I accumulate values from a stream in EK9?

Answer:
EK9 stream pipelines use | collect as Type to gather results. Built-in collectors handle List, Integer, and String. For custom aggregation, define operator | on a record.

BUILT-IN COLLECTORS
Collect into a list:
  evens <- cat numbers | filter by isEven | collect as List of Integer
Collect by summing integers:
  total <- cat numbers | collect as Integer
Collect by concatenating strings:
  joined <- cat words | collect as String

CUSTOM AGGREGATOR PATTERN
Define a record with operator | to receive stream items one at a time:
  Stats
    count as Integer: Integer()
    total as Integer: Integer()
    operator |
      -> arg as Integer
      if arg?
        if ~total?
          count := 1
          total :=: arg
        else
          count++
          total += arg
The stream feeds each item to operator | in sequence.

TRI-STATE FIRST-ITEM DETECTION
Use ~total? to detect the first item (total is unset). On first item, initialize with :=: (copy operator). On subsequent items, accumulate with +=.

USAGE
  stats <- for i in 1 ... 12 | collect as Stats
The for-range generates integers 1 through 12, piped into the Stats aggregator.

See Q80 for for-range expression as alternative accumulation. See Q82 for for-in expression as alternative fold/reduce. See Q89 for basic collect patterns. See Q96 for operator overloading. See Q120 for sort before collect. See Q124 for group by aggregation. See Q235 for complete stream operations reference. See Q237 for streams vs loops decision guide. See Q286 for batch accumulation with stream collect.

defines module qa.collections.collectas

  defines record

    Stats
      count as Integer: Integer()
      total as Integer: Integer()
      average as Float: Float()

      operator $ as pure
        <- rtn as String: String()
        if total?
          rtn :=? `[${count}, ${average}, ${total}]`

      operator |
        -> arg as Integer

        if arg?
          if ~total?
            count := 1
            total :=: arg
          else
            count++
            total += arg
          average := (#^total) / count

      default operator ?

  defines function

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

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

  defines program

    CollectAsDemo()
      stdout <- Stdout()

      numbers <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

      // === BUILT-IN: COLLECT AS LIST ===

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

      // === BUILT-IN: COLLECT AS INTEGER (SUM) ===

      total <- cat numbers | collect as Integer
      stdout.println(`Sum: ${total}`)

      // === CUSTOM AGGREGATOR ===

      stats <- for i in 1 ... 12 | collect as Stats
      stdout.println(`Stats: ${stats}`)

      // === CUSTOM AGGREGATOR WITH FILTER ===

      evenStats <- cat numbers | filter by isEven | collect as Stats
      stdout.println(`Even stats: ${evenStats}`)

Java: Collectors.reducing(), Collectors.summarizingInt(), custom Collector interface. Python: functools.reduce() or manual accumulation. JavaScript: array.reduce((acc, item) => ..., initial). Rust: iter.fold(initial, |acc, item| ...). Go: manual loop accumulation. EK9: define operator | on a record, use | collect as MyRecord in pipeline, tri-state ~var? for first-item detection.

accumulate accumulator aggregate collect collection custom data-structure fold operator pipe reduce reducer result statistics stream sum

Explanation: EK9 does not have .stream() or .collect() methods. Use the cat source | filter by pred | collect as Type pipeline syntax. See ek9 -h E50060 for details.

Explanation: A record with fields must define operator ? for tri-state semantics. A Stats record used with '| collect as' needs isSet so the pipeline can check if the result is valid. See ek9 -h E07235 for details.

Explanation: EK9 does not have toString(). Use string interpolation or the $ operator. See ek9 -h E50060 for details.

Explanation: EK9 does not have toString(). Use string interpolation or the $ operator for string conversion. See ek9 -h E50060 for details.

cat numbers | filter by doubleIt | collect as List of Integer

Explanation: A function used with '| filter by' must return Boolean. The function doubleIt() returns Integer, not Boolean, so it cannot be used as a predicate. Use a function that returns Boolean for filtering. See ek9 -h E07520 for details.


Question: How do I flatten nested collections and optionals in EK9?

How does the flatten pipeline operation work in EK9?

How do I merge nested lists into a single list in EK9?

How do I flatten a list of lists in EK9?

Answer:
EK9 uses | flatten in stream pipelines to collapse nested structures into flat sequences. This is commonly used after group operations that produce lists of lists.

FLATTEN LIST OF LIST
After a group operation, you have List of List of T. Flatten merges into List of T:
  cat grouped | flatten | collect as List of String
Each inner list's elements are emitted in order.

FLATTEN IN PIPELINES
Flatten typically appears after group and map stages:
  cat items
    | sort by key
    | group by category
    | map by transform
    | flatten
    | collect as List of Item
This is the standard group-process-flatten pattern.

WHEN FLATTEN IS NEEDED
Any pipeline operation that wraps results in an extra layer needs flatten to unwrap:
  group by produces List of List of T from List of T
  map after group produces List of List of U from List of List of T
Flatten removes exactly one level of nesting.

FLATTEN WITH OUTPUT
Pipe flattened results directly to stdout:
  cat groupedItems | flatten > stdout
Each element prints on its own line.

See Q85 for Optional stream patterns. See Q89 for basic stream pipelines. See Q124 for group by that creates nested lists.

defines module qa.collections.flatten

  defines function

    firstChar() as pure
      -> word as String
      <- rtn as String: $#<word

  defines program

    FlattenDemo()
      stdout <- Stdout()

      words <- ["apple", "avocado", "banana", "blueberry", "cherry", "cranberry"]

      // === GROUP THEN FLATTEN ===

      // Group by first character, then flatten back
      grouped <- cat words | sort | group by firstChar | collect as List of List of String
      stdout.println(`Grouped: ${grouped}`)

      flat <- cat grouped | flatten | collect as List of String
      stdout.println(`Flattened: ${flat}`)

      // === FLATTEN WITH DIRECT OUTPUT ===

      cat grouped | flatten > stdout

      // === NESTED LIST CREATION AND FLATTEN ===

      nested <- [
        ["one", "two"],
        ["three", "four"],
        ["five", "six"]
        ]

      allItems <- cat nested | flatten | collect as List of String
      stdout.println(`All items: ${allItems}`)

Java: stream.flatMap(Collection::stream) or Stream.concat(). Python: itertools.chain.from_iterable() or [item for sublist in nested for item in sublist]. JavaScript: array.flat() or array.flatMap(). Rust: iter.flatten() or iter.flat_map(). Go: manual nested loops. EK9: | flatten in pipeline after group or nested operations, removes one level of nesting.

collapse collection concatMap data-structure flat flatMap flatten group list merge nested optional pipeline stream unwrap

Explanation: EK9 has no flatten() method on collections. Use | flatten in a stream pipeline to collapse nested structures. The pipeline form is: cat nested | flatten | collect as List of T. See ek9 -h E50060 for details.

cat nested | flatten myFunction | collect as List of String

Explanation: Flatten takes no function argument — it simply unwraps the nested structure. Unlike Java's flatMap() which combines map and flatten, EK9 separates these operations. See ek9 -h E50001 for details.

allItems <- cat nested | flatten | collect as List of Date

Explanation: After flattening List of List of String, the stream type is String. Collecting into List of Date fails because String and Date are incompatible types. The collect target must match the stream type. See ek9 -h E07830 for details.

Explanation: EK9 does not have Java-style .stream().collect() method chains. Use pipe syntax: 'cat items | collect as Type'. See ek9 -h E50060 for details.


Question: How do I group and aggregate collection data in EK9?

How does group by work in EK9 stream pipelines?

How do I group items by a key and then process each group in EK9?

How do I use filter with to keep groups in EK9?

Answer:
EK9 stream pipelines support grouping items by a key extractor function. After grouping, you can filter, map, and flatten the groups.

GROUP BY KEY EXTRACTOR
Group by uses a function that extracts a key from each element:
  cat items | group by extractCategory | collect as List of List of Item
Elements with the same key value are collected into the same inner list. The input must be sorted by the same key first.

FILTER WITH (GROUPS)
Keep groups matching a predicate on each group (List of T):
  | filter with hasEnoughItems
The predicate receives the entire group (a List), not individual elements.

GROUP THEN MAP
Transform each group using a mapping function:
  | map by processGroup
The function receives a List of T and returns a transformed result.

FULL PIPELINE PATTERN
The complete group-process-flatten pattern:
  cat items
    | sort by keyExtractor
    | group by keyExtractor
    | filter with filterPredicate
    | map by groupTransformer
    | flatten
    > stdout
Sort first to ensure correct grouping. Group, filter, transform, then flatten back to a flat stream.

See Q89 for basic stream pipelines. See Q120 for sorting before group. See Q122 for collect as aggregation. See Q123 for flatten after group. See Q125 for head/tail/skip to limit results.

defines module qa.collections.groupaggregate

  defines class

    Sale
      region as String?
      amount as Float?

      default private Sale() as pure

      Sale() as pure
        ->
          region as String
          amount as Float
        this.region :=? region
        this.amount :=? amount

      region() as pure
        <- rtn as String: String(region)

      amount() as pure
        <- rtn as Float: Float(amount)

      operator $ as pure
        <- rtn as String: `${region}:${amount}`

      operator <=> as pure
        -> other as Sale
        <- rtn as Integer: region <=> other.region

      operator #? as pure
        <- rtn as Integer: #?region

      override operator ? as pure
        <- rtn as Boolean: region? and amount?

  defines function

    regionKey() as pure
      -> sale as Sale
      <- rtn as String: sale.region()

    comparingRegion() as pure
      ->
        t1 as Sale
        t2 as Sale
      <-
        rtn as Integer: t1.region() <=> t2.region()

    hasTwoOrMore() as pure
      -> group as List of Sale
      <- rtn <- Boolean()
      expectedGroups <- 2
      rtn: length group >= expectedGroups

  defines program

    GroupAggregateDemo()
      stdout <- Stdout()

      sales <- [
        Sale("North", 100.0),
        Sale("South", 200.0),
        Sale("North", 150.0),
        Sale("East", 300.0),
        Sale("South", 175.0),
        Sale("North", 125.0)
        ]

      // === GROUP BY REGION ===

      grouped <- cat sales
        | sort by comparingRegion
        | group by regionKey
        | collect as List of List of Sale
      stdout.println(`Grouped: ${grouped}`)

      // === FILTER GROUPS WITH 2+ SALES ===

      filtered <- cat sales
        | sort by comparingRegion
        | group by regionKey
        | filter with hasTwoOrMore
        | collect as List of List of Sale
      stdout.println(`Filtered: ${filtered}`)

      // === FULL PIPELINE: GROUP, FILTER, FLATTEN ===

      cat sales
        | sort by comparingRegion
        | group by regionKey
        | filter with hasTwoOrMore
        | flatten
        > stdout

Java: Collectors.groupingBy() with downstream collectors. Python: itertools.groupby() (requires pre-sort). JavaScript: manual reduce into Map or lodash groupBy. Rust: itertools group_by() (requires pre-sort). Go: manual loop with map. EK9: cat | sort by key | group by key | filter with | map by | flatten pipeline, requires pre-sort like Python/Rust itertools.

aggregate category collection data-structure extractor filter flatten group groupBy groupingBy key list map partition pipeline sort

Explanation: EK9 uses short method names without 'get' prefix. Sale has region() and amount() not getRegion() and getAmount(). Triggers E50060 — method not resolved. See ek9 -h E50060 for details.

Explanation: List has no toString() method. Use the $ prefix operator or string interpolation for string conversion. See ek9 -h E50060 for details.

Explanation: Group produces List of T from a stream of T — each group is a list. So the collection must be List of List of T, not List of T. A List of String cannot receive List of String items via the pipe operator. See ek9 -h E07830 for details.

Explanation: The group function's parameter type must match the stream's element type. Using a function that accepts Date on a String stream triggers E50030 because the types are incompatible. See ek9 -h E50030 for details.

| group by comparingRegion
        | filter with hasTwoOrMore

Explanation: The group by function must accept exactly one argument — the current stream element. A function with two parameters cannot be used for grouping. See ek9 -h E06300 for details.

Explanation: The sort by comparator must accept exactly two parameters of the element type and return Integer. A single-parameter function cannot compare two elements. Sort is required before group to ensure correct grouping. See ek9 -h E07470 for details.


Question: How do I use head, tail, and skip to limit streams in EK9?

How do I take the first N items from a stream in EK9?

How do I skip elements in a stream pipeline in EK9?

What is the EK9 equivalent of break in a loop for limiting results?

How do I stop processing early in EK9 without break?

Answer:
EK9 provides head, tail, and skip pipeline operations to control how many elements flow through a stream. These replace the need for break or early exit patterns.

HEAD N
Take only the first N elements from the stream:
  cat items | head 3 | collect as List of String
Elements after the first 3 are discarded. This is EK9's replacement for break in loops.

TAIL N
Take only the last N elements from the stream:
  cat items | tail 2 | collect as List of String
The pipeline buffers elements and only emits the final N.

SKIP N
Skip the first N elements, pass the rest:
  cat items | skip 5 | collect as List of String
Elements 1 through 5 are discarded, the rest flow through.

REPLACING BREAK WITH HEAD
Instead of a loop with break after finding enough items, use head:
  top5 <- cat allItems | sort | head 5 | collect as List of Item
This is cleaner and eliminates the bug-prone break pattern.

THE ONLY EARLY EXIT MECHANISM
EK9 has no break, return, or continue statements. Stream pipelines with head are the ONLY mechanism for efficient early exit processing in EK9. Instead of looping through a collection with a break condition, use:
  cat source | filter by predicate | head N | collect as List of T
This is not just a convenience pattern but the designed replacement for break. See Q64 for for-range loops, Q65 for for-in loops, Q66 for while loops, and Q67 for do-while loops, none of which have break.

COMBINING LIMITERS
Chain skip, head, and tail for windowing:
  cat items | skip 2 | head 3 | collect as List of String
Skip 2, then take 3 means elements 3, 4, 5 from the original.

See Q89 for basic stream pipelines. See Q120 for sort with head (top-N). See Q124 for group by with limiting. See Q145 for replacing break and continue with streams. See Q235 for complete stream operations reference. See Q237 for streams vs loops decision guide. See Q284 for find-first-match patterns. See Q287 for bounded processing with head.

defines module qa.collections.headtailskip

  defines program

    HeadTailSkipDemo()
      stdout <- Stdout()

      items <- ["alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf"]
      stdout.println(`Original: ${items}`)

      // === HEAD N ===

      firstThree <- cat items | head 3 | collect as List of String
      stdout.println(`Head 3: ${firstThree}`)

      // === TAIL N ===

      lastTwo <- cat items | tail 2 | collect as List of String
      stdout.println(`Tail 2: ${lastTwo}`)

      // === SKIP N ===

      afterTwo <- cat items | skip 2 | collect as List of String
      stdout.println(`Skip 2: ${afterTwo}`)

      // === COMBINING: SKIP THEN HEAD ===

      // Elements 3, 4, 5 (skip 2, take 3)
      window <- cat items | skip 2 | head 3 | collect as List of String
      stdout.println(`Skip 2 head 3: ${window}`)

      // === SORT THEN HEAD (TOP-N) ===

      sorted <- cat items | sort | head 3 | collect as List of String
      stdout.println(`Top 3 sorted: ${sorted}`)

      // === DIRECT OUTPUT ===

      cat items | skip 4 > stdout

Java: stream.limit(n), stream.skip(n), no built-in tail. Python: itertools.islice(iter, n), list[-n:] for tail. JavaScript: array.slice(0, n) for head, array.slice(-n) for tail, array.slice(n) for skip. Rust: iter.take(n), iter.skip(n). Go: manual index slicing. EK9: | head n, | tail n, | skip n as pipeline operations, chain for windowing.

alternative break collection data-structure early exit first head last limit loop pipeline replace skip stop stream tail take window

lastTwo <- items.subList(items.size() - 2, items.size())

Explanation: EK9 has no subList() method. Use | tail N in a stream pipeline to take the last N elements. See ek9 -h E50060 for details.

cat items | head 0 | collect as List of String

Explanation: The head, tail, and skip operations require a positive integer (greater than zero). Zero and negative values are rejected at compile time. Use a value of 1 or greater. See ek9 -h E07560 for details.

Explanation: A supplier function used with head, tail, or skip must return Integer. A function returning Date or any non-Integer type triggers E07550. The count value must be an Integer. See ek9 -h E07550 for details.

cat items | tail 0 | collect as List of String

Explanation: The head, tail, and skip operations require a positive integer (greater than zero). Zero is rejected at compile time. Use a value of 1 or greater. See ek9 -h E07560 for details.

cat items | skip 0 | head 3 | collect as List of String

Explanation: The skip operation requires a positive integer (greater than zero). Zero is rejected at compile time. Use a value of 1 or greater. See ek9 -h E07560 for details.

cat items | sort | head 3 | collect as List of Integer

Explanation: The collect type must match the pipeline's element type. A String pipeline cannot collect into List of Integer because there is no pipe operator between String and Integer. See ek9 -h E07830 for details.

Explanation: Skip requires a positive integer greater than zero. Negative values like -1 are rejected at compile time. See ek9 -h E07560 for details.

cat items | head -1 | collect as List of String

Explanation: Head, tail, and skip require a positive integer greater than zero. Negative values like -1 are rejected at compile time. Use a positive count value. See ek9 -h E07560 for details.

cat items | head "3" | collect as List of String

Explanation: Head, tail, and skip require an Integer literal, not a String. Passing a string like "3" instead of the integer 3 triggers E07560. EK9 does not implicitly convert strings to integers. See ek9 -h E07560 for details.


Question: How do I choose the right collection type in EK9?

When should I use List vs Dict vs PriorityQueue in EK9?

What collection types are available in EK9?

How do I decide between List, Dict, Optional, and Result in EK9?

Answer:
EK9 provides five main collection and container types. Each serves a distinct purpose.

LIST
Ordered, indexed collection of elements:
  items <- [1, 2, 3]
Use when: you need ordered sequences, indexed access, iteration, stream pipelines.

DICT
Key-value lookup collection:
  ages <- {"Alice": 30, "Bob": 25}
Use when: you need fast lookup by key, associative data, configuration maps.

PRIORITYQUEUE
Bounded, automatically sorted collection:
  top <- PriorityQueue(item).withComparator(comp).withSize(5)
Use when: you need the top-N items, maintaining sorted order with a size limit.

OPTIONAL
Contains zero or one value:
  name <- Optional("Alice")
Use when: a value may or may not exist, safe null alternative.

RESULT
Contains either a success value or an error:
  result <- Result(value) or Result(error)
Use when: an operation can fail and you want to handle both cases explicitly.

DECISION GUIDE
Need a sequence of items?           -> List
Need key-value lookup?              -> Dict
Need top-N with automatic ordering? -> PriorityQueue
Might have zero or one value?       -> Optional
Operation might succeed or fail?    -> Result

See Q45 for List details. See Q46 for Dict details. See Q47 for Optional. See Q48 for Result. See Q121 for PriorityQueue. See Q129 for safe Dict key access. See Q130 for mutating vs non-mutating operators on collections. See Q182 for collection empty check.

defines module qa.collections.choosing

  defines program

    ChoosingCollectionDemo()
      stdout <- Stdout()

      // === LIST: ORDERED SEQUENCE ===

      tasks <- ["write code", "run tests", "deploy"]
      tasks += "review"
      stdout.println(`Tasks: ${tasks}`)

      // === DICT: KEY-VALUE LOOKUP ===

      config <- {"host": "localhost", "port": "8080"}
      host <- config.getOrDefault("host", "unknown")
      stdout.println(`Host: ${host}`)

      // === PRIORITYQUEUE: TOP-N ===

      comparator <- () extends Comparator of Integer as pure function (r:=? t1 <=> t2)
      scores <- PriorityQueue(95).withComparator(comparator).withSize(3)
      scores += 87
      scores += 92
      scores += 78
      scores += 99
      topScores <- scores.list()
      stdout.println(`Top 3 scores: ${topScores}`)

      // === OPTIONAL: MAYBE A VALUE ===

      name <- Optional("Alice")
      if name?
        stdout.println(`Name: ${name}`)

      // === RESULT: SUCCESS OR ERROR ===

      result <- Result() of (String, Integer)
      if result?
        stdout.println(`Got: ${result}`)
      else
        stdout.println("No result")

Java: ArrayList, HashMap, PriorityQueue, Optional, no built-in Result. Python: list, dict, no PriorityQueue built-in (heapq module), no Optional. JavaScript: Array, Map/Object, no PriorityQueue/Optional/Result. Rust: Vec, HashMap, BinaryHeap, Option, Result. Go: slices, maps, no generics until 1.18, no Option/Result. EK9: List, Dict, PriorityQueue, Optional, Result all generic and type-safe with consistent operator syntax.

absent choose collection container data-structure decision dict error guard list ok optional priority result safe type

choose collection
which collection
list vs dict

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

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

Explanation: PriorityQueue has no .toArray() method. Use .list() to get entries as a sorted List. See ek9 -h E50060 for details.

Explanation: EK9 does not have toString(). Use string interpolation or the $ operator. See ek9 -h E50060 for details.


Question: How does collection type inference work in EK9?

How does EK9 determine the type of a list literal?

What happens when list elements have different types in EK9?

Answer:
EK9 infers collection types from their literal elements. The compiler determines the common type from all elements in the literal.

FIRST-ELEMENT RULE
The first element establishes the base type:
  numbers <- [1, 2, 3]            infers List of Integer
  names <- ["Alice", "Bob"]       infers List of String
  flags <- [true, false, true]    infers List of Boolean

TYPE COERCION IN LITERALS
When elements have compatible types, the compiler promotes to the wider type:
  mixed <- [1, 2.0, 3]            infers List of Float (Integer promotes to Float)
The promote (#^) operator handles type widening automatically.

EXPLICIT TYPING WHEN NEEDED
For empty collections, specify the type explicitly:
  emptyList <- List() of String
  emptyDict <- Dict() of (String, Integer)
The compiler cannot infer types from zero elements.

DICT LITERAL INFERENCE
Dict literals infer both key and value types:
  ages <- {"Alice": 30, "Bob": 25}   infers Dict of (String, Integer)
  scores <- {1: 95.0, 2: 87.5}       infers Dict of (Integer, Float)

SINGLE-ELEMENT CONSTRUCTION
Single-element constructors use the element type:
  single <- List(42)           creates List of Integer with one element
  opt <- Optional("hello")    creates Optional of String

See Q45 for List basics. See Q46 for Dict basics. See Q27 for static typing. See Q24 for type conversion and promotion. See Q128 for why collection types are closed by design.

See Q23 for basic types. See Q45 for list.

defines module qa.collections.typeinference

  defines program

    TypeInferenceDemo()
      stdout <- Stdout()

      // === INFERRED FROM FIRST ELEMENT ===

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

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

      flags <- [true, false, true]
      stdout.println(`Booleans: ${flags}`)

      // === TYPE PROMOTION ===

      floats <- [1.0, 2.5, 3.7]
      stdout.println(`Floats: ${floats}`)

      // === EXPLICIT TYPING FOR EMPTY ===

      emptyStrings <- List() of String
      stdout.println(`Empty strings: ${emptyStrings}`)

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

      // === DICT LITERAL INFERENCE ===

      ages <- {"Alice": 30, "Bob": 25, "Charlie": 35}
      stdout.println(`Ages: ${ages}`)

      // === SINGLE ELEMENT CONSTRUCTION ===

      singleList <- List(42)
      stdout.println(`Single: ${singleList}`)

      singleOpt <- Optional("hello")
      stdout.println(`Optional: ${singleOpt}`)

Java: diamond operator <> since Java 7, var since Java 10, List.of() infers types. Python: dynamically typed, no inference needed. JavaScript: dynamically typed. Rust: turbofish ::<T> when inference fails, Vec::new() needs type annotation or usage context. Go: no generics until 1.18, type inference from assignment. Kotlin: type inference from literal elements. EK9: first-element inference, automatic promotion for compatible types, explicit typing for empty collections.

automatic coercion collection data-structure dict element generic inference list literal promote type

Explanation: ArrayList is a Java type that does not exist in EK9. Use 'List() of String' for an empty typed list. EK9 has its own collection types. See ek9 -h E50001 for details.

Explanation: HashMap is a Java type that does not exist in EK9. Use Dict literal syntax with curly braces or 'Dict() of (K, V)' for empty dicts. See ek9 -h E50001 for details.

Explanation: Optional, List, Dict, and Result are generic types that require type parameters. Use 'Optional() of String' for explicit typing, or pass a value like 'Optional(42)' to let the compiler infer the type. See ek9 -h E06010 for details.

Explanation: Dict requires two type parameters for key and value types. Use 'Dict() of (String, Integer)' or a dict literal like '{"key": 42}' which infers types from the elements. See ek9 -h E06010 for details.

Explanation: Vector is a Java/C++ type that does not exist in EK9. Use List for ordered sequences. EK9 has its own collection types: List, Dict, PriorityQueue, Optional, and Result. See ek9 -h E50001 for details.

Explanation: LinkedList is a Java type that does not exist in EK9. Use list literal syntax [1.0, 2.5, 3.7] or List() of Float. See ek9 -h E50001 for details.

Explanation: List is a generic type that requires a type parameter. Use 'List() of String' for explicit typing, or pass a value like 'List(42)' to let the compiler infer the type. See ek9 -h E06010 for details.

Explanation: Array is not an EK9 type. Use list literal syntax [1, 2, 3, 4, 5] for creating a List of Integer. See ek9 -h E50001 for details.


Question: Why can't I extend List or Dict in EK9?

How do I add custom behavior to a List in EK9?

What is the alternative to subclassing collections in EK9?

Answer:
EK9 collection types (List, Dict, PriorityQueue, Optional, Result) are closed by default and cannot be extended. This is a deliberate design decision for semantic integrity.

CLOSED BY DESIGN
Attempting to extend List or Dict produces a compile error:
  MyList extends List of String    Error: not open to be extended
This applies to all built-in generic collection types.

WHY CLOSED
Java's open ArrayList and HashMap are widely considered a design mistake:
  Subclassing mixes collection behavior with application logic
  Fragile base class problem: internal changes break subclasses
  Liskov substitution violations when overriding methods
Modern languages (Swift, Rust, Kotlin) avoid this pattern.

COMPOSITION ALTERNATIVE
Wrap the collection as a private field:
  ValidatedList
    items as List of String: List() of String
    add(item)
      if isValid(item)
        items += item
This keeps collection behavior separate from your domain logic.

DELEGATION PATTERN
Expose only the operations that make sense for your domain:
  size() <- length items
  get(index) <- items.getOrDefault(index, "")
Don't expose the full collection API if your type has different semantics.

See Q101 for why types are closed by default. See Q109 for composition over inheritance. See Q45 for List basics. See Q46 for Dict basics. See Q130 for mutating vs non-mutating operators on collections. See Q194 for defining generic classes. See Q212 for composition over inheritance pattern.

defines module qa.collections.closedcollections

  defines class

    TaskList
      items as List of String: List() of String

      add()
        -> task as String
        if task? and not (items contains task)
          items += task

      remove()
        -> task as String
        items -= task

      size() as pure
        <- rtn as Integer: length items

      all() as pure
        <- rtn as List of String: items

      operator $ as pure
        <- rtn as String: $items

      override operator ? as pure
        <- rtn as Boolean: items?

  defines program

    ClosedCollectionsDemo()
      stdout <- Stdout()

      // === COMPOSITION PATTERN ===

      tasks <- TaskList()
      tasks.add("Write code")
      tasks.add("Run tests")
      tasks.add("Deploy")

      // Duplicate is rejected by our add logic
      tasks.add("Run tests")

      stdout.println(`Tasks (${tasks.size()}): ${tasks}`)

      // Safe removal
      tasks.remove("Deploy")
      stdout.println(`After remove: ${tasks}`)

      // Get a copy of internal list
      allTasks <- tasks.all()
      stdout.println(`All: ${allTasks}`)

Java: ArrayList and HashMap are open (considered design mistake). Python: can extend list and dict freely (mixin pattern). JavaScript: can extend Array. Rust: no inheritance, composition required. Go: no inheritance, embed structs. Kotlin: classes final by default (like EK9). EK9: all collection types closed, use composition and delegation instead of inheritance.

closed collection composition data-structure delegation design dict exhaustive extend inherit list migrate sealed wrap

Explanation: EK9 does not have null. The keyword is deliberately excluded. Use the ? operator to check if a variable is set. 'task?' checks tri-state (set/unset), replacing Java-style null checks. See ek9 -h E01073 for details.

Explanation: Class fields are always private in EK9. The 'items' field inside TaskList cannot be accessed directly — this is why composition works. You control the API through public methods like all(). See ek9 -h E06180 for details.

Explanation: TaskList has no .push() method. The class defines 'add()' as its public API. With composition, you name methods to match your domain. See ek9 -h E50060 for details.

Explanation: EK9 does not have toString(). Use string interpolation or the $ operator. TaskList defines operator $ for string conversion. See ek9 -h E50060 for details.


Question: What happens when I access a Dict key that doesn't exist in EK9?

How do I safely get a value from a Dict in EK9?

Does EK9 throw exceptions for missing dictionary keys?

How do I check if a key exists in a Dict in EK9?

Answer:
EK9 Dict uses safe access patterns instead of throwing exceptions for missing keys. This eliminates KeyNotFoundException and NullPointerException patterns.

NO EXCEPTIONS ON MISSING KEYS
EK9 never throws exceptions for missing keys. Instead, getOrDefault always returns a value, and contains checks for key existence.

GETORDEFAULT PATTERN
Provide a fallback value for missing keys:
  value <- config.getOrDefault("host", "localhost")
If "host" exists, returns its value. If missing, returns "localhost". Always returns a set value.

CONTAINS CHECK
Check if a key exists before accessing:
  if config contains "port"
    stdout.println("Port is configured")
The contains operator uses the key type for lookup.

COMBINING CONTAINS AND GETORDEFAULT
Use contains when you need conditional logic based on key existence:
  if config contains "debug"
    debug <- config.getOrDefault("debug", "false")
    stdout.println(debug)
Use getOrDefault directly when you always want a value.

SAFE ITERATION
for-in on a Dict iterates over DictEntry pairs:
  for entry in config
    stdout.println($entry)
This always works safely, even on an empty Dict.

See Q46 for Dict basics. See Q90 for Dict operations. See Q29 for unset variables and tri-state semantics. See Q126 for choosing the right collection type. See Q139 for EK9's error handling philosophy (safe returns vs exceptions). See Q260 for Dict key type requirements.

defines module qa.collections.missingdictkey

  defines program

    MissingDictKeyDemo()
      stdout <- Stdout()

      config <- {"host": "localhost", "port": "8080", "debug": "true"}

      // === GETORDEFAULT ===

      host <- config.getOrDefault("host", "unknown")
      stdout.println(`Host: ${host}`)

      // Missing key returns the default
      timeout <- config.getOrDefault("timeout", "30")
      stdout.println(`Timeout: ${timeout}`)

      // === CONTAINS CHECK ===

      if config contains "port"
        stdout.println("Port is configured")

      if not (config contains "missing")
        stdout.println("Missing key not found")

      // === GETORDEFAULT WITH ISSET CHECK ===

      debug <- config.getOrDefault("debug", "false")
      stdout.println(`Debug: ${debug}`)

      // Using contains for existence check
      if config contains "debug"
        stdout.println("Debug is configured")

      if not (config contains "nonexistent")
        stdout.println("Nonexistent key not in dict")

      // === SAFE ITERATION ===

      for entry in config
        stdout.println(`Entry: ${entry}`)

      // === EMPTY DICT IS SAFE ===

      emptyDict <- Dict() of (String, String)
      noValue <- emptyDict.getOrDefault("key", "default")
      stdout.println(`From empty: ${noValue}`)

Java: map.get() returns null, map.getOrDefault() since Java 8, map.containsKey(). Python: dict[key] throws KeyError, dict.get(key, default) is safe. JavaScript: obj[key] returns undefined. Rust: map.get() returns Option<&V>. Go: val, ok := map[key] two-value return. EK9: getOrDefault always returns a value, contains checks existence, safe iteration with for-in, no exceptions thrown.

access collection contains data-structure default dict exception getOrDefault key lookup missing safe

Explanation: EK9 Dict has no get() method. Use getOrDefault(key, default) which always returns a value, eliminating null/missing-key exceptions. See ek9 -h E50060 for details.

Explanation: EK9 uses the 'contains' operator for key existence checks, not a containsKey() method. See ek9 -h E50060 for details.

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

Explanation: The contains operator checks for key existence using the Dict's key type. Passing an Integer to a Dict of (String, String) fails because the contains operator is not defined for that type combination. Use the correct key type. See ek9 -h E07620 for details.

Explanation: getOrDefault requires two arguments: the key and a default value. Omitting the default value causes a parameter mismatch. See ek9 -h E50060 for details.

noValue <- emptyDict.getOrDefault(42, "default")

Explanation: The first argument to getOrDefault must match the Dict's key type. Passing an Integer key to a Dict of (String, String) causes a parameter type mismatch. See ek9 -h E50060 for details.


Question: Are collections mutable or immutable in EK9?

How do mutating and non-mutating operators differ on EK9 collections?

What is the difference between += and + for lists in EK9?

Answer:
EK9 collections are mutable by default. The language distinguishes between mutating operators (modify in place) and non-mutating operators (create new copies).

MUTATING OPERATORS
These modify the collection in place:
  list += item       appends item to this list
  list -= item       removes item from this list
  dict += entry      adds entry to this dict
  dict -= key        removes key from this dict
The original collection is changed.

NON-MUTATING OPERATORS
These create new collections, leaving the original unchanged:
  newList <- list + item    new list with item appended
  newList <- list - item    new list without item
  newDict <- dict + entry   new dict with entry added
The original collection is not modified.

WHEN TO USE EACH
Mutating (+=, -=): building up a collection step by step, performance-sensitive code.
Non-mutating (+, -): functional style, preserving original data, pipeline transforms.

PURE FUNCTIONS AND COLLECTIONS
Pure functions cannot mutate their parameters. If a pure function receives a collection, it can only use non-mutating operators:
  process() as pure
    -> items as List of String
    <- rtn as List of String: items + "extra"
The + operator creates a new list. The original items list is untouched.

See Q45 for List basics. See Q88 for List operations. See Q54 for pure functions. See Q96 for operator overloading. See Q122 for collect as aggregation with operator |. See Q128 for why collection types are closed. See Q141 for constant immutability and copy-on-access protection.

defines module qa.collections.mutableimmutable

  defines function

    addSuffix() as pure
      ->
        items as List of String
        suffix as String
      <-
        rtn as List of String: items + suffix

  defines program

    MutableImmutableDemo()
      stdout <- Stdout()

      // === MUTATING: += MODIFIES IN PLACE ===

      fruits <- List() of String
      fruits += "apple"
      fruits += "banana"
      fruits += "cherry"
      stdout.println(`After +=: ${fruits}`)

      // === NON-MUTATING: + CREATES NEW ===

      moreFruits <- fruits + "date"
      stdout.println(`Original: ${fruits}`)
      stdout.println(`New list: ${moreFruits}`)

      // === MUTATING: -= REMOVES IN PLACE ===

      fruits -= "banana"
      stdout.println(`After -=: ${fruits}`)

      // === NON-MUTATING: - CREATES NEW ===

      lessFruits <- fruits - "cherry"
      stdout.println(`Original: ${fruits}`)
      stdout.println(`Without cherry: ${lessFruits}`)

      // === DICT MUTATING ===

      config <- {"host": "localhost"}
      config += DictEntry("port", "8080")
      stdout.println(`Config: ${config}`)

      // === PURE FUNCTION WITH COLLECTION ===

      result <- addSuffix(fruits, "!")
      stdout.println(`Pure result: ${result}`)
      stdout.println(`Original unchanged: ${fruits}`)

Java: Collections.unmodifiableList() for immutable wrappers, List.of() for immutable since Java 9, ArrayList mutable. Python: lists mutable, tuples immutable, frozenset immutable. JavaScript: no built-in immutable, use spread [...arr, item] or Object.freeze(). Rust: Vec mutable by default, shared references prevent mutation. Go: slices mutable, no immutable variant. EK9: collections mutable by default, += mutates, + creates new, pure functions enforce read-only through language rules.

collection copy data-structure immutable list minus modify mutable mutating operator plus pure side-effect

Explanation: EK9 uses the += operator to add 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 -= operator to remove items from a list, not a remove() method. See ek9 -h E50060 for details.

Explanation: EK9 List has no append() method. Use the += operator to add items. EK9 uses consistent operator syntax across all collection types rather than named methods. See ek9 -h E50060 for details.

Explanation: EK9 Dict has no put() method. Use the += operator with DictEntry to add entries. See ek9 -h E50060 for details.


Question: What is the EK9 equivalent of Python list comprehensions?

How do I translate Python list comprehensions to EK9?

How do I filter and transform lists in EK9 like Python?

What replaces [x for x in list if cond] in EK9?

Answer:
Python list comprehensions translate to EK9 stream pipelines using cat | filter | map | collect. The pipe syntax is explicit about each operation stage.

BASIC FILTERING
Python: [x for x in items if x > 0]
EK9:    cat items | filter by isPositive | collect as List of Integer
Define the predicate as a named pure function.

BASIC MAPPING
Python: [f(x) for x in items]
EK9:    cat items | map with transform | collect as List of String
Define the transform as a named pure function.

FILTER THEN MAP
Python: [f(x) for x in items if pred(x)]
EK9:    cat items | filter by pred | map with transform | collect as List of String
Pipeline stages chain naturally, filter first then map.

REDUCTION
Python: sum(x for x in items if x > 0)
EK9:    cat items | filter by isPositive | collect as Integer
collect as Integer sums the stream.

SORT AND LIMIT
Python: sorted(items)[:5]
EK9:    cat items | sort | head 5 | collect as List of Integer
head replaces Python's slice notation for taking first N.

NESTED COMPREHENSIONS
Python: [x for sublist in nested for x in sublist]
EK9:    cat nested | flatten | collect as List of String
flatten replaces nested for-loops in comprehensions.

See Q89 for basic stream pipelines. See Q120 for sorting. See Q124 for grouping. See Q125 for head/tail/skip.

defines module qa.collections.pythoncomprehensions

  defines function

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

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

    intToString() as pure
      -> num as Integer
      <- rtn as String: $num

  defines program

    PythonComprehensionsDemo()
      stdout <- Stdout()

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

      // === FILTERING ===
      // Python: [x for x in numbers if x > 0]

      positives <- cat numbers | filter by isPositive | collect as List of Integer
      stdout.println(`Positives: ${positives}`)

      // === MAPPING ===
      // Python: [x * 2 for x in numbers]

      doubled <- cat numbers | map with doubleIt | collect as List of Integer
      stdout.println(`Doubled: ${doubled}`)

      // === FILTER THEN MAP ===
      // Python: [x * 2 for x in numbers if x > 0]

      doubledPositives <- cat numbers | filter by isPositive | map with doubleIt | collect as List of Integer
      stdout.println(`Doubled positives: ${doubledPositives}`)

      // === REDUCTION ===
      // Python: sum(x for x in numbers if x > 0)

      total <- cat numbers | filter by isPositive | collect as Integer
      stdout.println(`Sum of positives: ${total}`)

      // === SORT AND LIMIT ===
      // Python: sorted(numbers)[:3]

      topThree <- cat numbers | sort | head 3 | collect as List of Integer
      stdout.println(`First 3 sorted: ${topThree}`)

      // === NESTED FLATTEN ===
      // Python: [x for sublist in nested for x in sublist]

      nested <- [["a", "b"], ["c", "d"], ["e", "f"]]
      flat <- cat nested | flatten | collect as List of String
      stdout.println(`Flattened: ${flat}`)

Python: [expr for var in iterable if cond] comprehension syntax, generator expressions with (), dict comprehensions {k: v for ...}. EK9: cat source | filter by pred | map with func | collect as Type pipeline. Key differences: EK9 requires named functions (not lambdas), explicit pipeline stages, strongly typed collection results. Both require the data source first, then operations.

collection comprehension data-structure equivalent filter list map migration pipeline python stream translate

badFilter() as pure
      -> num as Integer
      <- rtn as Integer: num

Explanation: The 'filter by' pipeline stage requires a function that returns Boolean. If the filter function returns Integer instead of Boolean, E50001 is triggered — must return a Boolean. In Python, any truthy value works as a filter. In EK9, the filter function must explicitly return Boolean. See ek9 -h E50001 for details.

Explanation: The collect type must match the pipeline's element type. An Integer pipeline cannot collect into List of String because there is no pipe operator between Integer and String. E07830 is triggered — unable to find a pipe operator for type. In Python, type mismatches fail at runtime. In EK9, the compiler catches them. See ek9 -h E07830 for details.

positives <- cat numbers | filter by isPositive | collect as List of String

Explanation: The collect type must match the pipeline's element type. An Integer pipeline cannot collect into List of String. See ek9 -h E07830 for details.

Explanation: EK9 has no .stream() or .map() methods. Use pipe syntax: cat source | map with func | collect as Type. See ek9 -h E50060 for details.

doubledPositives <- cat numbers | filter by isPositive | map with doubleIt | collect as List of String

Explanation: The collect type must match the pipeline's output element type. After map with doubleIt, the stream contains Integer, not String. See ek9 -h E07830 for details.

topThree <- cat numbers | sort | head 3 | collect as List of String

Explanation: EK9 has no Java-style flatMap() or collect() methods on List. Use | flatten in a pipeline to flatten nested lists. See ek9 -h E50060 for details.

flat <- cat nested | flatten | collect as List of Integer

Explanation: The collect type must match the pipeline's element type. A String pipeline cannot collect into List of Integer. See ek9 -h E07830 for details.


Question: What is the EK9 equivalent of the Java Collections Framework?

How do I translate Java ArrayList and HashMap code to EK9?

How do Java collection patterns map to EK9?

Answer:
EK9 provides equivalent collection types to Java's Collections Framework, but with simpler syntax and unified operator patterns.

ARRAYLIST TO LIST
Java: List<String> names = new ArrayList<>(List.of("Alice", "Bob"));
EK9:  names <- ["Alice", "Bob"]
No angle brackets, no ArrayList/List distinction, literal syntax.

HASHMAP TO DICT
Java: Map<String, Integer> ages = new HashMap<>(Map.of("Alice", 30));
EK9:  ages <- {"Alice": 30}
Literal syntax, no Map.of() or put() calls needed.

PRIORITYQUEUE TO PRIORITYQUEUE
Java: PriorityQueue<String> pq = new PriorityQueue<>(comparator);
EK9:  pq <- PriorityQueue("first").withComparator(comparator)
Fluent API with bounded size support via withSize(n).

OPTIONAL TO OPTIONAL
Java: Optional<String> name = Optional.of("Alice");
EK9:  name <- Optional("Alice")
Same concept, simpler syntax, integrated with guard variables.

STREAM API TO EK9 STREAMS
Java: list.stream().filter(x -> x > 0).map(x -> x * 2).collect(Collectors.toList());
EK9:  cat list | filter by isPositive | map with doubleIt | collect as List of Integer
Named functions instead of lambdas, pipe syntax instead of method chaining.

COLLECTORS TO COLLECT AS
Java: Collectors.groupingBy(), Collectors.summarizingInt(), Collectors.joining()
EK9:  | group by func, | collect as Stats (custom), | collect as String
Custom collectors via operator | on records.

KEY DIFFERENCES
EK9 uses 'of' instead of angle brackets for generics.
No null in EK9, tri-state semantics instead.
Operators (+=, -=, +, -) instead of method calls (add, remove, put).
All types closed (no extending List or Dict).

See Q45 for List. See Q46 for Dict. See Q121 for PriorityQueue. See Q122 for collect as custom aggregation.

defines module qa.collections.javacollections

  defines function

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

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

  defines program

    JavaCollectionsDemo()
      stdout <- Stdout()

      // === ArrayList -> List ===
      // Java: List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));

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

      // === HashMap -> Dict ===
      // Java: Map<String, Integer> ages = new HashMap<>(Map.of("Alice", 30));

      ages <- {"Alice": 30, "Bob": 25}
      age <- ages.getOrDefault("Alice", 0)
      stdout.println(`Alice age: ${age}`)

      // === Stream API -> EK9 Streams ===
      // Java: numbers.stream().filter(x -> x % 2 == 0).map(x -> x * 2).collect(Collectors.toList());

      numbers <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
      result <- cat numbers | filter by isEven | map with doubleIt | collect as List of Integer
      stdout.println(`Doubled evens: ${result}`)

      // === Collectors.summarizingInt -> collect as Integer ===
      // Java: numbers.stream().mapToInt(x -> x).sum();

      total <- cat numbers | collect as Integer
      stdout.println(`Sum: ${total}`)

      // === Optional ===
      // Java: Optional<String> name = Optional.of("Alice");

      optName <- Optional("Alice")
      if optName?
        stdout.println(`Got: ${optName}`)

      // === PriorityQueue ===
      // Java: PriorityQueue<Integer> pq = new PriorityQueue<>(comparator);

      comparator <- () extends Comparator of Integer as pure function (r:=? t1 <=> t2)
      pq <- PriorityQueue(5).withComparator(comparator)
      pq += 3
      pq += 8
      pq += 1
      stdout.println(`PQ: ${pq.list()}`)

Java: ArrayList<T>, HashMap<K,V>, PriorityQueue<T>, Optional<T>, Stream API with Collectors. EK9: List of T, Dict of (K, V), PriorityQueue of T, Optional of T, cat | pipe | collect. Key differences: EK9 literal syntax ([1,2,3] and {k:v}), 'of' instead of <>, operators instead of methods, named functions instead of lambdas, no null.

Kotlin LINQ Rust arraylist collection collectors convert data-structure equivalent framework generic hashmap java migration replace stream translate

Explanation: EK9 uses the += operator to add items to a List, not the Java-style add() method. See ek9 -h E50060 for details.

Explanation: EK9 Dict has no get() method. Use getOrDefault(key, default) for safe access. See ek9 -h E50060 for details.

Explanation: EK9 does not have Java's Stream API. No .stream(), .filter(), .map(), or .toList() methods. Use pipe syntax: cat source | filter by fn | map with fn | collect as Type. See ek9 -h E50060 for details.

Explanation: EK9 does not have toString(). Use string interpolation or the $ operator. See ek9 -h E50060 for details.


Question: How do I use tee and uniq in stream pipelines in EK9?

How do I capture intermediate stream results in EK9?

How do I remove duplicates from a stream in EK9?

How do I debug a stream pipeline in EK9?

How do I take a side copy of data as it flows through a stream pipeline in EK9?

How do I observe or inspect elements mid-pipeline in EK9?

Answer:
EK9 provides tee for capturing intermediate pipeline state and uniq for removing duplicate elements. Both are pipeline operations that integrate with cat | collect syntax.

TEE FOR SIDE EFFECTS
Tee copies each element to a variable while passing it through:
  captured <- List() of String
  cat items | tee in captured | collect as List of String
After the pipeline, captured contains all items that flowed through that point. Useful for debugging or capturing intermediate state.

TEE FOR DEBUGGING
Insert tee at any point in a pipeline to observe values:
  cat items
    | filter by isValid
    | tee in afterFilter
    | map with transform
    | collect as List of String
afterFilter shows what passed the filter before transformation.

UNIQ BY HASHCODE
Remove consecutive duplicates using the #? (hashcode) operator:
  cat items | sort | uniq | collect as List of String
Sort first to ensure duplicates are adjacent. Uses the element's #? operator for comparison.

UNIQ BY KEY EXTRACTOR
Remove duplicates based on a specific field:
  cat items | uniq by extractKey | collect as List of Item
The key extractor function determines what counts as a duplicate.

COMBINING TEE AND UNIQ
Capture state before deduplication:
  cat items
    | sort
    | tee in beforeUniq
    | uniq
    | collect as List of String
beforeUniq has all sorted items; the result has duplicates removed.

See Q89 for basic stream pipelines. See Q96 for hashcode operator (#?). See Q120 for sort (needed before uniq). See Q124 for group by. See Q235 for complete stream operations reference. See Q237 for streams vs loops decision guide.

defines module qa.collections.teeuniq

  defines function

    firstChar() as pure
      -> word as String
      <- rtn as String: $#<word

    longEnough() as pure
      -> word as String
      <- rtn <- Boolean()
      expectedCount <- 4
      rtn: length word >= expectedCount

  defines program

    TeeUniqDemo()
      stdout <- Stdout()

      words <- ["banana", "apple", "cherry", "apple", "banana", "date", "cherry", "elderberry"]

      // === TEE: CAPTURE INTERMEDIATE STATE ===

      afterFilter <- List() of String
      result <- cat words
        | filter by longEnough
        | tee in afterFilter
        | sort
        | collect as List of String
      stdout.println(`After filter (via tee): ${afterFilter}`)
      stdout.println(`Final sorted: ${result}`)

      // === UNIQ: REMOVE DUPLICATES ===

      // Sort first to make duplicates adjacent
      unique <- cat words | sort | uniq | collect as List of String
      stdout.println(`Unique: ${unique}`)

      // === UNIQ BY KEY EXTRACTOR ===

      // One word per first character
      uniqueByFirst <- cat words | sort | uniq by firstChar | collect as List of String
      stdout.println(`Unique by first char: ${uniqueByFirst}`)

      // === TEE + UNIQ TOGETHER ===

      beforeUniq <- List() of String
      deduped <- cat words
        | sort
        | tee in beforeUniq
        | uniq
        | collect as List of String
      stdout.println(`Before uniq: ${beforeUniq}`)
      stdout.println(`After uniq: ${deduped}`)

Java: stream.peek() for side effects (like tee), stream.distinct() for unique elements. Python: no built-in tee (itertools.tee is different), set() for unique. JavaScript: no built-in pipeline tee, [...new Set(arr)] for unique. Rust: iter.inspect() for tee, iter.dedup() for consecutive dedup (like EK9 uniq). Go: manual implementation for both. EK9: | tee in variable for capture, | uniq for dedup by hashcode, | uniq by func for dedup by key.

capture collection copy data-structure debug duplicate effect inspect intercept intermediate observe pipeline side stream tap tee uniq unique

Explanation: EK9 has no distinct() or stream() methods. Use | sort | uniq in a pipeline to remove consecutive duplicates by hashcode. See ek9 -h E50060 for details.

unique <- cat words | sort | uniq | collect as List of Integer

uniqueByFirst <- cat words | sort | uniq by firstChar | collect as List of Integer

Explanation: The collect type must match the pipeline's element type. After uniq by firstChar, the stream still contains String elements, not Integer. See ek9 -h E07830 for details.

| tee in afterFilter
        | sort
        | collect as List of Integer

| tee in beforeUniq
        | uniq
        | collect as List of Integer

Explanation: The tee and collect must match the pipeline type. After uniq the stream still contains Strings, so collecting into List of Integer fails. See ek9 -h E07830 for details.

Explanation: EK9 List has no toString() method. Use the $ prefix operator or string interpolation for string conversion. See ek9 -h E50060 for details.


Question: How do I use try/catch to handle exceptions in EK9?

What is the EK9 equivalent of Java try-catch?

Answer:
EK9 uses try/catch blocks to handle exceptions, similar to Java, Python, and other languages. The catch block binds the exception to a variable using the arrow syntax.

BASIC TRY/CATCH
Wrap code that might throw in a try block, and handle errors in catch:
  try
    riskyOperation()
  catch
    -> ex as Exception
    handleError(ex)
The catch block only executes if an exception is thrown inside the try block.

EXCEPTION BINDING
The catch block declares an exception variable with arrow syntax:
  catch
    -> ex as Exception
This binds the caught exception to the variable 'ex' for use within the catch body.

ACCESSING EXCEPTION INFO
Exception provides reason() and the $ operator for string conversion:
  catch
    -> ex as Exception
    stdout.println(ex.reason())
    stdout.println($ex)
The reason() method returns the message. The $ operator converts the full exception to a string.

HANDLE SYNONYM
EK9 supports 'handle' as a synonym for 'catch'. Both are identical:
  try
    riskyOperation()
  handle
    -> ex as Exception
    handleError(ex)
Use whichever reads better in context.

TRY AS EXPRESSION
Try can be used as an expression that returns a value:
  result <- try
    <- rtn as String: "default"
    rtn: computeValue()
  catch
    -> ex as Exception
    rtn: "error: " + $ex
The return variable must be declared in the try body.

See Q78 for guard variables in try blocks. See Q48 for the Result type alternative. See Q135 for try/catch/finally. See Q136 for throwing exceptions. See Q246 for debugging strategies. See Q283 for retry logic without break.

See Q304 for require preconditions. See Q305 for require vs assert vs throw.

See Q333 for transaction try-with-resources. See Q338 for partial commit prevention.

defines module qa.errorhandling.trycatch

  defines function

    riskyDivide()
      ->
        a as Integer
        b as Integer
      <- rtn <- Integer()

      if b == 0
        ex <- Exception("Division by zero")
        throw ex
      rtn: a / b

  defines program

    TryCatchBasics()
      stdout <- Stdout()

      // === BASIC TRY/CATCH ===

      try
        result <- riskyDivide(10, 2)
        stdout.println("Result: " + $result)
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)

      // === CATCHING AN EXCEPTION ===

      try
        result <- riskyDivide(10, 0)
        stdout.println("Result: " + $result)
      catch
        -> ex as Exception
        stdout.println("Caught: " + ex.reason())

      // === HANDLE SYNONYM ===

      try
        result <- riskyDivide(5, 0)
        stdout.println("Result: " + $result)
      handle
        -> ex as Exception
        stdout.println("Handled: " + $ex)

      // === TRY AS EXPRESSION ===

      message <- try
        <- rtn as String: "no result"
        value <- riskyDivide(20, 4)
        rtn: "Computed: " + $value
      catch
        -> ex as Exception
        rtn: "Failed: " + ex.reason()

      stdout.println(message)

Java: try { ... } catch (Exception e) { ... } with checked/unchecked distinction. Python: try: ... except Exception as e: ... with duck-typed exceptions. Rust: no try/catch, uses Result<T,E> and ? operator. Go: no try/catch, uses error return values. Kotlin: try/catch like Java but all exceptions unchecked. Swift: do { try expr() } catch { error }, throws keyword marks throwing functions, try? converts to optional, try! force-unwraps (crashes on error). EK9: try/catch with arrow binding (-> ex as Exception), 'handle' synonym for 'catch', all exceptions unchecked, try can be an expression.

basic binding catch error exception handle migrate reason swift syntax throw try

try catch
exception handling
error handling

catch
        ->
          ex1 as ValidationError
          ex2 as Exception

Explanation: EK9 catch blocks accept only a single exception parameter. Unlike Java's multi-catch, you cannot list multiple exception types. E50010 is triggered — only a single Exception is supported. Catch the common base type (Exception) and check specific types within the block. See ek9 -h E50010 for details.

Explanation: The catch variable must be an Exception type or a subclass of Exception. Using a non-exception type like String triggers E04030 — type must be of Exception type. Define custom exceptions with 'extends Exception'. See ek9 -h E04030 for details.

ex <- "Division by zero"
        throw ex

Explanation: Only Exception types (or subtypes) can be thrown in EK9. Throwing a String triggers E04030 — type must be of Exception type. Wrap the message in an Exception constructor. See ek9 -h E04030 for details.

stdout.println("Caught: " + ex.getMessage())

Explanation: EK9 Exception has no getMessage() method. Use reason() to get the exception message. See ek9 -h E50060 for details.

Explanation: EK9 has no toString() method. Use the $ operator or string interpolation for string conversion. See ek9 -h E50060 for details.

Explanation: EK9 has no toString() method. Use the $ operator for string conversion. See ek9 -h E50060 for details.


Question: How does try/catch/finally work in EK9?

What does finally do in EK9?
How do I guarantee cleanup in EK9?
Does EK9 have try-finally?

Answer:
The finally block in EK9 guarantees cleanup code runs regardless of whether the try block succeeds, an exception is caught, or a guard is unset.

FULL PATTERN
The complete try/catch/finally structure:
  try
    riskyOperation()
  catch
    -> ex as Exception
    handleError(ex)
  finally
    cleanup()
The finally block always runs after try and catch complete.

FINALLY ALWAYS RUNS
Finally executes in all three scenarios:
1. Try completes normally (no exception)
2. Exception thrown and caught
3. Guard variable is unset (try body skipped)
This guarantee makes finally ideal for cleanup operations.

TRY/FINALLY WITHOUT CATCH
You can use try/finally without a catch block:
  try
    doWork()
  finally
    cleanup()
If an exception occurs, it propagates after finally runs.

CLEANUP PATTERNS
Common uses for finally:
- Logging completion status
- Resetting state
- Releasing resources (though try-with-resources is preferred)

NESTED TRY BLOCKS
Try blocks can be nested. Each has its own catch and finally:
  try
    try
      innerOperation()
    catch
      -> ex as Exception
      handleInner(ex)
    finally
      innerCleanup()
  finally
    outerCleanup()

See Q134 for basic try/catch. See Q137 for try-with-resources (automatic cleanup). See Q78 for guard variables in try blocks.

defines module qa.errorhandling.trycatchfinally

  defines function

    riskyOperation()
      -> shouldFail as Boolean
      <- rtn <- String()

      if shouldFail
        ex <- Exception("Operation failed")
        throw ex
      rtn: "success"

  defines program

    TryCatchFinallyDemo()
      stdout <- Stdout()

      // === FULL PATTERN: try/catch/finally ===

      stdout.println("=== Normal execution ===")
      try
        result <- riskyOperation(false)
        stdout.println("Try: " + result)
      catch
        -> ex as Exception
        stdout.println("Catch: " + $ex)
      finally
        stdout.println("Finally: always runs")

      // === EXCEPTION PATH ===

      stdout.println("=== Exception path ===")
      try
        result <- riskyOperation(true)
        stdout.println("Try: " + result)
      catch
        -> ex as Exception
        stdout.println("Catch: " + ex.reason())
      finally
        stdout.println("Finally: still runs after catch")

      // === TRY/FINALLY WITHOUT CATCH ===

      stdout.println("=== Try/finally without catch ===")
      try
        stdout.println("Try: doing work")
      finally
        stdout.println("Finally: cleanup without catch")

      // === NESTED TRY BLOCKS ===

      stdout.println("=== Nested try blocks ===")
      try
        try
          result <- riskyOperation(true)
          stdout.println("Inner try: " + result)
        catch
          -> ex as Exception
          stdout.println("Inner catch: " + ex.reason())
        finally
          stdout.println("Inner finally")
      finally
        stdout.println("Outer finally")

Java: try { } catch { } finally { } with identical guarantee. Python: try: ... except: ... finally: ... same semantics. Rust: no try/finally, uses RAII (Drop trait) for cleanup. Go: defer statement provides cleanup guarantee. Kotlin: try/catch/finally like Java. Swift: defer keyword for cleanup (runs when scope exits, same purpose as finally), no try/finally syntax. EK9: try/catch/finally with same guarantee, also works with guard variables (unset guard skips try body but finally still runs).

always catch cleanup defer error exception finally guarantee handle nested pattern resource swift try

catch
        ->
          ex1 as Exception
          ex2 as Exception

Explanation: Catch blocks accept only a single exception parameter. EK9 does not support multi-catch syntax like Java. E50001 — only a single Exception is supported. Use the Exception base type and dispatch inside the catch body. See ek9 -h E50001 for details.

-> ex as Integer
        stdout.println("Catch: " + $ex)

Explanation: The catch parameter must be an Exception type or subtype. Using Integer triggers E04030 — type must be of Exception type. See ek9 -h E04030 for details.

stdout.println("Catch: " + ex.getMessage())

ex <- "Operation failed"
        throw ex

Explanation: Only Exception types can be thrown in EK9. Throwing a String triggers E04030 — type must be of Exception type. Wrap the message in an Exception constructor. See ek9 -h E04030 for details.

stdout.println("Inner catch: " + ex.getMessage())

Explanation: EK9 Exception has no getMessage() method. Use reason() for the exception message. See ek9 -h E50060 for details.

Explanation: EK9 has no toString() method. Use string concatenation or interpolation for string conversion. See ek9 -h E50060 for details.


Question: How do I throw exceptions in EK9?

How do I raise an error in EK9?
How do I create custom exceptions?
How do I extend Exception in EK9?

Answer:
EK9 uses the throw keyword to raise exceptions. You can throw the built-in Exception or create custom exception types by extending Exception.

CREATING EXCEPTIONS
Create an Exception with a reason string:
  ex <- Exception("Something went wrong")
Or with a reason and exit code:
  ex <- Exception("Fatal error", 1)
The exit code is used if the exception propagates to the program entry point.

THROW KEYWORD
Throw an exception variable:
  ex <- Exception("Error occurred")
  throw ex
The throw statement must reference a variable, not an inline expression.

CUSTOM EXCEPTION TYPES
Define custom exceptions by extending Exception:
  ValidationError extends Exception
    field <- String()
    ValidationError()
      ->
        reason as String
        field as String
      super(reason)
      this.field :=: field
    field() as pure
      <- rtn as String: field
    default operator ?
Custom exceptions can carry additional context beyond the reason string.

EXCEPTION PROPERTIES
All exceptions have:
  ex.reason()     the error message
  ex.exitCode()   optional exit code
  $ex             string representation
  ex?             isSet check

WHEN TO THROW
Throw exceptions for truly unexpected, exceptional conditions:
- Invalid arguments that indicate programmer error
- System failures (I/O errors, resource unavailability)
- Broken invariants that should never occur
For expected failures (validation, missing data), prefer the Result type instead.

See Q134 for try/catch. See Q138 for catching specific exception types. See Q139 for choosing between try/catch and Result.

See Q304 for require preconditions. See Q305 for require vs assert vs throw.

defines module qa.errorhandling.throwexception

  defines class

    <?-
      Custom exception for validation errors with field name context.
    -?>
    ValidationError extends Exception
      field <- String()

      ValidationError()
        ->
          reason as String
          field as String

        super(reason)
        this.field :=: field

      field() as pure
        <- rtn as String: field

      default operator ?

  defines function

    validateAge()
      -> age as Integer
      <- rtn <- String()

      if age < 0
        ex <- ValidationError("Age cannot be negative", "age")
        throw ex
      maxAmount <- 150
      if age > maxAmount
        ex <- ValidationError("Age unreasonably large", "age")
        throw ex
      rtn: "Valid age: " + $age

  defines program

    ThrowExceptionDemo()
      stdout <- Stdout()

      // === THROWING BUILT-IN EXCEPTION ===

      try
        ex <- Exception("Something went wrong")
        throw ex
      catch
        -> ex as Exception
        stdout.println("Caught: " + ex.reason())

      // === THROWING CUSTOM EXCEPTION ===

      try
        result <- validateAge(-5)
        stdout.println(result)
      catch
        -> ex as ValidationError
        stdout.println(`Validation failed on field '${ex.field()}': ${ex.reason()}`)

      // === SUCCESSFUL PATH ===

      try
        result <- validateAge(25)
        stdout.println(result)
      catch
        -> ex as ValidationError
        stdout.println("Should not reach here")

      // === EXCEPTION WITH EXIT CODE ===

      try
        ex <- Exception("Fatal startup error", 1)
        throw ex
      catch
        -> ex as Exception
        stdout.println("Exit code: " + $ex.exitCode())

Java: throw new SomeException("msg") with extends Exception or RuntimeException. Python: raise ValueError("msg") with class hierarchy. Rust: no throw, uses Result::Err() or panic!(). Go: no throw, uses error return values. Kotlin: throw SomeException("msg") like Java. Swift: throw SomeError() with Error protocol conformance, throws keyword required on function signatures. EK9: throw ex where ex is a variable (not inline new), custom exceptions use 'extends Exception' with additional fields, default operator ? required.

catch create custom error exception exitCode extends handle raise reason swift throw

stdout.println("Exit code: " + ex.getExitCode())

Explanation: EK9 Exception uses exitCode(), not getExitCode(). EK9 does not use Java-style getter naming. See ek9 -h E50060 for details.

stdout.println(`Validation failed on field '${ex.getField()}': ${ex.reason()}`)

Explanation: The method is field(), not getField(). EK9 does not use Java-style getter naming conventions. See ek9 -h E50060 for details.

ex <- "Something went wrong"
        throw ex

Explanation: Only Exception types (or subtypes) can be thrown. Throwing a String triggers E04030 — type must be of Exception type. Wrap the message in an Exception constructor. See ek9 -h E04030 for details.

ex <- "Fatal startup error"
        throw ex

Explanation: Only Exception types can be thrown in EK9. Throwing a String triggers E04030. Create an Exception with the message. See ek9 -h E04030 for details.


Question: How do I manage resources with try-with-resources in EK9?

How does try-with-resources work in EK9?

What is the EK9 equivalent of Python with statement?

Answer:
EK9 supports try-with-resources for automatic resource management. Resources declared in the try header are automatically closed when the scope exits, whether normally or via exception.

RESOURCE BINDING
Declare resources in the try header with the arrow syntax:
  try
    -> resource <- ResourceClass("config")
    useResource(resource)
  catch
    -> ex as Exception
    handleError(ex)
The resource is created before the try body and automatically closed after.

OPERATOR CLOSE
A resource class must define 'operator close' to support try-with-resources:
  ResourceClass
    name <- String()
    operator close as pure
      performCleanup()
The close operator is called automatically when the try scope exits.

RESOURCE WITH CATCH
Combine resources with error handling:
  try
    -> conn <- openConnection()
    data <- conn.read()
  catch
    -> ex as Exception
    handleError(ex)
The connection is closed whether read() succeeds or throws.

COMPARISON WITH OTHER LANGUAGES
Java: try (var r = new Resource()) { use(r); }
Python: with open(file) as f: use(f)
EK9: try -> resource <- Resource() ... (arrow syntax in try header)
All three guarantee cleanup. EK9's syntax is consistent with its guard variable pattern.

See Q134 for basic try/catch. See Q135 for try/catch/finally. See Q78 for guard variables in try blocks.

See Q333 for transaction try-with-resources pattern.

defines module qa.errorhandling.trywithresources

  defines class

    <?-
      A simple resource that tracks open/close state.
    -?>
    ManagedResource
      name <- String()

      ManagedResource()
        -> name as String
        this.name: name

      name() as pure
        <- rtn as String: name

      read() as pure
        <- rtn as String: "data from " + name

      operator close as pure
        stdout <- Stdout()
        stdout.println("Closing: " + name)

      override operator ? as pure
        <- rtn <- true

  defines function

    openResource()
      -> name as String
      <- rtn <- ManagedResource(name)

  defines program

    TryWithResourcesDemo()
      stdout <- Stdout()

      // === BASIC TRY-WITH-RESOURCES ===

      stdout.println("=== Basic resource ===")
      try
        -> resource <- ManagedResource("config.txt")
        content <- resource.read()
        stdout.println("Read: " + content)
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)

      // === RESOURCE FROM FUNCTION ===

      stdout.println("=== Resource from function ===")
      try
        -> resource <- openResource("database")
        content <- resource.read()
        stdout.println("Read: " + content)
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)

      // === RESOURCE WITH NO EXCEPTION ===

      stdout.println("=== Clean exit ===")
      result <- String()
      try
        -> resource <- ManagedResource("temp.dat")
        result: resource.read()
      catch
        -> ex as Exception
        result: "error"

      stdout.println("Result: " + result)

Java: try (var r = new Resource()) { ... } with AutoCloseable interface. Python: with open(file) as f: ... with context manager protocol (__enter__/__exit__). Rust: no try-with-resources, RAII with Drop trait handles cleanup automatically. Go: defer file.Close() after opening. Kotlin: use { } extension on Closeable. EK9: try -> resource <- expr with 'operator close' for auto-cleanup, consistent with guard variable syntax.

RAII auto automatic catch cleanup close dispose error exception handle manage migrate operator resource try

try with resources
resource management
auto close

Explanation: Try-with-resources requires 'operator close', not a regular method named close. The name 'close' is reserved for the operator and cannot be used as a method name. See ek9 -h E07660 for details.

Explanation: Try-with-resources requires the class to define 'operator close'. Without it, the type cannot be used as a resource in a try header. Renaming to a regular method removes the operator. See ek9 -h E07620 for details.

Explanation: ManagedResource has a read() method, not getData(). Check the class definition to confirm method names. See ek9 -h E50060 for details.

Explanation: EK9 has no toString() method. Use string concatenation or interpolation. See ek9 -h E50060 for details.