Question: What is the difference between :=, ?=, and :=? guard operators in EK9?

When should I use := vs ?= vs :=? in a switch statement?

How do the three assignment guard operators differ in EK9?

What guard operator should I use in EK9 control flow?

Show me all three guard operators side by side in EK9

Answer:
EK9 has three assignment guard operators with distinct semantics for EXISTING variables. Each checks something different before proceeding with the control flow body.

:= (BLIND ASSIGNMENT)
Always evaluates expression. Always assigns. No safety checks.
  switch mainValue := getValue()
    case 1 ...
Use when: Value is known safe, or an explicit 'with' condition validates it.

?= (GUARDED ASSIGNMENT - checks RIGHT side)
Always evaluates expression. Checks if result is SET. Only assigns if SET.
  switch mainValue ?= getValue()
    case 1 ...
Use when: Expression might return unset (network call, lookup, parse). Protects against assigning bad data.

:=? (ASSIGNMENT IF UNSET - checks LEFT side)
First checks if target is already set. Only evaluates expression if target is UNSET. Then checks result before assigning.
  switch mainValue :=? getValue()
    case 1 ...
Use when: Variable may already have a value (caching, defaults, priority chains). Avoids re-evaluation.

DECISION TREE:
  First time using this variable? -> Use <- (declaration guard)
  Variable exists, keep if already set? -> Use :=? (lazy, avoids re-evaluation)
  Variable exists, need to validate new value? -> Use ?= (checks result quality)
  Variable exists, value known safe? -> Use := (blind, no checks)

See Q74 for declaration guard (<-). See Q79 for :=? standalone usage. See Q759 for ?= as Boolean expression. See Q1024 for ?= across control flow. See Q1025 for := vs :=? across control flow.

defines module qa.controlflow.guard.operator.contrast

  defines function

    getZero()
      <- rtn as Integer?
      rtn: 0

    getUnsetValue()
      <- rtn as Integer?
      rtn: Integer()

    getOne()
      <- rtn as Integer?
      rtn: 1

  defines program

    GuardOperatorContrastDemo()
      stdout <- Stdout()

      // === := BLIND ASSIGNMENT ===
      // Always assigns. getZero() returns 0, matches no case -> default.
      blindResult <- Integer()

      switch blindResult := getZero()
        case 1
          stdout.println(":= matched case 1")
        default
          stdout.println(`:= hit default (value: ${blindResult})`)

      // === ?= GUARDED ASSIGNMENT (checks RIGHT side) ===
      // Evaluates getUnsetValue() which returns an unset Integer.
      // Because result is NOT set, assignment is SKIPPED and switch body is SKIPPED.
      guardedResult <- Integer()

      switch guardedResult ?= getUnsetValue()
        case 1
          stdout.println("?= matched case 1")
        default
          stdout.println("?= hit default")

      if guardedResult?
        stdout.println("?= assigned a value")
      else
        stdout.println("?= skipped (RHS unset, no assignment)")

      // === :=? ASSIGNMENT IF UNSET (checks LEFT side) ===
      // lazyResult starts unset, so :=? WILL evaluate getOne().
      // getOne() returns 1, which is set, so assignment happens and matches case 1.
      lazyResult <- Integer()

      switch lazyResult :=? getOne()
        case 1
          stdout.println(`:=? assigned (value: ${lazyResult})`)
        default
          stdout.println(":=? hit default")

Java: No equivalent. Must manually write if/else chains for each pattern. Optional.orElse() handles one level. Kotlin: Elvis (?:) handles null-only cases, not tri-state isSet. Rust: if let handles pattern matching, no assignment-if-unset. Go: if err := f(); err != nil — closest to := but no ?= or :=? equivalents. EK9: Three operators cover declaration, blind assignment, quality-checked assignment, and lazy assignment — all in one unified syntax across all control flow.

assignment blind choose comparison contrast control decision difference flow guard guarded lazy operator switch unset

guard operator comparison
switch guard operators
choosing guard operator

Explanation: EK9 has no null; use the tri-state '?' isSet operator (or a guard) instead of comparing to null. See ek9 -h E01073 for details.


Question: How does the ?= guarded assignment protect against unset values in control flow?

How does ?= work in if and switch statements?

What happens when ?= gets an unset value in EK9?

How do I safely assign from a function that might return unset?

Show me ?= guarded assignment in switch and if in EK9

Answer:
The ?= operator ALWAYS evaluates the right side expression first. It then checks if the result is SET (not null and isSet returns true). Only if the result IS set does it assign to the target variable and proceed into the control flow body.

IN IF:
  if data ?= fetchRecord(id)
    process(data)
fetchRecord is called. If the result is set, data is assigned and the body executes. If unset, the body is skipped entirely.

IN SWITCH:
  switch value ?= parseInput(text)
    case 1 ...
parseInput is called. If result is unset, the entire switch (including default) is skipped. No bad data is assigned.

KEY INSIGHT: ?= protects the TARGET variable from receiving unset data. If the expression returns something unset, the target stays unchanged. This is critical for:
  - Network calls that might fail
  - Database lookups that might find nothing
  - Parsing that might produce invalid results

COMPARISON:
  := always assigns (no protection)
  ?= checks result BEFORE assigning (protects against bad data)
  :=? checks target BEFORE evaluating (lazy, avoids unnecessary work)

See Q1023 for all three operators compared. See Q759 for ?= returning Boolean. See Q74 for <- declaration guard.

defines module qa.controlflow.guarded.assignment.flow

  defines function

    fetchSetValue()
      <- rtn as Integer?
      rtn: 42

    fetchUnsetValue()
      <- rtn as Integer?
      rtn: Integer()

  defines program

    GuardedAssignmentFlowDemo()
      stdout <- Stdout()

      // === ?= IN IF: protects against unset ===
      existing <- Integer()

      if existing ?= fetchSetValue()
        stdout.println("IF ?= with set value: assigned " + $existing)

      // Reset for next demo
      existing: Integer()

      if existing ?= fetchUnsetValue()
        stdout.println("This should not print")
      else
        stdout.println("IF ?= with unset value: body skipped")

      // === ?= IN SWITCH: skips entire switch when unset ===
      switchControl <- Integer()

      switch switchControl ?= fetchUnsetValue()
        case 42
          stdout.println("Switch matched 42")
        default
          stdout.println("Switch hit default")

      if switchControl?
        stdout.println("Switch entered with value")
      else
        stdout.println("Switch skipped entirely (RHS unset)")

      // === ?= IN SWITCH: enters switch when set ===
      switchValid <- Integer()

      switch switchValid ?= fetchSetValue()
        case 42
          stdout.println("Switch matched 42 from set value")
        default
          stdout.println("Switch hit default from set value")

Java: Must write 'var temp = fetch(); if (temp != null && temp.isValid()) { data = temp; switch(data) {...} }'. EK9: 'switch data ?= fetch()' does all of this in one line. Rust: if let Some(v) = expr — similar concept but no assignment to existing variable. Go: No equivalent — must check err separately.

assignment check control flow guard guarded if isset null-safe protect safe switch unset validate

?= in control flow
guarded assignment protection
unset value handling

Explanation: 'null' does not exist in EK9 - use '?=' to evaluate, isSet-check and conditionally assign in one step. See ek9 -h E01073 for details.


Question: How do := and :=? work differently as guard operators in EK9 control flow?

When should I use := vs :=? in a while loop or if statement?

What is the difference between blind assignment and assignment-if-unset in guards?

How does :=? avoid re-evaluation in EK9 control flow?

Show me := vs :=? in if and while in EK9

Answer:
The key difference is WHAT gets checked and WHEN the expression evaluates.

:= (BLIND ASSIGNMENT)
Always evaluates the expression. Always assigns. No isSet checks.
  if count := getCount() with count > 0
    process(count)
getCount() is called unconditionally. count is assigned unconditionally. The 'with' condition is the only gate.

:=? (ASSIGNMENT IF UNSET)
First checks if the target variable is already SET. Only evaluates the expression if the target is UNSET. Then checks the result before assigning.
  if cache :=? expensiveCompute() with cache > threshold
    useCache(cache)
If cache already has a value, expensiveCompute() is NEVER called. This is lazy evaluation.

WHEN TO USE :=
  - Fetching fresh data every time (sensor readings, API polls)
  - The value changes between calls and you want the latest
  - Combined with 'with' condition for validation

WHEN TO USE :=?
  - Caching: avoid re-computing if already have a result
  - Configuration defaults: try sources in priority order
  - First-match: assign from first source that provides a value
  - Loop with lazy init: only initialize on first iteration

See Q1023 for all three operators compared. See Q79 for :=? standalone. See Q1024 for ?= protection.

defines module qa.controlflow.assign.vs.assignifunset

  defines function

    fetchFreshData()
      <- rtn as Integer?
      rtn: 10

    expensiveCompute()
      <- rtn as Integer?
      rtn: 99

  defines program

    AssignVsAssignIfUnsetDemo()
      stdout <- Stdout()
      minCounter <- 5
      threshold <- 50

      // === := BLIND ASSIGNMENT in IF ===
      // Always evaluates, always assigns.
      counter <- Integer()

      if counter := fetchFreshData() with counter > minCounter
        stdout.println(":= fetched and condition met: " + $counter)

      // === :=? ASSIGNMENT IF UNSET in IF ===
      // cache starts unset -> expensiveCompute() IS called
      cache <- Integer()

      if cache :=? expensiveCompute() with cache > threshold
        stdout.println(":=? computed (was unset): " + $cache)

      // cache is now set to 99 -> expensiveCompute() is NOT called again
      if cache :=? expensiveCompute() with cache > threshold
        stdout.println(":=? skipped compute (already set): " + $cache)

      // === := ALWAYS RE-FETCHES (contrast with :=?) ===
      // counter already has value 10, but := overwrites it
      if counter := fetchFreshData() with counter > minCounter
        stdout.println(":= re-fetched (always evaluates): " + $counter)

Java: ':=' is like simple assignment 'x = expr;'. ':=?' requires 'if (x == null || !x.isSet()) { var temp = expr(); if (temp != null && temp.isSet()) x = temp; }' — 4 lines vs 1. Go: No ':=?' equivalent. Must write 'if x == nil { x = compute() }'. Python: No direct equivalent. 'x = x or compute()' breaks for falsy values.

assign blind cache compare conditional control default evaluation flow guard if lazy operator unset while

:= vs :=? in control flow
blind vs conditional assignment
lazy evaluation guard

Explanation: 'return' does not exist in EK9 — declare the return value with '<-' and assign it with 'rtn:'. See ek9 -h E01072 for details.


Question: Review this class. Are the names meaningful and consistent?

Check if my variable and method names are clear

Review my naming choices in this EK9 class

Answer:
The naming is mostly good but has two issues:

1. 'proc' is too abbreviated — rename to 'processOrder' or 'fulfil'. Method names should describe the action clearly.

2. 'x' for the discount amount is meaningless — rename to 'discountAmount' or 'savings'. Local variables should describe what the value represents.

The rest is fine:
- 'Order' is a clear class name
- 'totalPrice' and 'customerName' are descriptive fields
- 'applyDiscount' clearly states the action
- 'discountRate' describes the parameter's role

Good naming makes code self-documenting. A reader should understand the purpose of each variable without reading the implementation.

defines module qa.gettingstarted.reviewernaming

  defines class

    Order
      totalPrice as Float?
      customerName as String?

      default private Order()

      Order()
        ->
          name as String
          price as Float
        this.customerName: name
        this.totalPrice: price

      applyDiscount()
        -> discountRate as Float
        <- discountAmount as Float: Float()

        if discountRate?
          discountAmount: totalPrice * discountRate

      override operator ? as pure
        <- rtn as Boolean: customerName? and totalPrice?

      operator $ as pure
        <- rtn as String: `Order for ${customerName}`

  defines program

    NamingReviewDemo()
      stdout <- Stdout()

      order <- Order("Alice", 100.0)
      saving <- order.applyDiscount(0.15)
      stdout.println(`${order} saving: ${saving}`)

EK9 naming follows the same clean-code principles as any language. Use descriptive names that state purpose.

      val <- order.applyDiscount(0.15)
      stdout.println(`${order} saving: ${val}`)

Explanation: The name 'val' is one of the banned non-descriptive variable names, so 'val <- ...' triggers E11031 — use a descriptive name like saving. See ek9 -h E11031 for details.


Question: Review this design. Should I use composition instead of a large class?

Should I break this class into smaller pieces?

Assess whether this class follows single responsibility

Answer:
Yes, this class is doing too much. It handles both user validation and notification — two separate concerns.

Split it into:
- A UserValidator class that checks names and emails
- A Notifier class that sends messages
- The main class delegates to both via composition

Benefits of composition:
- Each class has one responsibility
- You can test validation without notification
- You can swap notification implementations
- EK9 types are closed by default, so composition is the standard extension pattern

Keep each class focused on one concern. Compose them together in a coordinator class.

defines module qa.gettingstarted.compositionreview

  defines class

    UserValidator
      isValidName() as pure
        -> name as String
        <- rtn as Boolean: name.length() > 0

      isValidEmail() as pure
        -> email as String
        <- rtn as Boolean: email.contains("@")

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

      operator $ as pure
        <- rtn as String: "UserValidator"

    Notifier
      sendWelcome()
        -> name as String
        <- rtn as String: `Welcome ${name}`

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

      operator $ as pure
        <- rtn as String: "Notifier"

  defines program

    CompositionDemo()
      stdout <- Stdout()

      validator <- UserValidator()
      notifier <- Notifier()

      userName <- "Alice"
      email <- "alice@example.com"

      if validator.isValidName(userName) and validator.isValidEmail(email)
        message <- notifier.sendWelcome(userName)
        stdout.println(message)

EK9 types are closed by default. Composition is the primary way to combine behavior from multiple sources.

composition design responsibility review split

composition over inheritance
single responsibility

    Notifier extends UserValidator
      sendWelcome()

Explanation: EK9 types are closed by default and cannot be extended; 'extends UserValidator' fails with E05030 - use composition instead. See ek9 -h E05030 for details.


Question: Write a module-level documentation comment explaining what this code does for a future maintainer.

Document this EK9 module for someone reading it in 6 months

Write a description of this module's purpose and design

Explain this code for documentation purposes

Answer:
Module: inventory.pricing

Provides price calculation functions for the inventory system. All functions are pure — they compute results from inputs without side effects.

calculateDiscount: Applies a percentage discount to a base price. Returns the discounted price, or the original price if the discount percentage is unset.

applyTax: Adds tax to a price at the given rate. Returns the price with tax included.

formatPrice: Converts a Float price to a display String with two decimal places.

Design notes:
- All functions are marked 'as pure' for thread safety and testability
- Guard expressions handle unset inputs gracefully
- Functions are small and focused — each does one thing
- The module has no state — it is a collection of utility functions

defines module inventory.pricing

  defines function

    <?-
      Applies a percentage discount to a base price.
      Returns the discounted price, or the original if discount is unset.
    -?>
    calculateDiscount() as pure
      ->
        basePrice as Float
        discountRate as Float
      <- rtn as Float: basePrice

      if discountRate?
        rtn := basePrice - (basePrice * discountRate)

    <?-
      Adds tax to a price at the given rate.
    -?>
    applyTax() as pure
      ->
        netPrice as Float
        taxRate as Float
      <- rtn as Float: netPrice + (netPrice * taxRate)

    <?-
      Converts a price to a display string.
    -?>
    formatPrice() as pure
      -> amount as Float
      <- rtn as String: `${amount}`

  defines program

    PricingDemo()
      stdout <- Stdout()

      basePrice <- 100.0
      discountRate <- 0.15
      taxRate <- 0.20

      discounted <- calculateDiscount(basePrice, discountRate)
      withTax <- applyTax(discounted, taxRate)
      stdout.println(`Price: ${formatPrice(withTax)}`)

EK9 documentation describes what the code does, its design decisions, and how to use it — without comparing to other languages.

document explain maintainer module purpose

      data <- calculateDiscount(basePrice, discountRate)
      withTax <- applyTax(data, taxRate)

Explanation: Generic non-descriptive variable names like 'data' are banned; use a meaningful identifier such as 'discounted'. See ek9 -h E11031 for details.


Question: My code uses := for the first variable. Fix it with the minimum change.

Quick fix: I used := instead of <- to declare a variable

What is the minimal fix when := is used before <-?

I get an error on my first assignment. What is the simplest fix?

Answer:
Change := to <-. That is the only change needed.

BEFORE (wrong):
  greeting := "Hello"

AFTER (fixed):
  greeting <- "Hello"

The <- operator declares a new variable. The := operator updates an existing one. If the variable does not exist yet, you must use <- to create it.

For subsequent assignments to the same variable, := is correct:
  greeting <- "Hello"     first time: <- to create
  greeting := "Hi there"  after: := to update

defines module qa.gettingstarted.quickfixes

  defines program

    QuickFixDemo()
      stdout <- Stdout()

      //Correct: <- to declare
      greeting <- "Hello"
      stdout.println(greeting)

      //Correct: := to update
      greeting := "Hi there"
      stdout.println(greeting)

The fix is always minimal: change := to <- for the first use of a variable.

assign declare fix generalist minimal quick

Explanation: Use <- to declare a new variable. Using := on a name that does not exist yet means it cannot be resolved.


Question: Review this function. Is it too complex?

Is this code doing too much in one function?

Should I break this function into smaller pieces?

Assess the complexity of this EK9 function

Answer:
Yes, this function is too complex. It does three things: validates input, calculates a result, and formats output. Each concern should be a separate pure function.

Split into:
- validateInput: checks if the values are usable
- calculateTotal: does the arithmetic
- formatResult: converts to a display string

Benefits:
- Each function is testable in isolation
- Pure functions can be reused elsewhere
- The main function becomes a pipeline of clear steps
- Complexity is distributed across small, readable units

EK9 encourages small pure functions composed together. When a function has multiple responsibilities, split it.

defines module qa.gettingstarted.reviewcomplexity

  defines function

    validateAmount() as pure
      -> amount as Float
      <- rtn as Boolean: amount?

    calculateWithTax() as pure
      ->
        amount as Float
        taxRate as Float
      <- rtn as Float: amount + (amount * taxRate)

    formatCurrency() as pure
      -> amount as Float
      <- rtn as String: `Total: ${amount}`

  defines program

    ComplexityDemo()
      stdout <- Stdout()

      amount <- 99.50
      taxRate <- 0.20

      if validateAmount(amount)
        total <- calculateWithTax(amount, taxRate)
        stdout.println(formatCurrency(total))
      else
        stdout.println("Invalid amount")

EK9 encourages small, pure functions composed together. Split complex functions into single-responsibility pieces.

complexity assessment
function decomposition

        value <- calculateWithTax(amount, taxRate)
        stdout.println(formatCurrency(value))

Explanation: 'value' is a banned non-descriptive variable name in EK9 — use a meaningful identifier such as 'total'. See ek9 -h E11031 for details.


Question: Explain the design decisions in this EK9 code for a team wiki.

Document the design rationale behind this code

Write a technical explanation of this module for new team members

Explain why this code is structured this way

Answer:
Design Rationale for UserService module:

Structure: The module separates validation (pure functions) from state management (the UserService class). This separation means validation logic can be tested without setting up a service instance.

Validation functions are marked 'as pure' because they only compute results from their inputs — no side effects, no state changes. This makes them safe to call from any context, including concurrent code.

The UserService class uses guard expressions in its methods. When findUser returns a value, the guard (if user <- findUser(name)) ensures the block only runs when a valid user was found. There is no null checking and no exception handling for missing users — the guard handles it.

Return values are declared at the top of each function with <- . This is how EK9 functions declare what they return — it is not a statement that causes an early exit. Every code path must initialise the return value.

The class has a default private constructor because its properties are uninitialised at construction time. Only the parameterised constructor is public.

defines module qa.gettingstarted.designrationale

  defines function

    isValidName() as pure
      -> name as String
      <- rtn as Boolean: name? and name.length() > 0

    isValidEmail() as pure
      -> email as String
      <- rtn as Boolean: email? and email.contains("@")

  defines class

    User
      name as String?
      email as String?

      default private User()

      User()
        ->
          name as String
          email as String
        this.name: name
        this.email: email

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

      operator $ as pure
        <- rtn as String: `${name} (${email})`

  defines program

    DesignRationaleDemo()
      stdout <- Stdout()

      name <- "Alice"
      email <- "alice@example.com"

      if isValidName(name) and isValidEmail(email)
        user <- User(name, email)
        stdout.println(`Created: ${user}`)

EK9 design documentation explains what the code does and why, without comparing to other languages.

design document explain rationale team wiki

      operator $ as pure
        <- rtn as String?

Explanation: A non-abstract operator or method must provide an implementation — declaring only an uninitialised return '<- rtn as String?' leaves it with no body, so give the return a value. See ek9 -h E07110 for details.


Question: My function uses return to send back a value. How do I fix it?

Quick fix: replace return statement in EK9

EK9 has no return keyword. What do I use instead?

How do I return a value from an EK9 function?

Answer:
EK9 has no return statement. Declare the return value with <- at the top of the function, then assign to it.

BEFORE (wrong):
  calculateArea()
    -> radius as Float
    return 3.14159 * radius * radius

AFTER (fixed):
  calculateArea() as pure
    -> radius as Float
    <- rtn as Float: 3.14159 * radius * radius

The <- rtn as Float line declares a variable called rtn that IS the return value. You can assign to it with := later in the function if needed:

  findLabel()
    -> code as Integer
    <- rtn as String: "unknown"

    switch code
      case 1
        rtn := "active"
      case 2
        rtn := "inactive"

Every code path must initialise the return value. The compiler enforces this — there is no way to forget.

defines module qa.gettingstarted.fixreturn

  defines function

    calculateArea() as pure
      -> radius as Float
      <- rtn as Float: 3.14159 * radius * radius

    findLabel() as pure
      -> code as Integer
      <- rtn as String: "unknown"

      switch code
        case 1
          rtn := "active"
        case 2
          rtn := "inactive"
        default
          rtn := "unknown"

  defines program

    FixReturnDemo()
      stdout <- Stdout()

      area <- calculateArea(5.0)
      stdout.println(`Area: ${area}`)

      label <- findLabel(1)
      stdout.println(`Label: ${label}`)

EK9 uses declared return values instead of return statements. The compiler ensures all paths assign a value.

return value declaration
no return statement

Explanation: 'return' does not exist in EK9. Declare the return value with <- at the function top, then assign to it.


Question: I used := to create a list. How do I fix it?

Quick fix: used := instead of <- for a new list

Fix my list creation: names := [Alice, Bob]

Answer:
Change := to <-. Use <- for first-time declarations, including lists.

BEFORE (wrong):
  names := ["Alice", "Bob", "Charlie"]

AFTER (fixed):
  names <- ["Alice", "Bob", "Charlie"]

For an empty list, declare the type explicitly:
  names <- List of String()

For a list with items, the type is inferred from the contents:
  numbers <- [1, 2, 3]         List of Integer
  prices <- [9.99, 12.50]      List of Float

The := operator is for updating an existing variable:
  names <- ["Alice"]           create with <-
  names := ["Alice", "Bob"]    update with :=

defines module qa.gettingstarted.fixlistcreation

  defines program

    FixListDemo()
      stdout <- Stdout()

      //Correct: <- to create a new list
      names <- ["Alice", "Bob", "Charlie"]
      stdout.println(`Names: ${names}`)

      //Correct: := to update an existing list
      names := ["Alice", "Bob", "Charlie", "Dave"]
      stdout.println(`Updated: ${names}`)

      //Empty list with explicit type
      scores <- List() of Integer
      stdout.println(`Empty scores: ${scores}`)

EK9 uses <- for all first-time declarations, including list literals. Use := only to reassign.

collection create declare fix list quick

Explanation: Use <- to declare a new variable. The := operator expects the variable to already exist.


Question: Write a function that transforms a list of names to uppercase.

How do I map a list of strings to uppercase in EK9?

Write code to convert all items in a list using a stream

Show me how to use map with in a stream pipeline

Answer:
Use a stream pipeline with map to transform each item:

  uppercased <- cat names
    | map with toUpper
    | collect as List of String

The 'map with' operation applies a function to each item in the stream. The function must take one argument and return one value.

Define the transformation as a named pure function:
  toUpper() as pure
    -> name as String
    <- rtn as String: name.upperCase()

Then use it in the pipeline. This is cleaner than a loop because:
- The intent is clear: transform each item
- The function is testable on its own
- No mutable accumulator needed

For output instead of collecting:
  cat names | map with toUpper > stdout

defines module qa.streams.codertransform

  defines function

    toUpper() as pure
      -> name as String
      <- rtn as String: name.upperCase()

    addGreeting() as pure
      -> name as String
      <- rtn as String: "Hello, " + name

  defines program

    StreamTransformDemo()
      stdout <- Stdout()

      names <- ["alice", "bob", "charlie"]

      //Transform to uppercase
      uppercased <- cat names
        | map with toUpper
        | collect as List of String
      stdout.println(`Uppercased: ${uppercased}`)

      //Chain transformations
      cat names
        | map with toUpper
        | map with addGreeting
        > stdout

EK9 uses 'cat source | map with fn | collect as Type' for list transformations. Named functions replace lambdas.

coder map pipeline stream transform uppercase

uppercased <- names.map(n -> n.upperCase())

Explanation: EK9 streams use pipe syntax with named functions. Write 'cat source | map with functionName | collect as Type'.


Question: Write code to filter a list and collect the results into a new list.

How do I filter items from a list into a new list in EK9?

Show the EK9 way to select matching items from a collection

Write a stream pipeline that filters and collects

Answer:
Use a stream pipeline with filter and collect:

  longNames <- cat names
    | filter by isLongName
    | collect as List of String

The filter function must return Boolean. Items where the function returns true pass through.

  isLongName() as pure
    -> name as String
    <- rtn as Boolean: name.length() > 4

You can combine filter with other operations:
  cat names
    | filter by isLongName
    | sort
    | head 3
    | collect as List of String

This reads naturally: take names, keep long ones, sort them, take first 3, collect into a list.

For counting matches, use the collected list:
  matches <- cat items | filter by predicate | collect as List of T
  count <- matches.length()

defines module qa.streams.coderfiltercollect

  defines function

    isLongName() as pure
      -> name as String
      <- rtn as Boolean?

      minLength <- 4
      rtn: name.length() > minLength

  defines program

    FilterCollectDemo()
      stdout <- Stdout()

      names <- ["Al", "Alice", "Bob", "Charlie", "Dave", "Elizabeth"]

      //Filter and collect
      longNames <- cat names
        | filter by isLongName
        | collect as List of String
      stdout.println(`Long names: ${longNames}`)

      //Filter, sort, and take first 2
      topTwo <- cat names
        | filter by isLongName
        | sort
        | head 2
        | collect as List of String
      stdout.println(`Top two: ${topTwo}`)

EK9 uses 'cat source | filter by fn | collect as Type' instead of loops with conditionals.

coder collect filter list pipeline stream

      longNames <- []
      for name in names
        if name.length() > 4
          longNames.append(name)

Explanation: EK9 has no .append() or [] empty literal. Use a stream pipeline: cat source | filter by fn | collect as Type.


Question: Plan a search feature in EK9. What constructs should I use?

How should I structure a search feature in EK9?

What EK9 patterns work best for implementing search?

Plan the architecture for a search module in EK9

Answer:
Search feature plan using EK9 constructs:

1. Define the search criteria as a record:
   SearchCriteria with keyword as String and maxResults as Integer
   Records are immutable data carriers — perfect for search parameters.

2. Define the result type as a record:
   SearchResult with title as String and score as Float
   Immutable results can be safely passed between components.

3. Write a pure matching function:
   matchesCriteria() as pure
     Takes an item and criteria, returns Boolean.
   Pure functions are testable without mocking.

4. Use a stream pipeline to search:
   results <- cat allItems
     | filter by matchesCriteria
     | sort by score
     | head maxResults
     | collect as List of SearchResult
   The pipeline expresses the search algorithm declaratively.

5. Use guard expressions for the caller:
   if results <- performSearch(criteria)
     Display results
   The guard handles the case where no results are found.

Key EK9 choices:
- Records for data (immutable, automatic operators)
- Pure functions for logic (testable, composable)
- Streams for collection processing (declarative, no loops)
- Guards for result handling (null-safe, concise)

defines module qa.gettingstarted.plansearch

  defines function

    matchesKeyword() as pure
      -> title as String
      <- rtn as Boolean: title.length() > 0

    formatTitle() as pure
      -> title as String
      <- rtn as String: `Found: ${title}`

  defines program

    SearchDemo()
      stdout <- Stdout()

      titles <- ["EK9 Streams Guide", "EK9 Guard Patterns", "Getting Started"]

      //Stream pipeline for search
      cat titles
        | filter by matchesKeyword
        | map with formatTitle
        > stdout

EK9 planning uses records for data, pure functions for logic, streams for collection processing, and guards for result handling.

architecture guard plan record search stream

feature planning
EK9 construct selection


Question: Plan a refactoring to improve this code. What EK9 constructs should I use?

How should I refactor this procedural code into idiomatic EK9?

What is the EK9 way to restructure this code?

Plan an EK9 refactoring with specific construct recommendations

Answer:
Refactoring plan using EK9 constructs:

1. Extract pure functions for each calculation:
   Move validation and computation into separate pure functions. Pure functions are easier to test and can be reused in stream pipelines.

2. Replace loops with stream pipelines:
   If you are filtering or transforming a collection, use:
     cat items | filter by predicate | map with transform | collect as List of T
   Streams express intent more clearly than loops.

3. Use switch expressions instead of if-else chains:
   switch value
     case 1
       handleOne()
     case 2, 3
       handleFewCases()
     default
       handleOther()
   Multiple case values on one line replace fallthrough.

4. Use guard expressions for conditional logic:
   if result <- computeSomething()
     process(result)
   Guards combine computation and null-safety checking.

5. Use records for data transfer:
   Records give you automatic operators (equality, string representation, hashcode) with no boilerplate.

Priority order: extract pure functions first, then replace loops with streams, then clean up conditionals.

defines module qa.gettingstarted.planrefactor

  defines function

    isValid() as pure
      -> price as Float
      <- rtn as Boolean: price?

    applyMarkup() as pure
      -> price as Float
      <- rtn as Float: price * 1.20

    formatPrice() as pure
      -> price as Float
      <- rtn as String: `Price: ${price}`

  defines program

    RefactorDemo()
      stdout <- Stdout()

      prices <- [10.0, 25.50, 0.0, 42.99]

      //Stream pipeline: validate, transform, format, output
      cat prices
        | filter by isValid
        | map with applyMarkup
        | map with formatPrice
        > stdout

EK9 refactoring prioritises pure function extraction, stream pipeline adoption, and guard expressions.

refactoring plan
EK9 construct selection

      -> value as Float
      <- rtn as String: `Price: ${value}`

Explanation: 'value' is a banned non-descriptive variable name in EK9 — use a meaningful identifier like 'price'. See ek9 -h E11031 for details.


Question: How do I try multiple configuration sources and use the first one that has a value?

What is the EK9 pattern for fallback configuration?

How does :=? work for default value chains?

Show me priority-ordered assignment in EK9

Answer:
Use :=? (guarded assignment) to try sources in priority order. Each :=? only evaluates its right side if the variable is still unset.

  setting <- String()
  setting :=? loadFromEnvironment()
  setting :=? loadFromConfigFile()
  setting :=? loadFromDefaults()

This tries environment first. If it returns a set value, the variable is assigned and the remaining calls are skipped. If not, it tries the config file, then defaults.

Why :=? instead of :=
  With :=, every source would be called and the last one always wins.
  With :=?, the first source that provides a value wins. Later sources are not evaluated.

This pattern replaces:
  if setting is not set then try next source
without any explicit checking. The operator handles it.

Use :=? when:
  - You want first-wins priority ordering
  - Later sources are expensive to evaluate
  - You want to preserve an existing value if present

defines module qa.controlflow.guardedassign.configdefaults

  defines function

    loadFromEnvironment()
      <- rtn as String: String()
      //Simulate: environment variable not set

    loadFromConfigFile()
      <- rtn as String: "from-config"

    loadFromDefaults()
      <- rtn as String: "default-value"

  defines program

    ConfigDefaultsDemo()
      stdout <- Stdout()

      //Priority chain: environment -> config file -> defaults
      setting <- String()
      setting :=? loadFromEnvironment()
      setting :=? loadFromConfigFile()
      setting :=? loadFromDefaults()

      stdout.println(`Setting resolved to: ${setting}`)

      //Second example: already has a value, so :=? skips
      name <- loadFromEnvironment()
      name :=? loadFromDefaults()
      stdout.println(`Name preserved: ${name}`)

Java needs explicit null checks for each source. Python uses 'x = x or next_source()' but fails with falsy values. EK9's :=? handles it correctly in one operator.

assign config default fallback guarded priority

guarded assignment
configuration defaults
priority chain

      if setting == null
        setting := loadFromConfigFile()

Explanation: 'null' does not exist in EK9. Use ':=?' to conditionally assign — it only evaluates and assigns when the variable is currently unset.


Question: When do I use <- and when do I use :=? to set a variable safely?

What is the difference between <- and :=? in EK9?

Should I use declaration or guarded assignment here?

How do I choose between <- for new variables and :=? for safe reassignment?

Answer:
They solve different problems:

<- CREATES a new variable. Use it when the variable does not exist yet.
  name <- "Alice"           creates name, assigns "Alice"
  if greeting <- getGreeting()  creates greeting in this scope, only enters if SET

:=? REASSIGNS an existing variable, but only if it is currently UNSET. Use it when the variable already exists and you want to fill in a missing value without overwriting an existing one.
  name <- String()           creates name (unset)
  name :=? loadFromConfig()  assigns only if name is still unset
  name :=? "default"         assigns only if still unset after config

Key distinction:
  <- is about CREATION. The variable must not exist yet.
  :=? is about SAFE UPDATE. The variable must already exist. It protects existing values.

Common mistake: using <- when you mean :=?
  name <- loadFromConfig()      WRONG if name already exists in scope
  name :=? loadFromConfig()     RIGHT — safely fills in if unset

Common mistake: using :=? when you mean <-
  name :=? "Alice"              WRONG if name does not exist yet
  name <- "Alice"               RIGHT — creates the variable

In guards (if, switch, while):
  if name <- getGreeting()      declaration guard — creates name, enters if SET
  if name :=? getGreeting()     guarded assign — fills name if unset, enters if SET

defines module qa.controlflow.declare.vs.guardedassign

  defines function

    loadFromConfig()
      <- rtn as String: String()
      //Simulate: config returns unset

    loadFromEnvironment()
      <- rtn as String: "env-value"

  defines program

    DeclareVsGuardedDemo()
      stdout <- Stdout()

      // <- creates a new variable
      setting <- String()

      // :=? fills in if unset (config returns unset, so still unset)
      setting :=? loadFromConfig()

      // :=? fills in from next source (environment returns "env-value")
      setting :=? loadFromEnvironment()

      // :=? preserves existing value (already set, so "fallback" is ignored)
      setting :=? "fallback"

      stdout.println(`Setting: ${setting}`)

      // Contrast: <- in a guard creates a NEW variable
      if greeting <- loadFromEnvironment()
        stdout.println(`Greeting: ${greeting}`)

No other language has this distinction. Python's walrus := creates AND assigns. EK9 separates creation (<-) from safe update (:=?).

assign create declare difference guarded safe unset

<- vs :=?
declaration vs guarded assignment

Explanation: Use <- to create a new variable; :=? only reassigns a variable that already exists, so using it first leaves the name unresolved. See ek9 -h E50001 for details.


Question: I used := to create a variable and got an error. Why?

Why can't I use := to make a new variable in EK9?

The compiler says my variable doesn't exist but I used :=

In Python := creates a variable. Why doesn't it work in EK9?

Answer:
In EK9, := NEVER creates a variable. It only updates one that already exists.

The rule is simple:
  <- creates a new variable (declaration)
  := updates an existing variable (reassignment)

They are completely separate operations. There is no overlap.

BEFORE (error):
  count := 10           error: count does not exist yet

AFTER (correct):
  count <- 10           creates count with value 10
  count := 20           later: updates count to 20

This is different from Python where := (walrus) creates AND assigns inside expressions. In EK9, := is a plain assignment statement that requires the variable to already exist.

This is also different from Go where := creates a new variable. In EK9, := never creates — only <- creates.

See Q1041 for Python developers. See Q1042 for Go developers. See Q1029 for a quick fix example. See Q965 for the full assignment operators guide.

defines module qa.gettingstarted.assignnevercreates

  defines program

    AssignNeverCreatesDemo()
      stdout <- Stdout()

      // <- creates the variable
      count <- 10
      stdout.println(`Created: ${count}`)

      // := updates the existing variable
      count := 20
      stdout.println(`Updated: ${count}`)

      // Another example
      message <- "hello"
      message := "goodbye"
      stdout.println(message)

Python := (walrus) creates and assigns in expressions. Go := creates new variables. EK9 := does neither — it ONLY updates existing variables. Use <- to create.

assign create declare error go python walrus

:= never creates
declaration vs assignment

Explanation: The := operator requires the variable to already exist. Use <- to create a new variable. In EK9, := never creates — it only reassigns.


Question: I come from Python. I keep using := where I should use <-. How do I remember the difference?

As a Python developer, how do I stop confusing := and <- in EK9?

Python habit: I write := for everything. What is the EK9 rule?

Help me break the Python := habit in EK9

Answer:
The Python habit to break: in Python, := (walrus) creates a variable inside an expression. In EK9, := NEVER creates anything.

Simple rule: first time you mention a name, use <-. Every time after, use :=.

  name <- "Alice"       FIRST mention: <- to create
  name := "Bob"         SECOND mention: := to update

Think of <- as 'born here' and := as 'changed here'.

Python patterns mapped to EK9:

  Python: x = 10              EK9: x <- 10        (first use)
  Python: x = 20              EK9: x := 20        (update)
  Python: if (m := re.match)  EK9: if m <- match() (guard creates m)
  Python: [x := f(y)]         EK9: not needed — use stream pipeline

The compiler catches every mistake. If you use := on a name that does not exist, the compiler tells you. If you use <- on a name that already exists, the compiler tells you. You cannot get it wrong without knowing.

See Q1040 for the general rule. See Q986 for walrus vs EK9 assign. See Q1039 for <- vs :=? distinction.

defines module qa.gettingstarted.pythonassigntrap

  defines function

    findMatch() as pure
      -> text as String
      <- rtn as String: String()

      minLength <- 3
      if text.length() > minLength
        rtn: text

  defines program

    PythonTrapDemo()
      stdout <- Stdout()

      // <- creates (like Python's first x = ...)
      greeting <- "Hello"
      stdout.println(greeting)

      // := updates (like Python's second x = ...)
      greeting := "Hi there"
      stdout.println(greeting)

      // <- in guard (replaces Python's walrus := in if)
      if match <- findMatch("EK9 language")
        stdout.println(`Found: ${match}`)

Python developers: forget := walrus habits. EK9 <- is for creation, := is for update. The compiler enforces this — you cannot confuse them without an error.

assign declare habit migrate python trap walrus

Python := migration
breaking walrus habit

Explanation: In EK9 ':=' only updates an existing variable; use '<-' the first time you introduce a name, or it is not resolved. See ek9 -h E50001 for details.


Question: I come from Go. How does EK9 assignment differ from Go's := and =?

As a Go developer, what are the EK9 equivalents of := and =?

Go uses := to declare. What does EK9 use?

Map Go's short variable declaration to EK9 syntax

Answer:
Go and EK9 both separate declaration from assignment, but they use OPPOSITE symbols.

Go:  := declares (creates)    = assigns (updates)
EK9: <- declares (creates)    := assigns (updates)

Go's := is EK9's <-
Go's = is EK9's :=

Examples:
  Go:  name := "Alice"        EK9: name <- "Alice"
  Go:  name = "Bob"           EK9: name := "Bob"
  Go:  count := 0             EK9: count <- 0
  Go:  count = count + 1      EK9: count := count + 1

The key difference: in Go, := can also reassign in multi-return contexts. In EK9, <- ONLY creates. It never reassigns. The separation is absolute.

Another difference: EK9 has :=? (guarded assignment) which Go does not have. It only assigns if the target is currently unset.

defines module qa.gettingstarted.goassigntrap

  defines program

    GoTrapDemo()
      stdout <- Stdout()

      // Go: name := "Alice"  →  EK9: name <- "Alice"
      name <- "Alice"
      stdout.println(name)

      // Go: name = "Bob"  →  EK9: name := "Bob"
      name := "Bob"
      stdout.println(name)

      // Go: count := 0  →  EK9: count <- 0
      count <- 0
      count := count + 1
      stdout.println(`Count: ${count}`)

Go := declares, EK9 <- declares. Go = assigns, EK9 := assigns. The symbols are swapped. EK9 also adds :=? for conditional assignment which Go lacks.

assign declare go golang migrate short variable

Explanation: Go developers: your := habit means 'declare' but in EK9, := means 'update'. Swap to <- for declarations.


Question: I get E05030 'not open to be extended'. What does this mean and how do I fix it?

Diagnose E05030: my class cannot be extended

The compiler says my type is not open. What is going on?

Why can't I extend this class? I get E05030

Answer:
E05030 means you tried to extend a type that is closed. In EK9, ALL types are closed by default — they cannot be extended unless explicitly marked 'as open'.

Diagnosis steps:
1. Find the class you are trying to extend
2. Check if it has 'as open' in its declaration
3. If it does not, it is closed and cannot be extended

Fix option 1 — make the parent open (if you own it):
  BEFORE: MyBase
  AFTER:  MyBase as open

Fix option 2 — use composition instead (recommended):
  BEFORE: MyChild extends MyBase
  AFTER:  MyChild
            delegate as MyBase?

Composition is the preferred pattern in EK9. It avoids tight coupling and works with all types, including built-in types like List and Dict which are always closed.

Built-in types (List, Dict, Optional, Result) can NEVER be extended. Always use composition with these.

Run 'ek9 -h E05030' for the full compiler explanation.

See Q1044 for extending built-in types. See Q102 for the 'as open' modifier. See Q109 for composition patterns. See Q1027 for a design review using composition.

defines module qa.debugging.diagnosee05030

  defines class

    //This class is open — it CAN be extended
    Vehicle as open
      speed <- Float()

      default Vehicle()

      accelerate()
        -> amount as Float
        speed :=? amount

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

      operator $ as pure
        <- rtn as String: "Vehicle"

    //This extends Vehicle — allowed because Vehicle is open
    Car is Vehicle
      brand as String?

      default private Car()

      Car()
        -> brand as String
        this.brand: brand

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

      override operator $ as pure
        <- rtn as String: `${brand} car`

  defines program

    DiagnoseE05030Demo()
      stdout <- Stdout()

      car <- Car("Ford")
      car.accelerate(60.0)
      stdout.println(`${car}`)

Java classes are open by default. EK9 classes are closed by default. Use 'as open' to opt in, or use composition instead.

E05030 closed diagnose error extend investigate open

Explanation: Removing 'as open' makes Vehicle a closed type (EK9 types are closed by default), so 'Car is Vehicle' is rejected as not open to be extended. See ek9 -h E05030 for details.


Question: I tried to extend List and got E05030. How do I wrap a collection instead?

Why can't I extend List of String in EK9?

How do I create a custom list type without inheritance?

Diagnose E05030 on a built-in generic type

Answer:
Built-in generic types (List, Dict, Optional, Result, PriorityQueue, MutexLock, DictEntry) are permanently closed. They cannot be extended. This is by design — extending collections mixes container behaviour with application logic.

The fix is composition: hold the collection as a property and expose only the operations you need.

BEFORE (E05030 error):
  UniqueNames extends List of String   closed — cannot extend

AFTER (composition):
  UniqueNames
    items as List of String?

    add()
      -> name as String
      if not items.contains(name)
        items += name

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

Benefits of composition:
- You control the API — only expose what makes sense
- The internal collection type can change without affecting callers
- Works with any built-in type, not just open ones
- Your type gets its own operators (?, $, etc.)

Run 'ek9 -h E05030' for the compiler explanation.

See Q1043 for E05030 on user-defined types. See Q128 for why collections are closed. See Q212 for composition over inheritance.

defines module qa.debugging.diagnoseclosedbuiltin

  defines class

    UniqueNames
      items as List of String?

      UniqueNames()
        items: List() of String

      add()
        -> name as String
        if not items.contains(name)
          items += name

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

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

      operator $ as pure
        <- rtn as String: `UniqueNames(${count()} items)`

  defines program

    ClosedBuiltinDemo()
      stdout <- Stdout()

      names <- UniqueNames()
      names.add("Alice")
      names.add("Bob")
      names.add("Alice")
      stdout.println(`${names}`)

Java allows extending ArrayList. EK9 collections are closed. Use composition to create custom collection wrappers.

E05030 builtin closed composition extend list wrap

E05030 built-in types
collection composition

    UniqueNames
      items as List of String?

Explanation: List is a closed built-in type. Use composition: hold a List property and delegate methods to it.


Question: Review this design. Should it use composition instead of inheritance?

Is inheritance the right choice here or should I use composition?

Assess whether this class hierarchy is appropriate

When should I use composition over inheritance in EK9?

Answer:
In EK9, composition is generally preferred over inheritance. Types are closed by default — you must explicitly opt into extension with 'as open'.

WHEN INHERITANCE IS APPROPRIATE:
- True 'is-a' relationship (Circle is a Shape)
- Shared behaviour that varies by subtype (abstract methods)
- Small, stable hierarchies (2-3 levels max)

WHEN COMPOSITION IS BETTER:
- 'Has-a' relationship (Car has an Engine, not Car is an Engine)
- Combining capabilities from multiple sources (use traits)
- When the parent class might change (composition isolates you)
- When you need to swap implementations at runtime

EK9 COMPOSITION TOOLS:
- Traits for shared contracts: 'with trait of Printable'
- Delegation with 'by' keyword
- Records for pure data aggregation
- Components for DI-managed services

The compiler enforces max inheritance depth — if you hit the limit, it is a signal to refactor toward composition.

defines module qa.codequality.reviewcomposition

  defines trait

    Describable
      describe() as pure
        <- rtn as String?

  defines class

    Engine
      horsePower as Integer: 0

      Engine()
        -> horsePower as Integer
        this.horsePower :=: horsePower

      horsePower() as pure
        <- rtn as Integer: horsePower

      default operator

    //Composition: Car HAS an Engine (correct)
    Car with trait of Describable
      engine as Engine: Engine(0)
      modelName as String: String()

      Car()
        ->
          modelName as String
          engine as Engine
        this.modelName :=: modelName
        this.engine :=: engine

      override describe() as pure
        <- rtn as String: `${modelName} (${engine.horsePower()}hp)`

      default operator

  defines program

    CompositionDemo()
      stdout <- Stdout()

      engine <- Engine(250)
      car <- Car("Roadster", engine)
      stdout.println(car.describe())

EK9 encourages composition through closed types, traits, and delegation. Inheritance is available but not the default approach.

composition delegation design inheritance review trait

composition vs inheritance review
design assessment

      defines class
        Car extends Engine

Explanation: Car is not an Engine — it has an Engine. Use composition (field) not inheritance (extends). Also, Engine must be 'as open' to extend.


Question: Review this EK9 class for naming quality. Are the names descriptive enough?

Is this class well-named by EK9 standards?

What naming improvements would you suggest for this code?

Answer:
Review findings:

GOOD NAMING:
- Class name 'OrderProcessor' describes what it does
- Method 'calculateTotal' clearly states its purpose
- Field 'customerName' is descriptive

COULD BE IMPROVED:
- 'items' is acceptable but 'orderItems' would be more specific in this context
- 'process()' is vague — what does processing mean? Consider 'validateAndSubmit()' or 'fulfil()'
- Parameter 'disc' should be 'discountPercentage' — abbreviations lose meaning over time

EK9 NAMING PRINCIPLES:
- Types use PascalCase, variables use camelCase
- Names should describe purpose, not type (orderCount not intCount)
- The compiler bans generic names like temp, data, flag, value
- Methods should describe the action: calculateTotal not doCalc

defines module qa.codequality.reviewnaming

  defines class

    OrderProcessor
      customerName as String: String()
      orderItems as List of Float: List() of Float

      OrderProcessor()
        ->
          customerName as String
          orderItems as List of Float
        this.customerName :=: customerName
        this.orderItems :=: orderItems

      calculateTotal() as pure
        <- rtn as Float: 0.0

        for orderItem in orderItems
          rtn := rtn + orderItem

      default operator

  defines program

    ReviewNamingDemo()
      stdout <- Stdout()

      orders <- [29.99, 15.50, 42.00]
      processor <- OrderProcessor("Alice", orders)
      stdout.println(`Total: ${processor.calculateTotal()}`)

EK9 enforces naming quality at compile time. Generic names cause compile errors, not just style warnings.

assess descriptive naming quality review

code review naming
naming quality assessment

      data <- [29.99, 15.50, 42.00]
      processor <- OrderProcessor("Alice", data)

Explanation: EK9 bans non-descriptive variable names like 'data' at compile time — use a name that describes what the value represents. See ek9 -h E11031 for details.


Question: How do I use the #? hashcode operator on a record?

How does #? work on records in EK9?
Show me hashcode with a record type
Can records have a hashcode operator?

Answer:
Records can use 'default operator' to auto-generate #? from all fields. The generated hashcode combines the hash values of every field.

  defines record
    Coordinate
      latitude as Float: 0.0
      longitude as Float: 0.0
      default operator

Usage:
  point <- Coordinate(51.5, -0.12)
  hash <- #? point

The default operator generates #? that combines #?latitude and #?longitude. You can also define it manually if you need custom logic:

  operator #? as pure
    <- rtn as Integer: #?latitude + #?longitude

The #? operator always returns Integer. It must be pure. Records with default operator also get ==, $, :=:, and other operators automatically.

See Q911 for what #? means. See Q1053 for manual #? on a class. See Q1055 for default vs manual operators. See Q116 for what default operator generates.

defines module qa.operators.hashcoderecord

  defines record

    Coordinate
      latitude as Float: 0.0
      longitude as Float: 0.0

      Coordinate()
        ->
          latitude as Float
          longitude as Float
        this.latitude :=: latitude
        this.longitude :=: longitude

      default operator

  defines program

    HashcodeRecordDemo()
      stdout <- Stdout()

      pointA <- Coordinate(51.5, -0.12)
      pointB <- Coordinate(51.5, -0.12)
      pointC <- Coordinate(48.8, 2.35)

      hashA <- #? pointA
      hashB <- #? pointB
      hashC <- #? pointC

      stdout.println(`A hash: ${hashA}`)
      stdout.println(`B hash: ${hashB}`)
      stdout.println(`C hash: ${hashC}`)
      stdout.println(`A == B: ${pointA == pointB}`)
      stdout.println(`A == C: ${pointA == pointC}`)

Java: records auto-generate hashCode(). Python: dataclasses can auto-generate __hash__(). EK9: 'default operator' on records generates #? from all fields.

default hashcode integer operator record


Question: How do I use the #^ promote operator on a class?

How does #^ type promotion work on a class?

Show me the promote operator on a custom class

How do I widen a class type to a simpler type?

Answer:
Define operator #^ on your class to convert it to a wider or simpler type. The return type MUST be different from the class.

  defines class
    Percentage
      amount as Float: 0.0

      operator #^ as pure
        <- rtn as Float: amount

Usage:
  discount <- Percentage(15.0)
  asFloat <- #^ discount

The compiler uses #^ automatically when a Percentage is used where a Float is expected. This is type-safe widening — the compiler knows exactly how to convert.

Common patterns:
- Percentage to Float (domain type to primitive)
- Measurement to Float (unit type to raw number)
- UserId to Integer (wrapper to underlying value)

The #^ operator must be pure, take no parameters, and return a different type. The compiler enforces this with E07420.

See Q912 for what #^ means. See Q1052 for #^ on a record. See Q839 for promote return type rules.

defines module qa.operators.promoteclass

  defines class

    Percentage
      amount as Float: 0.0

      Percentage() as pure
        -> amount as Float
        this.amount :=: amount

      operator #^ as pure
        <- rtn as Float: amount

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

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

  defines function

    applyDiscount() as pure
      ->
        price as Float
        rate as Float
      <- rtn as Float: price - (price * rate)

  defines program

    PromoteClassDemo()
      stdout <- Stdout()

      discount <- Percentage(15.0)
      stdout.println(`Discount: ${discount}`)

      //Promote to Float for arithmetic
      rawRate <- #^ discount
      price <- 100.0
      finalPrice <- applyDiscount(price, rawRate)
      stdout.println(`Final: ${finalPrice}`)

Java: implicit primitive widening (int to double). Rust: explicit 'as' casting. EK9: #^ operator provides explicit, type-safe promotion.

class conversion operator promote widening

      operator #^ as pure
        <- rtn as Percentage: Percentage(amount)

Explanation: The #^ promote operator must return a DIFFERENT type. Returning the same type defeats the purpose and triggers E07420.


Question: How do I implement comparison operators on a record for sorting?

Show me comparison operators on a custom record

Answer:
Implement the <=> (spaceship) operator on your record. Then derive <, >, <=, >= from it. All comparison operators must be pure.

  defines record
    Priority
      level as Integer: 0

      operator <=> as pure
        -> other as Priority
        <- rtn as Integer: level <=> other.level

      operator < as pure
        -> other as Priority
        <- rtn as Boolean: (this <=> other) < 0

      operator > as pure
        -> other as Priority
        <- rtn as Boolean: (this <=> other) > 0

      operator <= as pure
        -> other as Priority
        <- rtn as Boolean: (this <=> other) <= 0

      operator >= as pure
        -> other as Priority
        <- rtn as Boolean: (this <=> other) >= 0

Once these are defined, you can sort lists and use coalescing operators:
  smaller <- priorityA <? priorityB
  larger <- priorityA >? priorityB

The coalescing operators <? and >? return the lesser or greater value. They work because the comparison operators are defined.

See Q1050 for coalescing operators on a class. See Q239 for comparison ordering. See Q1051 for operators on traits.

defines module qa.operators.comparisonrecord

  defines record

    Priority
      level as Integer: 0
      label as String: String()

      Priority()
        ->
          level as Integer
          label as String
        this.level :=: level
        this.label :=: label

      operator <=> as pure
        -> other as Priority
        <- rtn as Integer: level <=> other.level

      operator < as pure
        -> other as Priority
        <- rtn as Boolean: (this <=> other) < 0

      operator > as pure
        -> other as Priority
        <- rtn as Boolean: (this <=> other) > 0

      operator <= as pure
        -> other as Priority
        <- rtn as Boolean: (this <=> other) <= 0

      operator >= as pure
        -> other as Priority
        <- rtn as Boolean: (this <=> other) >= 0

      default operator

  defines program

    ComparisonRecordDemo()
      stdout <- Stdout()

      high <- Priority(10, "Critical")
      medium <- Priority(5, "Normal")
      low <- Priority(1, "Low")

      stdout.println(`High > Low: ${high > low}`)
      stdout.println(`Medium <= High: ${medium <= high}`)

      //Coalescing: get the lesser/greater
      lesser <- high <? medium
      greater <- low >? medium
      stdout.println(`Lesser: ${lesser}`)
      stdout.println(`Greater: ${greater}`)

Java: implements Comparable. Python: __lt__, __gt__, etc. Rust: derive Ord. EK9: define <=> then derive <, >, <=, >= from it.

comparison operator record sort spaceship

comparison operators on records
sortable records

      operator < as pure
        -> other as Priority
        <- rtn as Integer: level - other.level

Explanation: The < operator must return Boolean, not Integer. Use <=> for Integer comparison results, < for Boolean.

Oracle can add comparison operators to a record for sorting support.


Question: How do the coalescing operators <?, >?, <=?, >=? work on a custom class?

What are the comparison coalescing operators in EK9?

Show me <? and >? on a class with examples

How do I find the minimum or maximum of two objects?

Answer:
The coalescing operators return one of the two operands based on comparison:

  <? returns the lesser value (minimum)
  >? returns the greater value (maximum)
  <=? returns the left if left <= right, else right
  >=? returns the left if left >= right, else right

To use these on a custom class, the class must implement the corresponding comparison operators (<, >, <=, >=).

  score1 <- Score(85)
  score2 <- Score(92)
  minimum <- score1 <? score2     returns Score(85)
  maximum <- score1 >? score2     returns Score(92)

They also handle unset values safely — if either operand is unset, the result reflects that.

These replace patterns like:
  if a < b then result := a else result := b
With just:
  result <- a <? b

See Q963 for <? in detail. See Q964 for >? in detail. See Q1049 for comparison operators on records. See Q243 for coalescing with unset values.

defines module qa.operators.coalescingclass

  defines class

    Score
      points as Integer: 0

      Score() as pure
        -> points as Integer
        this.points :=: points

      operator <=> as pure
        -> other as Score
        <- rtn as Integer: points <=> other.points

      operator < as pure
        -> other as Score
        <- rtn as Boolean: (this <=> other) < 0

      operator > as pure
        -> other as Score
        <- rtn as Boolean: (this <=> other) > 0

      operator <= as pure
        -> other as Score
        <- rtn as Boolean: (this <=> other) <= 0

      operator >= as pure
        -> other as Score
        <- rtn as Boolean: (this <=> other) >= 0

      operator == as pure
        -> other as Score
        <- rtn as Boolean: points == other.points

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

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

      operator $ as pure
        <- rtn as String: `Score(${points})`

  defines program

    CoalescingClassDemo()
      stdout <- Stdout()

      alice <- Score(85)
      bob <- Score(92)
      charlie <- Score(78)

      //Coalescing: find minimum and maximum
      minimum <- alice <? bob
      maximum <- alice >? bob
      stdout.println(`Min: ${minimum}`)
      stdout.println(`Max: ${maximum}`)

      //Chain coalescing for three values
      lowest <- alice <? bob <? charlie
      highest <- alice >? bob >? charlie
      stdout.println(`Lowest: ${lowest}`)
      stdout.println(`Highest: ${highest}`)

Java: Math.min(a,b) / Math.max(a,b). Python: min(a,b) / max(a,b). Rust: a.min(b) / a.max(b). EK9: a <? b / a >? b as operators.

class coalescing compare maximum minimum operator

Explanation: EK9 has no Math class; use the '<?' coalescing operator to get the lesser of two values. See ek9 -h E50001 for details.


Question: Can a trait define comparison and hashcode operators in EK9?

Show me operator definitions inside a trait

Do traits support #? and comparison operators?

Define a sortable contract using a trait with operators

Answer:
Yes, traits can define operator signatures that implementing classes must provide. A trait can declare abstract operators or provide default implementations.

Abstract operator in a trait (no body):
  defines trait
    Rankable
      operator <=> as pure
        -> other as Rankable
        <- rtn as Integer?

Default operator in a trait (with body):
  defines trait
    Identifiable
      operator #? as pure
        <- rtn as Integer: 0

Any class 'with trait of Rankable' must implement <=>. The trait contract guarantees that all Rankable objects can be compared.

This pattern is useful for:
- Defining sortable contracts
- Ensuring all types in a collection support comparison
- Building generic algorithms that depend on ordering

Note: override is required when implementing trait operators in a class.

See Q1049 for comparison operators on records. See Q1050 for coalescing on classes. See Q210 for trait delegation.

defines module qa.operators.operatorsontrait

  defines trait

    Rankable
      rank() as pure
        <- rtn as Integer?

  defines class

    Task with trait of Rankable
      priority as Integer: 0
      taskName as String: String()

      Task()
        ->
          taskName as String
          priority as Integer
        this.taskName :=: taskName
        this.priority :=: priority

      override rank() as pure
        <- rtn as Integer: priority

      operator <=> as pure
        -> other as Task
        <- rtn as Integer: priority <=> other.priority

      operator < as pure
        -> other as Task
        <- rtn as Boolean: (this <=> other) < 0

      operator > as pure
        -> other as Task
        <- rtn as Boolean: (this <=> other) > 0

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

      override operator ? as pure
        <- rtn as Boolean: taskName? and priority?

      operator $ as pure
        <- rtn as String: `${taskName}(${priority})`

  defines program

    TraitOperatorsDemo()
      stdout <- Stdout()

      taskA <- Task("Fix bug", 3)
      taskB <- Task("Deploy", 8)

      stdout.println(`A < B: ${taskA < taskB}`)
      stdout.println(`A > B: ${taskA > taskB}`)
      stdout.println(`A hash: ${#? taskA}`)

      //Coalescing: get higher priority
      urgent <- taskA >? taskB
      stdout.println(`More urgent: ${urgent}`)

Java: Comparable interface. Rust: Ord trait. Python: __lt__ protocol. EK9: trait with operator signatures.

abstract comparison contract hashcode operator trait

Explanation: Overriding an inherited trait member (here rank() from trait Rankable) requires the 'override' keyword, otherwise the compiler reports E05120. See ek9 -h E05120 for details.


Question: Define #^ on a record to promote it to String for display purposes.

How do I make a record automatically convert to String with #^?

Show a record with a promote operator that returns String

Use #^ to widen a record to its string representation

Answer:
Define operator #^ on your record to return String. The compiler will use this automatically when a String is needed.

  defines record
    Colour
      red as Integer: 0
      green as Integer: 0
      blue as Integer: 0

      operator #^ as pure
        <- rtn as String: `rgb(${red}, ${green}, ${blue})`

Usage:
  crimson <- Colour(220, 20, 60)
  asString <- #^ crimson

Note: #^ and $ serve different purposes.
- $ is the string representation operator (called with $variable)
- #^ is the promotion operator (automatic type widening)

When a function expects a String parameter and you pass a Colour, the compiler calls #^ automatically. With $, you must explicitly write $colour.

Both are pure and take no parameters. The difference is intent: $ is for display, #^ is for type conversion.

See Q912 for what #^ means. See Q1048 for #^ on a class. See Q1055 for default vs manual operators.

defines module qa.operators.promoterecordstring

  defines record

    Colour
      red as Integer: 0
      green as Integer: 0
      blue as Integer: 0

      Colour()
        ->
          red as Integer
          green as Integer
          blue as Integer
        this.red :=: red
        this.green :=: green
        this.blue :=: blue

      operator #^ as pure
        <- rtn as String: `rgb(${red}, ${green}, ${blue})`

      default operator

  defines program

    PromoteRecordDemo()
      stdout <- Stdout()

      crimson <- Colour(220, 20, 60)
      skyBlue <- Colour(135, 206, 235)

      //Explicit promote
      asString <- #^ crimson
      stdout.println(asString)

      //$ for display
      stdout.println(`Colour: ${skyBlue}`)

Java: toString() for display, casting for conversion. EK9 separates these: $ for display, #^ for type promotion.

convert display operator promote record string

#^ on records
record to String promotion

      operator #^ as pure
        <- rtn as Colour: Colour(red, green, blue)

Explanation: The #^ operator must return a different type. Returning the same record type triggers E07420.


Question: Implement #? manually on a class that combines multiple fields.

Write a custom hashcode operator for a class with several fields

How do I build a hashcode from multiple fields in EK9?

Show me a multi-field #? implementation on a class

Answer:
Combine the hash values of each field using arithmetic. The #? prefix operator gets the hash of individual fields.

  operator #? as pure
    <- rtn as Integer: #?firstName + #?lastName + #?age

Each #?field calls _hashcode() on that field. Adding them produces a combined hash. For better distribution, multiply by a prime:

  operator #? as pure
    <- rtn as Integer: (#?firstName * 31) + (#?lastName * 17) + #?age

Alternatively, use 'default operator' to auto-generate #? from all fields. Manual implementation is only needed when you want a custom hash strategy (for example, hashing on a subset of fields).

The #? operator must be pure, take no parameters, and return Integer.

See Q911 for what #? means. See Q1047 for #? on records with default operator. See Q1055 for default vs manual operators.

defines module qa.operators.hashcodeclassmulti

  defines class

    Employee
      firstName as String: String()
      lastName as String: String()
      employeeId as Integer: 0

      default private Employee()

      Employee()
        ->
          firstName as String
          lastName as String
          employeeId as Integer
        this.firstName :=: firstName
        this.lastName :=: lastName
        this.employeeId :=: employeeId

      //Custom #? using subset of fields
      operator #? as pure
        <- rtn as Integer: (#?firstName * 31) + (#?lastName * 17) + #?employeeId

      operator == as pure
        -> other as Employee
        <- rtn as Boolean: employeeId == other.employeeId

      override operator ? as pure
        <- rtn as Boolean: firstName? and lastName? and employeeId?

      operator $ as pure
        <- rtn as String: `${firstName} ${lastName} (${employeeId})`

  defines program

    HashcodeClassDemo()
      stdout <- Stdout()

      emp1 <- Employee("Alice", "Smith", 1001)
      emp2 <- Employee("Alice", "Smith", 1001)
      emp3 <- Employee("Bob", "Jones", 1002)

      stdout.println(`emp1 hash: ${#? emp1}`)
      stdout.println(`emp2 hash: ${#? emp2}`)
      stdout.println(`emp3 hash: ${#? emp3}`)
      stdout.println(`emp1 == emp2: ${emp1 == emp2}`)

Java: Objects.hash(field1, field2). Python: hash((f1, f2)). EK9: #?field1 + #?field2 or 'default operator'.

class custom hashcode manual multi-field operator

custom #? implementation
multi-field hashcode

      operator #? as pure
        <- rtn as Float: #?firstName + #?lastName

Explanation: The #? operator must return Integer. Using Float or any other type triggers E07550.


Question: My code has three errors. Fix them all with minimal changes.

Fix these multiple errors in my EK9 code

Quick fix: several mistakes in one function

Correct all the problems in this code at once

Answer:
Here are the three fixes:

1. Change := to <- on line where greeting is first created.
   greeting := "Hello"  becomes  greeting <- "Hello"
   Reason: := updates existing variables. <- creates new ones.

2. Add 'as pure' to the function that has no side effects.
   formatName()  becomes  formatName() as pure
   Reason: functions that only compute from inputs should be pure.

3. Change the return variable type initialisation.
   <- rtn as String?  becomes  <- rtn as String: String()
   Reason: return values must always be initialised. String() creates an unset String.

Each fix is one small change. Do not restructure the code or add features — just fix the errors.

defines module qa.gettingstarted.multistepfix

  defines function

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

      if name?
        rtn := "Dear " + name

  defines program

    MultiStepFixDemo()
      stdout <- Stdout()

      greeting <- "Hello"
      stdout.println(greeting)

      formatted <- formatName("Alice")
      stdout.println(formatted)

EK9 error fixing is incremental. Fix one error at a time. The compiler will guide you to the next one.

errors fix generalist minimal multiple quick

Explanation: Use <- to create a new variable. The := operator expects the variable to already exist.


Question: Should I write #? and $ manually or use default operator?

When should I use default operator instead of writing operators by hand?

Show the difference between default operator and manual #? and $ implementations

Can I mix default operator with custom operator overrides?

Answer:
Use 'default operator' when field-by-field behaviour is correct. Write manual operators only when you need custom logic.

DEFAULT OPERATOR (recommended for most types):
  defines record
    Address
      street as String: String()
      city as String: String()
      default operator

This generates #?, $, ==, <>, <=>, and ? automatically from all fields. The generated $ concatenates field strings. The generated #? combines field hashes.

MANUAL OPERATORS (when you need custom behaviour):
  defines class
    Account
      accountId as Integer: 0
      balance as Float: 0.0

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

      operator $ as pure
        <- rtn as String: `Account-${accountId}`

Here #? hashes only the accountId (not balance), and $ shows a formatted string. Default operator would hash both fields and show both in the string.

YOU CAN MIX: use 'default operator' then override specific ones:
  default operator
  override operator $ as pure
    <- rtn as String: `Custom: ${name}`

The default generates all operators, then your override replaces $.

See Q116 for what default operator generates. See Q1047 for #? on records. See Q1053 for manual #? on classes. See Q949 for default operator rules.

defines module qa.operators.defaultvsmanual

  defines record

    //DEFAULT: all operators generated from fields
    Address
      street as String: String()
      city as String: String()

      Address()
        ->
          street as String
          city as String
        this.street :=: street
        this.city :=: city

      default operator

  defines class

    //MANUAL: custom hashcode and string
    Account
      accountId as Integer: 0
      balance as Float: 0.0

      default private Account()

      Account()
        ->
          accountId as Integer
          balance as Float
        this.accountId :=: accountId
        this.balance :=: balance

      //Hash only by accountId, not balance
      operator #? as pure
        <- rtn as Integer: #? accountId

      //Custom display format
      operator $ as pure
        <- rtn as String: `Account-${accountId}: ${balance}`

      override operator ? as pure
        <- rtn as Boolean: accountId? and balance?

  defines program

    DefaultVsManualDemo()
      stdout <- Stdout()

      //Record with default operator
      addr <- Address("123 Main St", "London")
      stdout.println(`Address: ${addr}`)
      stdout.println(`Hash: ${#? addr}`)

      //Class with manual operators
      acct <- Account(1001, 5250.75)
      stdout.println(`${acct}`)
      stdout.println(`Hash: ${#? acct}`)

Java: records auto-generate, classes need manual. Python: @dataclass auto-generates. EK9: 'default operator' works on both records and classes.

default generate hashcode manual operator override string

default vs manual operators
when to override

      operator == as pure
        ...
      operator $ as pure
        ...
      operator #? as pure
        ...

Explanation: For simple value types, 'default operator' generates all standard operators in one line. Manual implementation is only needed for custom behaviour.


Question: Is the $ operator the same as string interpolation in EK9?

What is the difference between $variable and ${variable} in EK9?

How do I convert a value to String in EK9?

Answer:
No. The $ operator and string interpolation are completely separate things in EK9.

$ OPERATOR — converts any value to String:
  age <- 25
  ageAsString <- $age
This calls the _string() method on the value and returns a String. It works outside backtick strings as a standalone operator.

STRING INTERPOLATION — embeds values inside backtick strings:
  message <- `Age is ${age}`
This is backtick string syntax. The ${...} is interpolation that happens inside backtick strings only.

The confusion:
  $age outside backticks = calls _string() operator, returns String
  ${age} inside backticks = interpolation, embeds value in string

You can combine them:
  ${$myObject} inside backticks = explicitly calls _string() then interpolates
But usually ${myObject} is enough because interpolation calls _string() automatically.

Defining $ on a custom type:
  operator $ as pure
    <- rtn as String: `value: ${amount}`

The $ operator must be pure, take no parameters, and return String.

See Q908 for $ operator details. See Q43 for backtick string escaping. See Q1055 for default vs manual operators.

defines module qa.operators.dollarnotinterpolation

  defines class

    Temperature
      celsius as Float: 0.0

      Temperature() as pure
        -> celsius as Float
        this.celsius :=: celsius

      //$ operator — converts to String
      operator $ as pure
        <- rtn as String: `${celsius}C`

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

  defines program

    DollarDemo()
      stdout <- Stdout()

      reading <- Temperature(22.5)

      //$ operator: converts to String
      readingString <- $reading
      stdout.println(readingString)

      //Interpolation: embeds in backtick string
      stdout.println(`The temperature is ${reading}`)

      //Both together: explicit $ inside interpolation
      stdout.println(`Explicit: ${$reading}`)

Java: toString(). Python: str() / __str__. JavaScript: template literals use ${} for interpolation. EK9 separates these: $ is the conversion operator, ${} is backtick interpolation.

backtick conversion dollar interpolation operator string

$ vs interpolation
string conversion operator

Explanation: EK9 has no .toString() method; `reading.toString()` triggers E50060 (method not resolved) - use the $ prefix operator: $reading calls _string(). See ek9 -h E50060 for details.


Question: Does ++ return a value in EK9? Can I write x <- y++?

Is ++ a mutator or does it return a new value?

Answer:
In EK9, ++ and += are mutation operators. They modify the variable in place and return NOTHING. You cannot use them in expressions.

++ INCREMENTS IN PLACE:
  count <- 0
  count++          modifies count to 1, returns nothing

+= ADDS IN PLACE:
  score <- 10
  score += 5       modifies score to 15, returns nothing

WHAT YOU CANNOT DO:
  x <- count++     WRONG — ++ returns nothing, cannot assign
  total <- a += b  WRONG — += returns nothing, cannot assign
  if count++ > 5   WRONG — ++ returns nothing, cannot compare

WHAT TO DO INSTEAD:
  count++
  x <- count       increment then read separately

All mutation operators return void: ++, --, +=, -=, *=, /=, :=:, :^:, :~:

Mutation operators CANNOT be marked 'as pure' because they change state. A pure function must not mutate.

See Q241 for all mutation operators. See Q908 for $ operator. See Q1055 for default vs manual operators.

defines module qa.operators.incrementmutator

  defines program

    IncrementDemo()
      stdout <- Stdout()

      count <- 0
      stdout.println(`Before: ${count}`)

      //++ mutates in place, returns nothing
      count++
      stdout.println(`After ++: ${count}`)

      //+= mutates in place, returns nothing
      count += 10
      stdout.println(`After += 10: ${count}`)

      //-= also mutates in place
      count -= 3
      stdout.println(`After -= 3: ${count}`)

Java/C/Go: ++ returns a value (pre/post increment). Python: no ++ operator. EK9: ++ mutates in place, returns nothing — cannot be used in expressions.

expression increment mutator operator plus void

Explanation: ++ mutates in place and returns nothing, so it cannot appear inside an expression such as string interpolation; increment first, then read the variable. See ek9 -h E07950 for details.


Question: What is the difference between :=: :^: and :~: in EK9?

How do copy, replace, and merge operators work?

Explain the three content transfer operators

Answer:
EK9 has three operators for transferring content between objects:

:=: COPY — copies all fields from source to target:
  target :=: source
Every field in source is copied to target, overwriting all existing values. The default implementation does a shallow copy (field references are shared).

:~: MERGE — copies only SET fields from source to target:
  target :~: source
Only fields that are SET in source are copied. Unset fields in source leave the target fields unchanged. Use this for partial updates.

:^: REPLACE — replaces the entire content:
  target :^: source
Fully replaces the target content with the source. Similar to :=: but semantically means 'this object is now that object'.

All three are mutation operators — they modify the target in place and return nothing. They cannot be marked 'as pure'.

Custom :~: example (merge only SET fields):
  operator :~:
    -> from as Config
    if from.host?
      host :=: from.host
    if from.port?
      port :=: from.port

See Q98 for record operators. See Q241 for all mutation operators. See Q116 for default operator generation.

defines module qa.operators.copyreplacemerge

  defines record

    Config
      host as String: String()
      port as Integer: 0

      Config()
        ->
          host as String
          port as Integer
        this.host :=: host
        this.port :=: port

      //:~: MERGE — only copies SET fields
      operator :~:
        -> from as Config
        if from.host?
          host :=: from.host
        if from.port?
          port :=: from.port

      default operator

  defines program

    CopyReplaceMergeDemo()
      stdout <- Stdout()

      original <- Config("localhost", 8080)
      stdout.println(`Original: ${original}`)

      // :=: COPY — all fields copied
      backup <- Config("", 0)
      backup :=: original
      stdout.println(`Copy: ${backup}`)

      // :~: MERGE — only set fields from partial are copied
      partial <- Config("remote.host", 9090)
      original :~: partial
      stdout.println(`After merge: ${original}`)

Java: clone() or copy constructor. Python: copy.copy() / copy.deepcopy(). Rust: Clone trait. EK9 has three distinct operators: :=: (copy all), :~: (merge set only), :^: (replace).

copy merge mutation operator replace transfer

:=: :^: :~: operators
copy vs merge vs replace

Explanation: EK9 has no '.clone()' method; use ':=:' to copy all fields (':~:' merge, ':^:' replace). See ek9 -h E50060 for details.


Question: Do the ?? and ?: operators check for null in EK9?

Is ?? a null coalescing operator in EK9?

How do ?? and ?: work — are they null checks?

What do the coalescing operators actually check in EK9?

Answer:
No. EK9 has no null. The ?? and ?: operators check whether a value is SET, not whether it is null.

?? VALUE COALESCING:
  result <- name ?? "default"
Returns name if name is SET. Returns "default" if name is UNSET.

?: ELVIS COALESCING:
  result <- name ?: "default"
Same behaviour as ?? — returns left if SET, otherwise right.

The distinction from other languages:
  Java: name != null ? name : "default"     checks null
  Kotlin: name ?: "default"                  checks null
  EK9: name ?? "default"                     checks isSet

In EK9, a variable can exist but be UNSET. This is not the same as null. An unset String is a String object that has no meaningful value yet. It still exists — you can call ? on it to check.

  name <- String()     name exists, but is UNSET
  name?                returns false (unset)
  name := "Alice"     now SET
  name?                returns true

If BOTH sides of ?? are unset, the result is unset.

See Q899 for all coalescing operators. See Q898 for ? is not ternary. See Q883 for tri-state semantics.

defines module qa.operators.coalescingisset

  defines program

    CoalescingDemo()
      stdout <- Stdout()

      //Unset string
      name <- String()
      stdout.println(`name is set: ${name?}`)

      //?? returns right when left is unset
      greeting <- name ?? "stranger"
      stdout.println(`Hello ${greeting}`)

      //Set the name
      name := "Alice"
      stdout.println(`name is set: ${name?}`)

      //?? now returns left (it is set)
      greeting := name ?? "stranger"
      stdout.println(`Hello ${greeting}`)

      //?: works the same way
      title <- String()
      displayTitle <- title ?: "Untitled"
      stdout.println(`Title: ${displayTitle}`)

Java: ternary with null check. Kotlin: ?: (elvis) checks null. JavaScript: ?? (nullish coalescing). EK9: ?? and ?: check isSet, not null — EK9 has no null.

coalescing elvis isset null operator unset

?? checks isSet not null
?: elvis operator

Explanation: 'null' does not exist in EK9 — a variable is either set or unset (tri-state), so assign a real value rather than 'null'. See ek9 -h E01073 for details.


Question: Are record fields public or private in EK9?

Can I access record fields directly in EK9?

What is the difference between record and class field visibility?

Do records have getters and setters in EK9?

Answer:
Record fields are PUBLIC. Class fields are PRIVATE. This is a fundamental difference.

RECORD — fields are public, accessed directly:
  defines record
    Point
      xCoord as Float: 0.0
      yCoord as Float: 0.0

  point <- Point(3.0, 4.0)
  stdout.println($point.xCoord)    direct access — works

CLASS — fields are private, accessed via methods:
  defines class
    Circle
      radius as Float: 0.0

  circle <- Circle(5.0)
  circle.radius                    WRONG — private field

Why records are public:
- Records are data carriers (like DTOs)
- Direct field access is the intended usage
- Functions operate on record data externally
- No encapsulation needed for pure data

Why classes are private:
- Classes encapsulate behaviour with data
- Access is through methods and operators
- Internal state is hidden

Records do NOT need getters or setters. Access fields directly. Use functions to operate on record data.

See Q97 for record basics. See Q98 for record operators. See Q93 for class basics.

defines module qa.classesandoop.recordfieldspublic

  defines record

    Point
      xCoord as Float: 0.0
      yCoord as Float: 0.0

      Point()
        ->
          xCoord as Float
          yCoord as Float
        this.xCoord :=: xCoord
        this.yCoord :=: yCoord

      default operator

  defines function

    distanceFromOrigin() as pure
      -> point as Point
      <- rtn as Float: point.xCoord + point.yCoord

  defines program

    RecordFieldsDemo()
      stdout <- Stdout()

      point <- Point(3.0, 4.0)

      //Record fields are PUBLIC — direct access
      stdout.println(`X: ${point.xCoord}`)
      stdout.println(`Y: ${point.yCoord}`)

      //Functions operate on public record data
      dist <- distanceFromOrigin(point)
      stdout.println(`Distance: ${dist}`)

Java: record fields are effectively public (via accessors). Python: dataclass fields are public. Rust: struct fields configurable. EK9: record=public, class=private — always.

access class field private public record visibility

record fields are public
class fields are private

Explanation: Records expose public fields accessed directly (point.xCoord); there are no getter methods, so point.getX() is an unresolved method. See ek9 -h E50060 for details.


Question: Write a reusable function that filters items above a threshold so I can call it from anywhere.

Write a pure filter function I can reuse across my application

How do I write a function for use in a stream pipeline?

Create a named function I can pass to filter by

Answer:
Define a named pure function with one parameter that returns Boolean. Then use it in any stream pipeline with 'filter by'.

  defines function

    isAboveThreshold() as pure
      -> price as Float
      <- rtn as Boolean?

      minimumPrice <- 50.0
      rtn: price > minimumPrice

Usage in a pipeline:
  cat prices | filter by isAboveThreshold | collect as List of Float

The function must:
- Take exactly one parameter (the stream item type)
- Return Boolean (true = keep, false = discard)
- Be marked 'as pure' for use in pipelines

For a configurable threshold, extract the threshold into a record and use a method instead. See Q1064 for that pattern.

See Q1034 for stream map examples. See Q1035 for filter and collect.

defines module qa.functions.writereusablefilter

  defines function

    isAboveThreshold() as pure
      -> price as Float
      <- rtn as Boolean?

      minimumPrice <- 50.0
      rtn: price > minimumPrice

    formatPrice() as pure
      -> price as Float
      <- rtn as String: `Price: ${price}`

  defines program

    ReusableFilterDemo()
      stdout <- Stdout()

      prices <- [12.50, 75.00, 8.99, 120.00, 45.00, 89.99]

      //Use the reusable function in a pipeline
      expensive <- cat prices
        | filter by isAboveThreshold
        | collect as List of Float
      stdout.println(`Expensive: ${expensive}`)

      //Reuse the same function with different data
      otherPrices <- [200.0, 30.0, 55.0]
      otherExpensive <- cat otherPrices
        | filter by isAboveThreshold
        | collect as List of Float
      stdout.println(`Other expensive: ${otherExpensive}`)

      //Combine with map
      cat prices
        | filter by isAboveThreshold
        | map with formatPrice
        > stdout

Java: Predicate<T> lambda. Python: lambda or def. EK9: named pure function with Boolean return.

coder filter function pipeline pure reusable stream

write a filter function
reusable pipeline function

isAboveThreshold = lambda price: price > 50

Explanation: EK9 has no lambda syntax. Define a named pure function that returns Boolean for use with 'filter by'.


Question: I need a simple one-off function to transform items in a stream pipeline.

Write a small function for use with map in a pipeline

How do I write a transform function for stream processing?

Create a function that converts each item in a stream

Answer:
Define a pure function that takes one parameter and returns the transformed value. Use it with 'map with' in a pipeline.

  defines function

    toUpperCase() as pure
      -> name as String
      <- rtn as String: name.upperCase()

Usage:
  cat names | map with toUpperCase > stdout

For a simple one-line transform, the function body can be on the return line:

    addPrefix() as pure
      -> name as String
      <- rtn as String: "Hello, " + name

The function must:
- Take exactly one parameter (the stream item type)
- Return the transformed type (can be different from input)
- Be marked 'as pure'

See Q1061 for filter functions. See Q1034 for chained transforms.

defines module qa.functions.writepipelinefunction

  defines function

    toUpperCase() as pure
      -> name as String
      <- rtn as String: name.upperCase()

    addPrefix() as pure
      -> name as String
      <- rtn as String: "Hello, " + name

    extractLength() as pure
      -> name as String
      <- rtn as Integer: name.length()

  defines program

    PipelineFunctionDemo()
      stdout <- Stdout()

      names <- ["alice", "bob", "charlie"]

      //Simple transform
      cat names | map with toUpperCase > stdout

      //Chain two transforms
      cat names
        | map with toUpperCase
        | map with addPrefix
        > stdout

      //Transform to different type (String -> Integer)
      lengths <- cat names
        | map with extractLength
        | collect as List of Integer
      stdout.println(`Lengths: ${lengths}`)

Java: Function<T,R> lambda. Python: lambda or def. EK9: named pure function with return type.

coder function map pipeline simple stream transform

write a map function
stream transform function

cat names | map with (n -> n.upperCase())

Explanation: EK9 has no inline lambdas. Define a named pure function and pass it to 'map with' by name.


Question: Write a complete EK9 program that reads a list of scores, filters passing ones, and prints the results.

Show me a complete working program with functions and a pipeline

Code a program that processes a list using filter and output

Answer:
Here is a complete, compilable EK9 program:

defines module scores.processor

  defines function

    isPassing() as pure
      -> score as Integer
      <- rtn as Boolean?
      passingMark <- 60
      rtn: score >= passingMark

    formatScore() as pure
      -> score as Integer
      <- rtn as String: `Score: ${score} - PASS`

  defines program

    ScoreProcessor()
      stdout <- Stdout()
      scores <- [85, 42, 91, 67, 73, 55, 88, 96, 61, 79]

      cat scores
        | filter by isPassing
        | map with formatScore
        > stdout

      passing <- cat scores
        | filter by isPassing
        | collect as List of Integer
      stdout.println(`Passing count: ${passing.length()}`)

Key elements:
- Module declaration at the top
- Functions in a 'defines function' section
- Program in a 'defines program' section
- Pure functions for pipeline use
- Stream pipeline for processing
- collect as List for gathering results

See Q1061 for reusable filter functions. See Q1062 for transform functions.

defines module scores.processor

  defines function

    isPassing() as pure
      -> score as Integer
      <- rtn as Boolean?

      passingMark <- 60
      rtn: score >= passingMark

    formatScore() as pure
      -> score as Integer
      <- rtn as String: `Score: ${score} - PASS`

  defines program

    ScoreProcessor()
      stdout <- Stdout()

      scores <- [85, 42, 91, 67, 73, 55, 88, 96, 61, 79]

      //Pipeline: filter passing, format, print
      cat scores
        | filter by isPassing
        | map with formatScore
        > stdout

      //Collect passing scores
      passing <- cat scores
        | filter by isPassing
        | collect as List of Integer
      stdout.println(`Passing count: ${passing.length()}`)

Java: class with main method, imports, System.out. Python: def + if __name__. EK9: module + defines function + defines program.

code coder complete filter pipeline program write

  def isPassing(score):
    return score >= 60

Explanation: EK9 requires 'defines function' section header, type annotations on parameters, and <- for return declaration. No 'def' or 'return'.