Question: What happens when an EK9 enum is unset?

What is the tri-state behaviour of EK9 enumerations?

How does unset propagation work with EK9 enums?

Answer:
EK9 enumerations follow tri-state semantics: a variable can be set to a specific member, or it can be unset. There is no null, no exception, and no undefined behaviour.

THREE WAYS TO GET UNSET
Priority() creates an unset enum. Priority("Invalid") returns unset when the string does not match any member. Incrementing past the last member or decrementing before the first member also produces unset.

UNSET PROPAGATION
Comparison with an unset enum returns an unset Boolean. String conversion of an unset enum returns an unset String. JSON conversion of an unset enum returns an unset JSON. The unset state flows through operations naturally.

FIRST AND LAST ON UNSET
The #< (first) and #> (last) operators are type-level operations. They always return the first or last declared member, even when applied to an unset variable. This makes them safe anchors for restarting navigation.

CHECKING WITH ?
Use the ? operator to check if an enum is set. Guard expressions work naturally: 'if checked <- getStatus()' only enters the block when the result is set.

See Q29 for unset variables. See Q219 for all auto-generated operators. See Q220 for navigation. See Q221 for string comparison.

defines module qa.enums.unset

  defines type

    Priority
      Low
      Medium
      High
      Critical

  defines program

    EnumUnsetDemo()
      stdout <- Stdout()

      // === THREE WAYS TO GET UNSET ===

      fromDefault <- Priority()
      stdout.println(`Default constructor isSet: ${fromDefault?}`)

      fromBadString <- Priority("Urgent")
      stdout.println(`Bad string isSet: ${fromBadString?}`)

      pastLast <- Priority.Critical
      pastLast++
      stdout.println(`Past last isSet: ${pastLast?}`)

      // === UNSET PROPAGATION ===

      unsetPriority <- Priority()
      setPriority <- Priority.High

      eqResult <- unsetPriority == setPriority
      stdout.println(`Unset == Set isSet: ${eqResult?}`)

      cmpResult <- unsetPriority <=> setPriority
      stdout.println(`Unset <=> Set isSet: ${cmpResult?}`)

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

      unsetHash <- #?unsetPriority
      stdout.println(`Unset hashcode isSet: ${unsetHash?}`)

      // === FIRST AND LAST STILL WORK ON UNSET ===

      firstFromUnset <- #< unsetPriority
      stdout.println(`First from unset: ${firstFromUnset}`)

      lastFromUnset <- #> unsetPriority
      stdout.println(`Last from unset: ${lastFromUnset}`)

      // === CHECKING WITH ? ===

      if setPriority?
        stdout.println(`Set priority: ${setPriority}`)

      if unsetPriority?
        stdout.println("This will not print")

Java: throws NullPointerException on null enum reference, IllegalArgumentException on bad valueOf(). Python: raises ValueError on invalid Enum construction, None requires explicit null checks. Rust: Option<EnumType> requires explicit unwrapping with match or if-let, no propagation. Go: zero value is integer 0 which may silently represent a valid enum constant. C#: nullable enum exists but no propagation semantics, requires explicit null checks. Kotlin: nullable enum with ?. operator but no systematic unset propagation. EK9: systematic tri-state with natural propagation through all operators, check with ? when ready.

absent check enum enumeration exception null propagation safe set tri-state unset

Explanation: EK9 enumerations do not have an 'equals()' method. Use the == operator for equality comparison. The == operator handles unset values safely by producing an unset Boolean result. See ek9 -h E50060 for details.


Question: How do I create a subset of enum values in EK9?

What are constrained enumerations in EK9?

How do I restrict an enum to a subset of values?

Can I create a type-safe enum subset in EK9?

Answer:
EK9 supports constrained enumerations that create a new type limited to a subset of values from a base enum. This is a compile-time type-safe mechanism, not a runtime collection.

CONSTRAINED SYNTAX
Declare a constrained enum with 'as BaseEnum constrain as' followed by quoted values joined with 'or':
  RedSuit as CardSuit constrain as
    "Hearts" or "Diamonds"
This creates a NEW type RedSuit that can only hold Hearts or Diamonds.

NOT A SUBTYPE
Constrained enums are separate types, not subtypes of the base enum. You cannot assign a RedSuit to a CardSuit variable or vice versa. This is intentional: the type system guarantees that each variable holds exactly the values its type allows.

CONSTRUCTION
A bare constructor ASSERTS validity: RedSuit(CardSuit.Hearts) yields a set value, but a set value outside the subset is invalid - it PANICS at runtime, and a violating literal constant (e.g. RedSuit("Clubs")) is the compile error E08260. To VALIDATE an untrusted base value without panicking, use the fallible factory .of(baseValue), which returns a set value when valid and an UNSET value when it violates the constraint: RedSuit().of(CardSuit.Hearts) is set, RedSuit().of(CardSuit.Clubs) is unset. Because .of(...) takes a base-enum value, parse a String into the base enum first: RedSuit().of(CardSuit("Hearts")) is set, RedSuit().of(CardSuit("Clubs")) is unset. Combine with guard expressions for clean checking: 'if parsed <- RedSuit().of(CardSuit("Diamonds"))'.

ITERATION
Constrained enums support iteration and streaming just like base enums: 'for suit in RedSuit' iterates over the constrained subset.

OPERATORS
Constrained enums inherit all comparison, string conversion, and query operators from the base enum. You can compare constrained values, check with ?, convert to string, and use first/last.

See Q99 for basic enumerations. See Q219 for auto-generated operators. See Q195 for generic constraints.

defines module qa.enums.constrained

  defines type

    CardSuit
      Hearts
      Diamonds
      Clubs
      Spades

    RedSuit as CardSuit constrain as
      "Hearts" or "Diamonds"

    BlackSuit as CardSuit constrain as
      "Clubs" or "Spades"

  defines program

    EnumConstrainedDemo()
      stdout <- Stdout()

      // === CONSTRUCTION FROM BASE ENUM ===
      // Use the fallible factory .of(baseValue) for validation: it returns
      // a SET value when the base enum value satisfies the constraint and an
      // UNSET value when it violates it - never panics. This is the pattern
      // for validating untrusted/boundary values.

      redCard <- RedSuit().of(CardSuit.Hearts)
      stdout.println(`Red suit: ${redCard}, isSet: ${redCard?}`)

      blackCard <- BlackSuit().of(CardSuit.Spades)
      stdout.println(`Black suit: ${blackCard}, isSet: ${blackCard?}`)

      // === CONSTRAINT ENFORCEMENT ===
      // Clubs is not in RedSuit, Hearts is not in BlackSuit: .of(...) returns
      // an UNSET value rather than panicking.

      invalidRed <- RedSuit().of(CardSuit.Clubs)
      stdout.println(`Clubs in RedSuit isSet: ${invalidRed?}`)

      invalidBlack <- BlackSuit().of(CardSuit.Hearts)
      stdout.println(`Hearts in BlackSuit isSet: ${invalidBlack?}`)

      // === OPERATORS WORK ON CONSTRAINED ENUMS ===

      red1 <- RedSuit(CardSuit.Hearts)
      red2 <- RedSuit(CardSuit.Diamonds)
      stdout.println(`Hearts < Diamonds: ${red1 < red2}`)
      sameAsRed1 <- RedSuit(CardSuit.Hearts)
      stdout.println(`Hearts == Hearts: ${red1 == sameAsRed1}`)
      stdout.println(`String: ${red1}`)
      stdout.println(`Hashcode: ${#?red1}`)

      // === STRING VALIDATION VIA THE BASE ENUM AND .of(...) ===
      // The fallible factory .of(...) takes a base-enum value, so parse the
      // (untrusted) String into a CardSuit first, then validate against the
      // constrained subset. A value outside the subset yields an UNSET result.

      fromString <- RedSuit().of(CardSuit("Hearts"))
      stdout.println(`From string: ${fromString}, isSet: ${fromString?}`)

      invalidString <- RedSuit().of(CardSuit("Clubs"))
      stdout.println(`Invalid string isSet: ${invalidString?}`)

      // === GUARD EXPRESSION WITH CONSTRAINED ENUM ===

      if parsed <- RedSuit().of(CardSuit("Diamonds"))
        stdout.println(`Parsed: ${parsed}`)

      // === UNSET CONSTRAINED ENUM ===

      unsetRed <- RedSuit()
      stdout.println(`Unset RedSuit isSet: ${unsetRed?}`)

      // === ITERATION OVER CONSTRAINED ENUM ===

      for suit in RedSuit
        stdout.println(`Red: ${suit}`)

      // === FULL BASE ENUM STILL WORKS ===

      allSuits <- cat CardSuit | collect as List of CardSuit
      stdout.println(`All suits: ${length allSuits}`)

Java: EnumSet.of(Hearts, Diamonds) is runtime only, no type safety, any CardSuit can be passed where EnumSet expected. Python: no equivalent, must use runtime validation. Rust: no equivalent, would need separate enum plus TryFrom conversion. Go: no equivalent, no enum type at all. Kotlin: no equivalent, sealed interface can approximate but verbose. C#: no equivalent, FlagsAttribute is bitwise not subset. TypeScript: union types are structural not nominal. EK9: compile-time type-safe enum subsets, constrained type is separate from base type.

card compile constrain define enum enumeration restricted safe separate subset suit type

stdout.display(`Red suit: ${redCard}, isSet: ${redCard?}`)

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


Question: How do I use enums with stream pipelines in EK9?

How do I iterate and transform enum values with pipelines?

How does cat work with EK9 enumerations?

Answer:
EK9 enumerations are first-class stream sources. Use 'cat EnumType' to feed all declared values into a pipeline. No .values() conversion, no array wrapping.

BASIC STREAMING
Stream all enum values to output: 'cat Season > stdout'. Or collect into a list: 'cat Season | collect as List of Season'.

FILTER AND TRANSFORM
Use filter and map in pipelines:
  cat Season | filter by isWarm | collect as List of Season
Where isWarm is a function taking Season and returning Boolean.

FOR-IN ITERATION
'for season in Season' iterates all declared values in order. This is the simplest form when you need to process each value.

COLLECTION FROM ENUM
Collect enums into lists for further processing:
  allSeasons <- cat Season | collect as List of Season
  length allSeasons returns the count of declared values.

COMBINING WITH FUNCTIONS
Pipe enum values through functions:
  cat Season | map with describeFunc > stdout
This transforms each enum value through a function and outputs the results.

See Q59 for function pipelines. See Q99 for basic enumeration. See Q122 for collect as. See Q219 for auto-generated operators.

defines module qa.enums.streams

  defines type

    Rank
      Two
      Three
      Four
      Five
      Six
      Seven
      Eight
      Nine
      Ten
      Jack
      Queen
      King
      Ace

    Suit
      Hearts
      Diamonds
      Clubs
      Spades

  defines function

    isFaceCard() as pure
      -> r as Rank
      <- face <- Boolean()
      face: r > Rank.Ten

    rankLabel() as pure
      -> r as Rank
      <- label <- String()
      label: $r

  defines program

    EnumStreamsDemo()
      stdout <- Stdout()

      // === BASIC STREAMING ===

      stdout.println("All suits:")
      cat Suit > stdout

      // === COLLECT INTO LIST ===

      allRanks <- cat Rank | collect as List of Rank
      stdout.println(`Total ranks: ${length allRanks}`)

      // === FILTER WITH FUNCTION ===

      faceCards <- cat Rank | filter by isFaceCard | collect as List of Rank
      stdout.println(`Face cards: ${length faceCards}`)

      // === MAP WITH FUNCTION ===

      stdout.println("Rank labels:")
      cat Rank | map with rankLabel > stdout

      // === FOR-IN ITERATION ===

      for suit in Suit
        stdout.println(`Suit: ${suit}`)

      // === STREAM SUITS AND COUNT ===

      suitList <- cat Suit | collect as List of Suit
      stdout.println(`Total suits: ${length suitList}`)

Java: Arrays.stream(Season.values()) verbose, requires importing Arrays, values() returns mutable array copy. Python: list(Season) simpler but no pipeline, must chain with map/filter manually. Rust: needs strum::IntoEnumIterator external crate for iteration, .iter() not built-in. Go: must manually create slice of constants, no built-in iteration. Kotlin: entries.forEach or entries.filter, entries property since 1.9. C#: Enum.GetValues() returns Array, needs casting. EK9: 'cat Season' directly in pipeline, first-class stream source, zero conversion.

cat collect enum enumeration filter functional iterate list map pipeline stream

stdout.display(`Face cards: ${length faceCards}`)


Question: How do I associate data or behaviour with enum values in EK9?

How do I add behaviour to an EK9 enumeration?

What is the EK9 pattern for enum-associated data?

Answer:
EK9 enumerations are pure value types with no methods or fields. Associate data using Dict with enum keys. Associate behaviour using functions that take the enum as a parameter.

DICT FOR ASSOCIATED DATA
Use a Dict with enum keys to map values to data:
  labels <- {Severity.Low: "Low priority", Severity.High: "Handle now"}
This separates the enumeration from its associated data, keeping both independently testable.

FUNCTIONS FOR BEHAVIOUR
Write functions that take the enum as a parameter:
  severityLabel() as pure
    -> s as Severity
    <- label as String: switch s ...
Behaviour lives in functions, not on the enum. Each function is independently testable.

SWITCH EXPRESSIONS FOR MAPPING
Use switch as an expression to map enum values to results:
  label <- switch severity
    <- rtn as String: String()
    case Severity.Low
      rtn: "monitor"
    case Severity.High
      rtn: "escalate"
Exhaustive switch guarantees all values are handled.

WHY COMPOSITION
Alan Perlis: 100 functions on one data structure beat 10 functions on 10 structures. Keeping enums pure means they stay serialisable, comparable, iterable, and JSON-convertible. Behaviour in functions means testable, composable, and replaceable logic.

See Q100 for enum vs Java comparison. See Q109 for composition pattern. See Q212 for composition over inheritance. See Q219 for auto-generated operators. See Q746 for migrating Swift enum associated values to EK9 composition.

defines module qa.enums.composition

  defines type

    Severity
      Info
      Warning
      Error
      Critical

  defines function

    severityLabel() as pure
      -> s as Severity
      <- label as String: switch s
        <- rtn as String: String()
        case Severity.Info
          rtn: "Information - log and continue"
        case Severity.Warning
          rtn: "Warning - investigate soon"
        case Severity.Error
          rtn: "Error - fix required"
        case Severity.Critical
          rtn: "Critical - immediate action needed"
        default
          rtn: "No severity set"

    isActionRequired() as pure
      -> s as Severity
      <- required <- Boolean()
      required: s >= Severity.Error

  defines program

    EnumCompositionDemo()
      stdout <- Stdout()

      // === FUNCTIONS FOR BEHAVIOUR ===

      for s in Severity
        stdout.println(severityLabel(s))

      // === FUNCTIONS WITH COMPARISON ===

      for s in Severity
        stdout.println(`${s} requires action: ${isActionRequired(s)}`)

      // === DICT FOR ASSOCIATED DATA ===

      icons <- {Severity.Info: "i", Severity.Warning: "!", Severity.Error: "X", Severity.Critical: "!!!"}

      for s in Severity
        icon <- icons.getOrDefault(s, "?")
        stdout.println(`${s}: ${icon}`)

      // === SWITCH EXPRESSION FOR MAPPING ===

      testSeverity <- Severity.Warning
      category <- switch testSeverity
        <- rtn as String: String()
        case Severity.Info, Severity.Warning
          rtn: "monitor"
        case Severity.Error, Severity.Critical
          rtn: "escalate"
        default
          rtn: "unknown"
      stdout.println(`${testSeverity} category: ${category}`)

Java: puts methods, fields, and constructors ON the enum (Planet with mass/radius/surfaceGravity()), creates tight coupling, harder to serialise. Python: adds methods to Enum class, can override __str__, mixes concerns. Rust: associates data per variant with enum fields, pattern matching extracts data. Go: methods on integer type, but enum is just integers. Kotlin: enum class with properties and abstract method implementations per entry. Swift: enum cases carry associated values per variant (case circle(radius: Double)), extracted via pattern matching, powerful but couples identity to data. C#: extension methods on enum type, or attributes for metadata. EK9: enums are pure values, use Dict for data association and functions for behaviour, separation of concerns.

associate behaviour composition design dict enum enumeration function immutable migrate pattern pure separate side-effect swift

Explanation: EK9 Dict does not have a 'get()' method. Use 'getOrDefault(key, default)' to retrieve values from a Dict, or use 'contains' to check for key existence first. See ek9 -h E50060 for details.


Question: How does EK9 prevent common enum bugs?

What enum bugs does EK9 eliminate at compile time?

Why are EK9 enums safer than in other languages?

How does EK9 protect against enum-related errors?

Answer:
EK9 prevents entire categories of enum bugs that plague other languages. These protections are automatic and cannot be bypassed.

EXHAUSTIVE SWITCH ENFORCEMENT
When you switch on an enum using direct constant references, the compiler verifies ALL values are covered. Adding a new value to the enum forces you to handle it everywhere. No silent missing-case bugs.

BOUNDARY TO UNSET
Incrementing past the last value or decrementing before the first produces unset, not an index error or silent wraparound. Check with ? when you need to know.

SAFE STRING CONSTRUCTION
Constructing from an invalid string returns unset, not an exception. No try-catch overhead. No risk of uncaught IllegalArgumentException crashing production.

IMMUTABLE CONSTANTS
Enum values are immutable constants. You can compare and copy them but never modify the enum definition at runtime. No reflection tricks, no monkey-patching.

NO INTEGER CASTING
EK9 enums are not integers. You cannot accidentally pass 42 where a Season is expected. The type system prevents it at compile time.

UNSET PROPAGATION
Operations on unset enums produce unset results. No NullPointerException. No undefined behaviour. The unset state flows safely through your program until you check it.

See Q73 for exhaustive switch details. See Q99 for basic enumerations. See Q219 for all auto-generated operators. See Q222 for unset semantics.

defines module qa.enums.safety

  defines type

    Status
      Pending
      Active
      Paused
      Complete

  defines function

    describeStatus() as pure
      -> s as Status
      <- description <- String()

      //Exhaustive: compiler forces ALL values to be handled
      switch s
        case Status.Pending
          description: "waiting to start"
        case Status.Active
          description: "in progress"
        case Status.Paused
          description: "temporarily stopped"
        case Status.Complete
          description: "finished"
        default
          description: "no status set"

  defines program

    EnumSafetyDemo()
      stdout <- Stdout()

      // === EXHAUSTIVE SWITCH: all values must be covered ===

      for s in Status
        stdout.println(`${s}: ${describeStatus(s)}`)

      // === BOUNDARY TO UNSET: no index errors ===

      last <- Status.Complete
      last++
      stdout.println(`Past last isSet: ${last?}`)

      first <- Status.Pending
      first--
      stdout.println(`Before first isSet: ${first?}`)

      // === SAFE STRING CONSTRUCTION: no exceptions ===

      valid <- Status("Active")
      stdout.println(`Valid: ${valid}, isSet: ${valid?}`)

      invalid <- Status("Running")
      stdout.println(`Invalid isSet: ${invalid?}`)

      // === IMMUTABLE CONSTANTS: copy, do not modify ===

      original <- Status.Active
      copied <- Status(original)
      stdout.println(`Original: ${original}, Copy: ${copied}`)

      // === UNSET PROPAGATION: no null exceptions ===

      unsetStatus <- Status()
      result <- unsetStatus == Status.Active
      stdout.println(`Unset comparison isSet: ${result?}`)

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

C/C++: enums are integers, any integer value accepted, no type safety, missing switch cases are optional warnings only. Java: null enum throws NullPointerException, valueOf throws IllegalArgumentException, exhaustive switch only since Java 21 with sealed types. Python: no exhaustive checking in match/case for enums, Enum construction can raise ValueError. Go: iota constants are integers with zero type safety, any int passes. Rust: exhaustive match is strong, but no boundary-to-unset or safe string construction. Kotlin: when-expression exhaustive only when used as expression, nullable enums require explicit handling. C#: integer-backed, Enum.IsDefined for runtime checking only. EK9: compile-time exhaustive switch, boundary-to-unset, safe string construction, no integer casting, unset propagation.

boundary bug compile enum enumeration exhaustive immutable prevent protection safety switch

Explanation: EK9 enumerations do not have a 'valueOf()' method like Java enums. Use the String constructor 'Status("Active")' for safe string construction. Invalid strings return unset instead of throwing exceptions. See ek9 -h E50060 for details.


Question: How does EK9 validate dependency injection at compile time?

Does EK9 catch DI errors before runtime?

How does EK9 prevent missing dependency errors at compile time?

What DI validations does the EK9 compiler perform?

How does inversion of control work in EK9?

Answer:
EK9 validates ALL dependency injection at compile time. If your program compiles, every injection point is guaranteed to be satisfied. There are zero runtime DI failures.

FOUR COMPILE-TIME VALIDATIONS
The compiler performs four checks on every application definition:
1. Completeness: Every injection field ('!' suffix) must have a matching registration
2. Ordering: Dependencies must be registered before dependents
3. Cycle detection: Circular dependencies are rejected with error E08190
4. Field count: Components with excessive injection fields are flagged with E11040

CONTRAST WITH SPRING/GUICE
In Java Spring, a missing @Bean causes NoSuchBeanDefinitionException at runtime, sometimes minutes into startup. In Guice, a missing binding produces CreationException. In EK9, you find out during compilation, before any code runs.

WHY THIS MATTERS
Runtime DI failures are among the most frustrating bugs. They appear only when a specific code path is exercised, often in production. EK9 eliminates this entire category of defect by moving validation to compile time.

WORKING EXAMPLE
The code below demonstrates a correctly wired application. The compiler has verified that Logger is registered before Formatter, both are registered before MessageService (which injects them), and the program's injection point is satisfied.

See Q111 for component basics. See Q117 for singleton lifecycle. See Q228 for registration ordering. See Q229 for circular dependency detection. See Q230 for missing registration errors. See Q310 for compile-time quality philosophy.

See Q324 for @Autowired equivalent. See Q332 for runtime overhead comparison.

See Q666 for injection pure context. See Q673 for missing registration. See Q675 for transitive validation.

defines module qa.di.compile.time

  defines component

    Logger as abstract
      log() as abstract
        -> message as String
        <- output as String?

      default operator ?

    ConsoleLogger is Logger
      override log()
        -> message as String
        <- output as String: "LOG: " + message

      default operator ?

    Formatter as abstract
      format() as abstract
        -> text as String
        <- result as String?

      default operator ?

    UpperFormatter is Formatter
      override format()
        -> text as String
        <- result as String: text.upperCase()

      default operator ?

    MessageService as abstract
      greet() as abstract
        -> name as String
        <- greeting as String?

      default operator ?

    SimpleMessageService is MessageService
      logger as Logger!
      formatter as Formatter!

      override greet()
        -> name as String
        <- greeting <- String()
        formatted <- formatter.format("Hello " + name)
        greeting: logger.log(formatted)

      default operator ?

  defines application

    ValidatedApp
      register ConsoleLogger() as Logger
      register UpperFormatter() as Formatter
      register SimpleMessageService() as MessageService

  defines program

    CompileTimeDiDemo() with application of ValidatedApp
      stdout <- Stdout()

      // === ALL INJECTION VALIDATED AT COMPILE TIME ===

      service as MessageService!

      result <- service.greet("World")
      stdout.println(result)

      stdout.println("All injections verified by compiler")

Java Spring: NoSuchBeanDefinitionException at runtime, @Conditional can hide missing beans, circular dependency detection only at startup. Guice: CreationException at Injector creation, JIT bindings can mask issues. .NET: InvalidOperationException during service resolution, runtime-only. Python: no compile-time DI validation at all. Go: no built-in DI. Rust: no built-in DI. EK9: ALL DI validated at compile time, zero runtime failures possible.

circular compile completeness guarantee inject injection inversion ioc migrate ordering safety validate

dependency injection
DI
inject
inversion of control
IoC
autowired

Explanation: Injection fields must use abstract types, not concrete implementations. Only abstract components can be injection targets. See ek9 -h E08150 for details.

Explanation: String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.

Explanation: If the register statement for Logger were removed, the injection field 'logger as Logger!' in SimpleMessageService would have no matching registration and the compiler would reject it. See ek9 -h E08210 for details.


Question: Why does registration order matter in EK9 applications?

What order should I register components in EK9?

Does the sequence of register statements matter in EK9?

How do I order dependencies in an EK9 application?

Answer:
In EK9, the order of 'register' statements in an application definition matters. Dependencies must be registered BEFORE the components that depend on them.

WHY ORDER MATTERS
EK9 processes registrations sequentially. When the compiler encounters a component with injection fields, it verifies those dependencies are already registered. If component B injects component A, then A must appear before B in the register list.

CORRECT ORDERING
For a chain A -> B -> C (C depends on B, B depends on A):
  register ConcreteA() as AbstractA    <- no dependencies, register first
  register ConcreteB() as AbstractB    <- depends on A, register second
  register ConcreteC() as AbstractC    <- depends on B, register third

WHAT HAPPENS WITH WRONG ORDER
If you register B before A, the compiler reports an error because B's injection field for A cannot be satisfied at that point in the registration sequence.

DESIGN RATIONALE
Explicit ordering makes the dependency graph visible in the application definition. You can read the register list top-to-bottom and understand the initialization order. No hidden runtime sorting or lazy resolution.

See Q111 for component basics. See Q227 for compile-time validation overview. See Q229 for circular dependency detection. See Q232 for transitive component chains.

See Q326 for @Bean/@Configuration equivalent.

defines module qa.di.ordering

  defines component

    Logger as abstract
      log() as abstract
        -> message as String
        <- output as String?

      default operator ?

    ConsoleLogger is Logger
      override log()
        -> message as String
        <- output as String: "[LOG] " + message

      default operator ?

    Formatter as abstract
      format() as abstract
        -> text as String
        <- result as String?

      default operator ?

    SimpleFormatter is Formatter
      logger as Logger!

      override format()
        -> text as String
        <- result <- String()
        result: logger.log(text.upperCase())

      default operator ?

    NotificationService as abstract
      notify() as abstract
        -> message as String
        <- result as String?

      default operator ?

    EmailNotifier is NotificationService
      formatter as Formatter!

      override notify()
        -> message as String
        <- result <- String()
        result: formatter.format("NOTIFY: " + message)

      default operator ?

  defines application

    OrderedApp
      // Dependencies registered BEFORE dependents
      register ConsoleLogger() as Logger
      register SimpleFormatter() as Formatter
      register EmailNotifier() as NotificationService

  defines program

    OrderingDemo() with application of OrderedApp
      stdout <- Stdout()

      // === CORRECT ORDER: Logger -> Formatter -> NotificationService ===

      notifier as NotificationService!

      result <- notifier.notify("System ready")
      stdout.println(result)

      stdout.println("Three-layer chain wired in correct order")

Java Spring: registration order does not matter, container resolves lazily. Guice: binding order irrelevant, dependency graph resolved at injector creation. .NET: registration order irrelevant, resolved on demand. Python: manual wiring, order depends on programmer. Go: manual wiring, initialization order explicit. Rust: no built-in DI. EK9: explicit registration order required, dependencies before dependents, compiler enforces correct sequencing.

after application before chain dependency explicit initialization inject order register sequence

register SimpleFormatter() as Formatter
      register ConsoleLogger() as Logger

Explanation: SimpleFormatter injects Logger, so Logger must be registered before SimpleFormatter. Reversing the order means the dependency is not yet available when the compiler processes SimpleFormatter. See ek9 -h E08200 for details.

Explanation: Injection fields must reference abstract component types, not concrete implementations. The application registers the concrete type; the field declares the abstract type. See ek9 -h E08150 for details.


Question: How does EK9 prevent circular dependencies?

Does EK9 detect dependency cycles at compile time?

How do I fix circular dependencies in EK9?

Answer:
EK9 detects circular dependencies at compile time using depth-first search on the dependency graph. If component A depends on B and B depends on A, the compiler reports error E08190 and refuses to compile.

WHAT E08190 CATCHES
The compiler traces every injection chain. If it finds a cycle of any length (A->B->A, or A->B->C->A, etc.), it reports E08190 with the cycle path so you know exactly which components are involved.

FIXING CIRCULAR DEPENDENCIES
The solution is always the same: break the cycle by extracting the shared responsibility into a separate component. If A and B both need each other, extract the shared logic into C, then have both A and B depend on C.

BEFORE (circular, would not compile):
  ServiceA injects ServiceB
  ServiceB injects ServiceA

AFTER (refactored, compiles correctly):
  SharedLogic has no injections
  ServiceA injects SharedLogic
  ServiceB injects SharedLogic

CONTRAST WITH SPRING
Spring tries to resolve circular dependencies with proxy objects and lazy initialization, which leads to subtle bugs, partial initialization, and startup order sensitivity. EK9 simply rejects cycles.

DESIGN PRINCIPLE
Circular dependencies indicate a design flaw. By rejecting them at compile time, EK9 forces clean layered architecture from the start.

See Q228 for registration ordering. See Q227 for compile-time validation overview. See Q232 for clean multi-layer dependency chains.

See Q329 for Guice comparison.

defines module qa.di.circular

  defines component

    // === CORRECT PATTERN: Shared logic extracted to break potential cycle ===

    SharedFormatter as abstract
      format() as abstract
        -> text as String
        <- result as String?

      default operator ?

    PlainFormatter is SharedFormatter
      override format()
        -> text as String
        <- result as String: `[${text}]`

      default operator ?

    UserService as abstract
      getUser() as abstract
        -> id as Integer
        <- name as String?

      default operator ?

    SimpleUserService is UserService
      formatter as SharedFormatter!

      override getUser()
        -> id as Integer
        <- name <- String()
        name: formatter.format("User-" + $id)

      default operator ?

    OrderService as abstract
      getOrder() as abstract
        -> id as Integer
        <- details as String?

      default operator ?

    SimpleOrderService is OrderService
      formatter as SharedFormatter!

      override getOrder()
        -> id as Integer
        <- details <- String()
        details: formatter.format("Order-" + $id)

      default operator ?

  defines application

    CleanApp
      // SharedFormatter registered first (no dependencies)
      // Both services depend on formatter, NOT on each other
      register PlainFormatter() as SharedFormatter
      register SimpleUserService() as UserService
      register SimpleOrderService() as OrderService

  defines program

    CircularDepsDemo() with application of CleanApp
      stdout <- Stdout()

      // === CLEAN DESIGN: no circular dependencies ===

      users as UserService!
      orders as OrderService!

      stdout.println(users.getUser(42))
      stdout.println(orders.getOrder(99))

      stdout.println("Both services share formatter without circular dependency")

Java Spring: circular dependencies resolved via proxies and @Lazy, deprecated since Spring 6, often indicates design flaw. Guice: circular dependency causes ProvisionException, except with Provider indirection. .NET: circular DI causes InvalidOperationException at runtime. Python: circular imports cause ImportError. Go: no built-in DI, circular package imports are compile errors. Rust: no built-in DI. EK9: E08190 compile-time error, DFS cycle detection, forces clean architecture.

E08190 break circular compile cycle dependency design detect graph inject migrate refactor

Explanation: If SimpleUserService injected OrderService and SimpleOrderService injected UserService, it would create a circular dependency cycle (A->B->A) that the compiler detects and rejects. The correct pattern extracts shared logic into a separate component. See ek9 -h E50001 for details.

Explanation: Injection fields must use abstract component types. Injecting the concrete PlainFormatter directly violates the abstract injection requirement. See ek9 -h E08150 for details.


Question: What happens if I forget to register a dependency in EK9?

Does EK9 catch missing component registrations?

What error do I get for unregistered dependencies in EK9?

How does EK9 handle missing DI bindings?

Answer:
If you forget to register a dependency, the EK9 compiler reports a compile-time error identifying the unsatisfied injection point. Your code will not compile until every injection field has a matching registration.

COMPILE-TIME VS RUNTIME
In Java Spring, forgetting an @Bean produces NoSuchBeanDefinitionException at runtime, potentially minutes into application startup. In EK9, the compiler catches this immediately during compilation.

WHAT THE ERROR TELLS YOU
The compiler identifies:
- The component or program with the unsatisfied injection field
- The type that was expected to be registered
- The application definition where the registration is missing
This gives you everything needed to fix the problem in seconds.

COMPLETENESS CHECK
The compiler walks every injection field in every component and program linked to an application. For each '!' field, it verifies a matching 'register X() as Y' exists where Y matches the field type.

WORKING EXAMPLE
The code below shows a complete, correctly wired application. All three components are registered, and the program's injection point is satisfied. If any registration were removed, the compiler would report the error before any code runs.

See Q111 for component basics. See Q227 for compile-time validation. See Q228 for registration ordering.

See Q111 for components. See Q227 for compile-time DI. See Q228 for registration ordering.

defines module qa.di.missing

  defines component

    Cache as abstract
      get() as abstract
        -> key as String
        <- value as String?

      default operator ?

    InMemoryCache is Cache
      override get()
        -> key as String
        <- value as String: "cached:" + key

      default operator ?

    Repository as abstract
      find() as abstract
        -> id as Integer
        <- result as String?

      default operator ?

    CachedRepository is Repository
      cache as Cache!

      override find()
        -> id as Integer
        <- result <- String()
        result: cache.get("item-" + $id)

      default operator ?

    Controller as abstract
      handle() as abstract
        -> request as String
        <- response as String?

      default operator ?

    ItemController is Controller
      repo as Repository!

      override handle()
        -> request as String
        <- response <- String()
        response: repo.find(1)

      default operator ?

  defines application

    CompleteApp
      // All three registrations present - compiler verifies completeness
      register InMemoryCache() as Cache
      register CachedRepository() as Repository
      register ItemController() as Controller

  defines program

    MissingRegDemo() with application of CompleteApp
      stdout <- Stdout()

      // === ALL REGISTRATIONS SATISFIED ===

      controller as Controller!

      response <- controller.handle("/items/1")
      stdout.println(response)

      stdout.println("Every injection point satisfied at compile time")

Java Spring: NoSuchBeanDefinitionException at runtime, UnsatisfiedDependencyException for @Autowired. Guice: ConfigurationException for missing bindings at Injector.getInstance(). .NET: InvalidOperationException 'No service for type'. Python: KeyError or AttributeError at runtime. Go: nil pointer at runtime. Rust: compile-time via generics, but no built-in DI. EK9: compile-time error identifies exact missing registration, zero runtime failures.

binding catch compile completeness dependency error forget inject migrate missing registration unsatisfied validate

Explanation: Removing the Cache registration leaves CachedRepository's injection field 'cache as Cache!' unsatisfied. The compiler rejects programs where any injection point lacks a matching registration. See ek9 -h E08210 for details.


Question: How do programs connect to applications for dependency injection?

What does 'with application of' mean in EK9?

Can multiple programs share one application in EK9?

Do all EK9 programs need an application?

How do I use the application construct in EK9?

Answer:
Programs declare their dependency source using 'with application of AppName'. This links the program to a specific application definition that provides its injected dependencies.

BASIC LINKING
A program declares its application after the parameter list:
  MyProgram() with application of MyApp
    service as ServiceType!
The compiler verifies that MyApp registers a concrete ServiceType.

MULTIPLE PROGRAMS, ONE APPLICATION
Several programs can link to the same application. Each program gets its own injection of the registered components:
  ProgramA() with application of SharedApp
    svc as ServiceType!
  ProgramB() with application of SharedApp
    svc as ServiceType!

PROGRAMS WITHOUT DI
Programs that do not need injection simply omit 'with application of':
  SimpleProg()
    stdout <- Stdout()
    stdout.println("No DI needed")
Stdout is a built-in, not an injected component.

WHEN TO USE APPLICATIONS
Use 'with application of' when your program needs components, services, or other registered dependencies. Programs that only use built-in types and local variables do not need an application.

See Q111 for component basics. See Q117 for singleton lifecycle. See Q227 for compile-time validation.

See Q326 for @Bean equivalent. See Q327 for Spring Boot comparison.

See Q672 for program-application link details.

defines module qa.di.program.app

  defines component

    Greeter as abstract
      greet() as abstract
        -> name as String
        <- message as String?

      default operator ?

    FriendlyGreeter is Greeter
      override greet()
        -> name as String
        <- message as String: `Hello, ${name}!`

      default operator ?

  defines application

    SharedApp
      register FriendlyGreeter() as Greeter

  defines program

    // === PROGRAM WITH APPLICATION: has injection ===

    ProgramWithDI() with application of SharedApp
      stdout <- Stdout()

      greeter as Greeter!
      stdout.println(greeter.greet("Alice"))

    // === SECOND PROGRAM: same application ===

    AnotherProgram() with application of SharedApp
      stdout <- Stdout()

      greeter as Greeter!
      stdout.println(greeter.greet("Bob"))

    // === PROGRAM WITHOUT APPLICATION: no injection needed ===

    StandaloneProgram()
      stdout <- Stdout()

      // No injection fields, no application needed
      stdout.println("Running without DI")

Java Spring: @SpringBootApplication auto-scans, no explicit linking per class. Guice: modules bound to Injector, no per-program declaration. .NET: IServiceCollection configured in Startup, all classes share one container. Python: no language-level DI linking. Go: no language-level DI. Rust: no language-level DI. EK9: explicit 'with application of' per program, multiple programs can share one application, programs without DI omit the clause entirely.

application connect construct declare defines inject injection link migrate program share wiring

Explanation: Injection fields must use abstract component types. Injecting the concrete FriendlyGreeter directly bypasses the abstraction that enables substitution. See ek9 -h E08150 for details.


Question: How do components inject other components in EK9?

Can EK9 components depend on other components?

How do transitive dependency chains work in EK9?

How does field injection work between components?

Answer:
Components inject other components using the '!' suffix on fields, exactly like programs inject components. The compiler resolves these transitive chains at compile time.

FIELD INJECTION IN COMPONENTS
A concrete component can declare injection fields:
  ConcreteService is AbstractService
    repo as Repository!
    override doWork()
      <- rtn as String: repo.find(1)
The '!' on 'repo' tells the compiler this field is injected, not constructed locally.

TRANSITIVE CHAINS
Multi-layer dependency chains are common:
  Repository depends on nothing
  Service depends on Repository
  Controller depends on Service
The compiler traces the entire chain and verifies every link is registered.

REGISTRATION ORDER
Register in dependency order (leaves first, roots last):
  register ConcreteRepo() as Repository
  register ConcreteService() as Service
  register ConcreteController() as Controller

ALL COMPILE-TIME
Every field injection in every component is verified before code runs. The compiler sees the full dependency graph across all components and programs.

See Q228 for registration ordering. See Q111 for component basics. See Q227 for compile-time validation.

defines module qa.di.chains

  defines component

    // === LAYER 1: Repository (no dependencies) ===

    Repository as abstract
      find() as abstract
        -> id as Integer
        <- result as String?

      default operator ?

    InMemoryRepo is Repository
      override find()
        -> id as Integer
        <- result as String: "Record-" + $id

      default operator ?

    // === LAYER 2: Service (depends on Repository) ===

    DataService as abstract
      process() as abstract
        -> id as Integer
        <- output as String?

      default operator ?

    DefaultDataService is DataService
      repo as Repository!

      override process()
        -> id as Integer
        <- output <- String()
        raw <- repo.find(id)
        output: "Processed: " + raw

      default operator ?

    // === LAYER 3: Controller (depends on Service) ===

    ApiController as abstract
      handle() as abstract
        -> id as Integer
        <- response as String?

      default operator ?

    DefaultApiController is ApiController
      service as DataService!

      override handle()
        -> id as Integer
        <- response <- String()
        result <- service.process(id)
        response: "Response: " + result

      default operator ?

  defines application

    ChainedApp
      // Register in dependency order: leaves first
      register InMemoryRepo() as Repository
      register DefaultDataService() as DataService
      register DefaultApiController() as ApiController

  defines program

    ComponentChainDemo() with application of ChainedApp
      stdout <- Stdout()

      // === THREE-LAYER INJECTION CHAIN ===

      controller as ApiController!

      response <- controller.handle(42)
      stdout.println(response)

      stdout.println("Repository -> Service -> Controller chain resolved at compile time")

Java Spring: @Autowired field injection between @Component classes, resolved at runtime via container. Guice: @Inject field injection, resolved at Injector creation. .NET: constructor injection between registered services. Python: no built-in component injection. Go: manual struct composition. Rust: no built-in DI. EK9: '!' field injection between components, transitive chains resolved at compile time, registration order enforced.

chain component controller depend field inject injection layer repository service transitive

Explanation: Injection fields must use abstract types. Concrete types like InMemoryRepo cannot be injection targets because DI resolves through abstract-to-concrete registration. See ek9 -h E08150 for details.


Question: Can dynamic classes use dependency injection in EK9?

Do anonymous trait implementations support injection in EK9?

How does DI work with inline classes in EK9?

Can I inject components into dynamic classes?

Answer:
Dynamic classes (anonymous trait implementations) can have injection fields. The compiler follows the call graph from programs through functions to find all dynamic class instantiations and validates their injection fields.

DYNAMIC CLASS WITH INJECTION
A dynamic class created with '() with trait of' can declare '!' fields:
  handler <- () with trait of Processable
    logger as Logger!
    override process()
      <- rtn as String: logger.log("processing")
The Logger injection is resolved from the application context.

CALL GRAPH ANALYSIS
The compiler traces the execution path from the program through any functions that create dynamic classes. If a function creates a dynamic class with injection fields, those fields must be satisfiable from the program's application.

WHY THIS IS UNIQUE
Most DI frameworks only support injection on named, registered classes. EK9 extends injection to anonymous inline implementations, which is powerful for callbacks, handlers, and one-off implementations that still need access to registered services.

COMPILE-TIME SAFETY
As with all EK9 DI, dynamic class injection is validated at compile time. If the required component is not registered, the compiler reports the error.

See Q227 for compile-time validation. See Q111 for component basics. See Q115 for dynamic class basics. See Q234 for component lifecycle.

defines module qa.di.dynamic

  defines trait

    Processor
      process() as abstract
        -> input as String
        <- output as String?

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

  defines component

    Logger as abstract
      log() as abstract
        -> message as String
        <- result as String?

      default operator ?

    ConsoleLogger is Logger
      override log()
        -> message as String
        <- result as String: "LOG: " + message

      default operator ?

  defines function

    createProcessor()
      <- handler as Processor?

      // === DYNAMIC CLASS WITH INJECTION ===
      handler: () with trait of Processor
        logger as Logger!

        override process()
          -> input as String
          <- output <- String()
          output: logger.log(input)

        default operator ?

  defines application

    DynamicApp
      register ConsoleLogger() as Logger

  defines program

    DynamicInjectionDemo() with application of DynamicApp
      stdout <- Stdout()

      // === FUNCTION CREATES DYNAMIC CLASS WITH INJECTION ===

      handler <- createProcessor()
      result <- handler.process("test message")
      stdout.println(result)

      stdout.println("Dynamic class injection validated at compile time")

Java: anonymous inner classes cannot use @Autowired, must receive dependencies via constructor or enclosing scope. Guice: no injection into anonymous classes. .NET: no injection into anonymous types. Python: no built-in DI for anonymous objects. Go: no anonymous classes. Rust: no anonymous classes with DI. EK9: dynamic classes with '() with trait of' support '!' injection fields, compiler traces call graph to validate.

anonymous call callback capture class closure dynamic graph handler inject injection inline trait

Explanation: Even in dynamic classes, injection fields must reference abstract component types. The concrete type is determined by the application registration, not the injection field declaration. See ek9 -h E08150 for details.

Explanation: Removing the only registration from the application leaves it empty, requiring at least one implementation to be provided. See ek9 -h E07160 for details.


Question: What is the lifecycle of injected components in EK9?

How are EK9 components created and destroyed?

What are the DI lifecycle phases in EK9?

Does EK9 support different component scopes like Spring?

Answer:
EK9 components follow a simple three-phase lifecycle: prepare, execute, decommission. There is only one scope: singleton for the program's lifetime.

PREPARE PHASE
When a program starts, the application's _prepare() method creates all registered components in registration order. Dependencies are created before dependents, so injection fields are satisfied as each component is initialized.

EXECUTE PHASE
The program body runs with all components fully initialized. Every injection point references the same instance throughout the program's execution.

DECOMMISSION PHASE
When the program completes, _decommission() releases components in REVERSE registration order. Dependents are released before their dependencies, ensuring no component uses a decommissioned dependency.

SINGLETON ONLY
EK9 has one scope: singleton for the program lifetime. There are no request, session, or prototype scopes. This simplicity eliminates an entire class of scope-related bugs.

CONTRAST WITH SPRING
Spring has singleton, prototype, request, session, and application scopes. Each scope adds complexity and potential for scope mismatch bugs (e.g., injecting a request-scoped bean into a singleton). EK9 avoids this entirely.

WHY SINGLETON ONLY
Most DI complexity comes from managing multiple scopes. Programs in EK9 are designed to be focused units. If you need different lifecycle management, use explicit construction within the program body.

See Q117 for singleton lifecycle. See Q111 for component basics. See Q227 for compile-time validation. See Q231 for program-application linking.

See Q330 for singleton-only scope rationale. See Q332 for performance comparison.

defines module qa.di.lifecycle

  defines component

    Logger as abstract
      log() as abstract
        -> message as String
        <- output as String?

      default operator ?

    AppLogger is Logger
      override log()
        -> message as String
        <- output as String: `[${message}]`

      default operator ?

    DataStore as abstract
      save() as abstract
        -> content as String
        <- result as String?

      default operator ?

    InMemoryStore is DataStore
      logger as Logger!

      override save()
        -> content as String
        <- result <- String()
        result: logger.log("Saved: " + content)

      default operator ?

    AppService as abstract
      run() as abstract
        -> input as String
        <- output as String?

      default operator ?

    MainService is AppService
      store as DataStore!
      logger as Logger!

      override run()
        -> input as String
        <- output <- String()
        saved <- store.save(input)
        output: logger.log("Service processed: " + saved)

      default operator ?

  defines application

    LifecycleApp
      // _prepare() creates in this order:
      //   1. AppLogger (no dependencies)
      //   2. InMemoryStore (depends on Logger)
      //   3. MainService (depends on DataStore and Logger)
      register AppLogger() as Logger
      register InMemoryStore() as DataStore
      register MainService() as AppService
      // _decommission() releases in reverse:
      //   1. MainService
      //   2. InMemoryStore
      //   3. AppLogger

  defines program

    LifecycleDemo() with application of LifecycleApp
      stdout <- Stdout()

      // === EXECUTE PHASE: all components fully initialized ===

      service as AppService!

      result <- service.run("test data")
      stdout.println(result)

      stdout.println("Components created in order, released in reverse")

Java Spring: singleton (default), prototype, request, session, application scopes. Complex lifecycle: @PostConstruct, @PreDestroy, InitializingBean, DisposableBean, BeanPostProcessor. Guice: unscoped (default), @Singleton, @RequestScoped, custom scopes. .NET: transient, scoped, singleton lifetimes. Python: manual lifecycle management. Go: manual lifecycle. Rust: ownership-based lifecycle. EK9: singleton only, _prepare() creates in order, _decommission() releases in reverse, no scope mismatch bugs.

component create decommission dependency destroy inject lifecycle migrate order phase prepare reverse scope singleton

register MainService() as AppService
      register AppLogger() as Logger
      register InMemoryStore() as DataStore

Explanation: MainService depends on both DataStore and Logger, so both must be registered before MainService. The prepare phase creates components in registration order, so dependencies must come first. See ek9 -h E08200 for details.

Explanation: Injection fields must use abstract component types. The lifecycle manager resolves abstract types to their registered concrete implementations during the prepare phase. See ek9 -h E08150 for details.


Question: What stream operations are available in EK9?

What is the complete list of EK9 stream pipeline operations?

What can I do in a stream pipeline in EK9?

What are all the stream stages available in EK9?

How do I reference all EK9 pipeline operations?

Answer:
EK9 stream pipelines use cat source | operations | terminal syntax. Operations fall into categories: filtering (filter, reject), transforming (map), ordering (sort), limiting (head, tail, skip), grouping (group by), flattening (flatten), deduplication (uniq), side effects (tee), and parallel execution (async). Terminals are collect as Type or > stdout.

Each operation expects a specific function type: filter uses Predicate (T to Boolean), sort uses Comparator (T, T to Integer), map uses a transform function (T to R). Use 'ek9 -h stream' for the complete reference.

Pipeline stages chain with |:
  cat items | filter by pred | sort | head 5 | collect as List of T

See Q120 for sort. See Q122 for collect. See Q123 for flatten. See Q124 for group by. See Q125 for head/tail/skip. See Q133 for tee and uniq. See Q237 for streams vs loops. See Q954 for collect as custom type.

defines module qa.streams.reference

  defines function

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

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

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

  defines program

    StreamOpsReferenceDemo()
      stdout <- Stdout()

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

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

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

      // === SORT ===
      sorted <- cat numbers | sort | collect as List of Integer
      stdout.println(`sort: ${sorted}`)

      // === HEAD ===
      firstThree <- cat numbers | head 3 | collect as List of Integer
      stdout.println(`head 3: ${firstThree}`)

      // === SKIP ===
      afterFive <- cat numbers | skip 5 | collect as List of Integer
      stdout.println(`skip 5: ${afterFive}`)

      // === TAIL ===
      lastTwo <- cat numbers | tail 2 | collect as List of Integer
      stdout.println(`tail 2: ${lastTwo}`)

      // === UNIQ (sort first) ===
      unique <- cat numbers | sort | uniq | collect as List of Integer
      stdout.println(`sort + uniq: ${unique}`)

      // === COMBINED: filter + sort + head ===
      topThreePositive <- cat numbers | filter by isPositive | sort | head 3 | collect as List of Integer
      stdout.println(`filter+sort+head: ${topThreePositive}`)

      // === COLLECT AS INTEGER (sum) ===
      total <- cat numbers | filter by isPositive | collect as Integer
      stdout.println(`sum of positives: ${total}`)

      // === TEE for intermediate capture ===
      captured <- List() of Integer
      result <- cat numbers | filter by isPositive | tee in captured | sort | collect as List of Integer
      stdout.println(`tee captured: ${captured}`)
      stdout.println(`final sorted: ${result}`)

      // === DIRECT OUTPUT ===
      cat numbers | filter by isPositive | map with intToString > stdout

Java: Stream API (filter, map, sorted, collect). Python: list comprehensions, map(), filter(). Rust: Iterator trait (filter, map, take, collect). Go: manual loops. EK9: cat | filter | sort | head | collect, Unix pipe syntax with typed function stages.

all async cat collect complete filter flatten group head list map migrate operations pipe pipeline reference reject skip sort stream swift tail tee uniq

stream operations
stream API
pipe operations

Explanation: Stream filter predicates must return Boolean. A function used with 'filter by' that returns Integer instead of Boolean triggers E50030. See ek9 -h E50030 for details.


Question: How do I create a custom iterator so my type works with cat and for-in?

How do I make my own type iterable in EK9?

How do I implement the iterator pattern for a custom class in EK9?

How do I stream my own type with cat in EK9?

Answer:
To make a custom type work with cat (stream source) and for-in loops, your type needs to provide an iterator() method. EK9 uses the iterator pattern: your type returns an Iterator of T, which the stream or loop consumes.

THE ITERATOR CONTRACT
An iterator must support two operations:
  hasNext()      Returns true if more elements are available.
  next()         Returns the next element and advances.

The for-in loop calls hasNext() before each iteration and next() to get each element. The cat operation does the same to feed elements into a pipeline.

PATTERN 1: DELEGATE TO A COLLECTION
The simplest approach is to store items in a List and delegate iteration:
  Library
    books as List of Book
    iterator() as pure
      <- rtn as Iterator of Book: books.iterator()

This is the most common pattern. Any class that wraps a collection can expose it for iteration.

PATTERN 2: DYNAMIC CLASS ITERATOR
For lazy generation (values computed on demand), create a dynamic class that implements Iterator of T:
  NumberRange
    iterator()
      <- rtn as Iterator of Integer: createIterator(start, limit)

  createIterator()
    -> start as Integer, limit as Integer
    <- rtn as Iterator of Integer?
    current <- Integer(start)
    rtn: (current, limit) is Iterator of Integer as class
      override hasNext() as pure
        <- rtn as Boolean: current? and limit? and current <= limit
      override next()
        <- rtn as Integer: Integer(current)
        current++
      default operator ?

The dynamic class captures 'current' and 'limit' by value. Each call to next() advances the internal position. The hasNext() method checks if more elements remain.

USING WITH FOR-IN
Once your type has iterator(), for-in works automatically:
  range <- NumberRange(1, 5)
  for num in range
    stdout.println($num)

USING WITH CAT (STREAM SOURCE)
The same type works as a stream source:
  range <- NumberRange(1, 10)
  evens <- cat range | filter by isEven | collect as List of Integer

This is the bridge between OOP types and functional stream processing. Any type with iterator() becomes a first-class stream source.

FRESH ITERATORS
The iterator() method should create a fresh iterator each time. This allows multiple independent iterations over the same source.

BUILT-IN ITERABLE TYPES
These types already provide iterators: List, Dict (iterates DictEntry values), Optional (yields 0 or 1 elements), Result (yields ok value if present), and all enumeration types.

See Q65 for for-in loops. See Q85 for Optional as iterator. See Q87 for Result as iterator. See Q89 for stream pipeline basics. See Q99 for enumeration iteration. See Q115 for dynamic classes. See Q224 for enum streams. See Q235 for complete stream operations reference. See Q237 for streams vs loops decision guide.

defines module qa.streams.customiterators

  defines class

    NumberRange
      start as Integer?
      limit as Integer?

      default private NumberRange() as pure

      NumberRange() as pure
        ->
          start as Integer
          limit as Integer
        this.start :=? start
        this.limit :=? limit

      iterator()
        <- rtn as Iterator of Integer: createIterator(start, limit)

      default operator ?

  defines function

    createIterator()
      ->
        start as Integer
        limit as Integer
      <- rtn as Iterator of Integer?

      current <- Integer(start)
      rtn: (current, limit) is Iterator of Integer as class
        override hasNext() as pure
          <- rtn as Boolean: current? and limit? and current <= limit

        override next()
          <- rtn as Integer: Integer(current)
          current++

        default operator ?

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

  defines program

    CustomIteratorDemo()
      stdout <- Stdout()

      // === FOR-IN WITH CUSTOM ITERATOR ===

      range <- NumberRange(1, 5)
      stdout.println("For-in iteration:")
      for num in range
        stdout.println(`  ${num}`)

      // === CAT WITH CUSTOM ITERATOR (stream source) ===

      range2 <- NumberRange(1, 10)
      evens <- cat range2 | filter by isEven | collect as List of Integer
      stdout.println(`Even numbers 1-10: ${evens}`)

      // === MULTIPLE ITERATIONS (fresh iterator each time) ===

      range3 <- NumberRange(1, 3)
      first <- cat range3 | collect as List of Integer
      second <- cat range3 | collect as List of Integer
      stdout.println(`First pass: ${first}`)
      stdout.println(`Second pass: ${second}`)

Java: implement Iterable<T> with iterator() method, Iterator<T> with hasNext() and next(), for-each loop and Stream.of() consume iterables. Python: implement __iter__() returning self with __next__(), raise StopIteration when done, for-in and list comprehensions consume iterables. Rust: implement Iterator trait with next() returning Option<T>, for-in and .iter() consume iterators, IntoIterator for owned iteration. Go: no iterator interface, manual for-range with index, channels for generator pattern. Kotlin: implement Iterable<T> or Iterator<T>, sequence builders for lazy generation. JavaScript: implement Symbol.iterator returning object with next() returning value and done. EK9: provide iterator() method returning Iterator of T, use dynamic class is Iterator of T as class with override hasNext() and override next(), works with both for-in loops and cat stream pipelines.

anonymous capture cat class closure custom define dynamic for-in hasNext implement iterable iterator loop next pattern pipe pipeline source stream type

stdout.display(`Even numbers 1-10: ${evens}`)

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


Question: When should I use a stream pipeline vs a loop in EK9?

What is the difference between a stream and a loop in EK9?

Should I use cat pipe collect or a for loop in EK9?

When are streams better than loops in EK9?

How do I choose between a stream pipeline and a for-in loop in EK9?

Answer:
EK9 provides both stream pipelines and loops. Use streams for filtering, transforming, limiting, and chaining operations. Use loops for side effects and condition-based iteration.

USE STREAMS WHEN
Filtering (filter by), transforming (map with), limiting (head N as early exit), deduplication (sort | uniq), chaining multiple operations, or grouping (group by).

USE FOR-IN LOOPS WHEN
Side effects on every item (printing, saving). Simple accumulation where a loop is more readable.

USE FOR-IN EXPRESSIONS WHEN
Complex multi-variable accumulation or fold/reduce logic. See Q82.

USE WHILE LOOPS WHEN
Condition-based iteration (polling, retrying, external APIs). See Q66.

DECISION SUMMARY
  Filter or limit?           Stream (filter by, head)
  Transform each item?       Stream (map with)
  Early exit?                Stream (head N) — See Q125
  Side effects on all items? For-in loop
  Complex accumulation?      For-in expression or collect as
  Condition-based?           While loop

See Q51 for abstract functions in pipelines. See Q52 for dynamic functions. See Q54 for Predicate/Comparator types. See Q64-Q66 for loop types. See Q80/Q82 for loop expressions. See Q89 for stream basics. See Q122 for collect as. See Q125 for head as early exit. See Q145 for replacing break/continue. See Q235 for stream operations reference. See Q261 for idiomatic patterns.

defines module qa.streams.vsloops

  defines function

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

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

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

  defines program

    StreamsVsLoopsDemo()
      stdout <- Stdout()

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

      // === STREAM: Filter + Transform + Limit ===

      topThreeDoubledEvens <- cat numbers
        | filter by isEven
        | map with doubleIt
        | head 3
        | collect as List of Integer
      stdout.println(`Stream (filter+map+head): ${topThreeDoubledEvens}`)

      // === STREAM: Sum with collect as Integer ===

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

      // === FOR-IN LOOP: Side effects ===

      stdout.println("Loop (side effects):")
      for num in numbers
        stdout.println(`  Processing ${num}`)

      // === FOR-IN EXPRESSION: Accumulation ===

      sum <- for n in numbers
        <- rtn <- 0
        rtn: rtn + n
      stdout.println(`Loop expression sum: ${sum}`)

      // === FOR-IN EXPRESSION: Complex accumulation ===

      evenCount <- for n in numbers
        <- rtn <- 0
        if n mod 2 == 0
          rtn: rtn + 1
      stdout.println(`Even count via loop: ${evenCount}`)

      // === STREAM: Same result as above ===

      evenList <- cat numbers | filter by isEven | collect as List of Integer
      stdout.println(`Even count via stream: ${length evenList}`)

Java/Kotlin: Streams vs for-each, similar trade-offs but EK9 head replaces break. Python: list comprehensions vs for-loops. Go: only for-loops. EK9: cat|filter|map|collect for chains, for-in for side effects, for-in expression for accumulation, head replaces break.

accumulate choose collect comparison decide effect filter for guide head loop pipe side stream transform versus vs when while

Explanation: A predicate function used with 'filter by' must return Boolean. Returning Integer (e.g., the modulo result) instead of Boolean triggers E07520. See ek9 -h E07520 for details.


Question: What operators does EK9 support and why is the set fixed?

Can I define new operator symbols in EK9?

What is the complete list of EK9 operators?

How does EK9 enforce operator rules at compile time?

Answer:
EK9 supports approximately 50 operators in a FIXED set. Unlike C++ where you can define arbitrary operator symbols, EK9 only allows you to IMPLEMENT existing operators on your types. You cannot invent new operator symbols. This is a deliberate design choice that keeps the language predictable and enables comprehensive compile-time validation.

FIXED SET PHILOSOPHY
Every operator in EK9 has defined semantics: purity requirements, parameter counts, and return types. The compiler enforces ALL of these. If you get any wrong, you get a specific error code telling you exactly what is incorrect.

PURITY ENFORCEMENT
Some operators MUST be pure (no side effects): ==, <>, <=>, <, >, <=, >=, $, #?, ?, +, -, *, /, ~, !, abs, sqrt, mod, rem, and, or, xor, contains, matches, #^, #<, #>, length, empty, close.
Some operators CANNOT be pure: +=, -=, *=, /=, ++, --, :=:, :~:, :^:, |.
Violating purity rules produces errors E07500 (must be pure) or E07510 (cannot be pure).

PARAMETER COUNT ENFORCEMENT
Each operator has a fixed parameter count. Binary operators (==, +, -, *, /, and, or) take exactly 1 argument. Unary operators (~, !, ?, $, #?, abs, sqrt, ++, --) take 0 arguments. Incorrect counts produce E06280 (too many parameters) or E06290 (too few parameters).

RETURN TYPE ENFORCEMENT
Return types are strictly enforced: ? must return Boolean, #? must return Integer, $ must return String, and $$ must return JSON. Comparison operators (==, <>, <, >, <=, >=) must return Boolean. The <=> operator must return Integer. Mutation operators (+=, -=, ++, --, :=:, :~:, :^:) must NOT return anything. Violations produce E07520, E07550, E07570, E07580, E07410, E07420, or E07430.

OPERATOR CATEGORIES
Comparison: ==, <>, <, >, <=, >=, <=>, <~> (pure, 1 arg, return Boolean or Integer)
Arithmetic: +, -, *, /, ^, mod, rem (pure, 1 arg, return value)
Unary: - (negate), ~ (NOT for Boolean/Bits), !, abs, sqrt (pure, 0 args, return value)
Mutation: +=, -=, *=, /=, ++, --, :=:, :~:, :^:, | (not pure, no return)
Conversion: $, $$, #?, #^, #<, #> (pure, 0 args, return value)
State: ?, empty, length, close (pure, 0 args)
Logical: and, or, xor (pure, 1 arg, return Boolean)
Bit shift: <<, >> (pure, 1 arg, return value)
Coalescing: ??, ?:, <?, <=?, >?, >=? (expression-level, not implementable)
Query: contains, matches (pure, 1 arg, return Boolean)

See Q96 for class operator syntax. See Q116 for default operator generation. See Q239 for comparison operators. See Q240 for arithmetic operators. See Q241 for mutation operators. See Q242 for conversion operators. See Q243 for coalescing operators. See Q244 for boolean and bitwise operators. See Q245 for implementing a complete custom type. See Q248 for error code ranges and lookup. See Q254 for the Any type and its default operators inherited by all types. See Q279 for common AI operator confusion.

defines module qa.operators.finiteset

  defines class

    Score
      points <- Integer()

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

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

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

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

      operator + as pure
        -> other as Score
        <- rtn as Score: Score(points + other.points)

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

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

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

  defines program

    FiniteOperatorSetDemo()
      stdout <- Stdout()

      a <- Score(10)
      b <- Score(20)

      // Comparison operators
      stdout.println(`Equal: ${a == b}`)
      stdout.println(`Compare: ${a <=> b}`)

      // Arithmetic operator
      total <- a + b
      stdout.println(`Total: ${total}`)

      // Conversion operators
      stdout.println(`String: ${a}`)

C++: arbitrary operator overloading including new symbols via operator keyword, no purity or return type enforcement, error-prone. Java: no operator overloading at all, everything is methods. Python: magic methods (__add__, __eq__) with no compiler enforcement of signatures. Rust: trait-based (Add, Eq, Ord) with strict signatures but verbose. Go: no operator overloading. Kotlin: operator keyword with some constraints but less strict than EK9. JavaScript: no custom operators, Symbol-based limited overloading. EK9: fixed set of ~50 operators, compiler enforces purity, parameter count, and return type for every operator.

compile enforce error expression fixed immutable migrate mutation operator overload parameter pure purity return set side-effect type

operators
operator list
operator reference

Explanation: Equality operators must be marked 'as pure' to guarantee no side effects. Omitting 'as pure' on ==, <>, or <=> triggers E07500. See ek9 -h E07500 for details.

<- rtn as String: points == other.points

Explanation: The == operator must return Boolean. Returning a non-Boolean type like String triggers E07520. See ek9 -h E07520 for details.

<- rtn as Boolean: points <=> other.points

Explanation: The <=> operator must return Integer for three-way ordering. Returning Boolean instead triggers E07550. See ek9 -h E07550 for details.

Explanation: EK9 types do not have an equals() method. Use the == operator for equality comparisons. Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.


Question: How do comparison and ordering operators work in EK9?

How does fuzzy matching work with the <~> operator?

Answer:
EK9 provides a comprehensive set of comparison and ordering operators. All comparison operators MUST be pure, take exactly 1 argument, and return specific types. If either operand is unset, the result is unset.

EQUALITY AND INEQUALITY
The == operator tests equality and returns Boolean. The <> operator tests inequality and returns Boolean. Both take one argument of the same type.
  operator == as pure
    -> other as MyType
    <- rtn as Boolean: ...

ORDERING
The <, >, <=, >= operators test ordering and return Boolean. They take one argument.
  if score1 > score2
    stdout.println("Higher")

THREE-WAY COMPARISON (SPACESHIP)
The <=> operator returns Integer: negative if less, zero if equal, positive if greater. This is the foundation for sorting.
  operator <=> as pure
    -> other as MyType
    <- rtn as Integer: ...
When you implement <=>, you typically derive <, >, <=, >= from it.

FUZZY COMPARISON
The <~> operator returns Integer representing a distance or similarity score. For String, it returns the Levenshtein edit distance. The lower the value, the more similar.
  distance <- "hello" <~> "hallo"
  stdout.println(`Edit distance: ${distance}`)

CONTAINS AND MATCHES
The 'contains' operator checks if one value contains another. The 'matches' operator checks pattern matching (e.g., regex). Both are pure, take 1 argument, and return Boolean.
  if greeting contains "hello"
    stdout.println("Found")
  if email matches /[a-z]+@[a-z]+\.[a-z]+/
    stdout.println("Valid")

UNSET PROPAGATION
If either operand is unset, comparison results are unset (not false). This prevents silent logic errors with missing data.

See Q96 for class operators. See Q238 for the complete operator set. See Q243 for coalescing operators that handle unset values.

defines module qa.operators.comparison

  defines class

    Temperature
      celsius <- Float()

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

      operator == as pure
        -> other as Temperature
        <- rtn as Boolean: celsius == other.celsius

      operator <> as pure
        -> other as Temperature
        <- rtn as Boolean: celsius <> other.celsius

      operator <=> as pure
        -> other as Temperature
        <- rtn as Integer: celsius <=> other.celsius

      operator < as pure
        -> other as Temperature
        <- rtn as Boolean: celsius < other.celsius

      operator > as pure
        -> other as Temperature
        <- rtn as Boolean: celsius > other.celsius

      operator <= as pure
        -> other as Temperature
        <- rtn as Boolean: celsius <= other.celsius

      operator >= as pure
        -> other as Temperature
        <- rtn as Boolean: celsius >= other.celsius

      operator $ as pure
        <- rtn as String: `${celsius}C`

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

  defines program

    ComparisonDemo()
      stdout <- Stdout()

      freezing <- Temperature(0.0)
      boiling <- Temperature(100.0)
      body <- Temperature(37.0)

      // === EQUALITY AND INEQUALITY ===

      stdout.println(`Equal: ${freezing == boiling}`)
      stdout.println(`Not equal: ${freezing <> boiling}`)

      // === ORDERING ===

      stdout.println(`Freezing < boiling: ${freezing < boiling}`)
      stdout.println(`Body >= freezing: ${body >= freezing}`)

      // === THREE-WAY COMPARISON ===

      cmp <- freezing <=> boiling
      stdout.println(`Compare: ${cmp}`)

      // === FUZZY COMPARISON ON STRINGS ===

      reference <- "hello"
      distance <- reference <~> "hallo"
      stdout.println(`Fuzzy distance: ${distance}`)

      // === CONTAINS AND MATCHES ===

      greeting <- "hello world"
      stdout.println(`Contains hello: ${greeting contains "hello"}`)

Java: equals() for equality, compareTo() for ordering (returns int), no fuzzy match built-in. Python: __eq__, __lt__, __gt__ etc., functools.total_ordering derives from __lt__ and __eq__. Rust: PartialEq, Eq, PartialOrd, Ord traits, derived with #[derive()]. Go: no operator overloading, manual comparison functions. Kotlin: compareTo() operator, == delegates to equals(). JavaScript: no custom comparisons, == with type coercion problems. EK9: ==, <>, <, >, <=, >= return Boolean, <=> returns Integer, <~> returns fuzzy distance, all must be pure with 1 argument.

Boolean Integer comparison contains equal expression fuzzy greater less matches operator ordering sort spaceship unset

Explanation: The spaceship comparison operator must be marked 'as pure' because comparisons cannot have side effects. Omitting 'as pure' triggers E07500. See ek9 -h E07500 for details.

<- rtn as String: celsius <=> other.celsius

Explanation: The <=> operator must return Integer for three-way comparison ordering. Returning String instead triggers E07550. See ek9 -h E07550 for details.

<- rtn as Integer: celsius < other.celsius

Explanation: The < operator must return Boolean (true or false). Returning Integer instead triggers E07520. See ek9 -h E07520 for details.


Question: How do arithmetic and mathematical operators work in EK9?

What is the difference between mod and rem in EK9?

What mathematical operators does EK9 provide?

Answer:
EK9 provides binary arithmetic operators, unary mathematical operators, and specialized math operations. All arithmetic and math operators MUST be pure.

BINARY ARITHMETIC
The +, -, *, / operators are pure, take 1 argument, and return a new value of the same or compatible type. They do NOT modify the original.
  result <- a + b
  quotient <- total / count
The ^ operator computes power: base ^ exponent.
  squared <- value ^ 2

UNARY OPERATORS
The - operator as unary (0 arguments) negates a numeric value:
  negative <- -positive
For Integer and Float, unary - returns the negated value. The ~ operator is NOT for numeric negation. It is logical NOT for Boolean and bitwise NOT for Bits.

The ! operator computes factorial (returns result, 0 arguments):
  result <- 5!
Factorial is defined on Integer.

SQRT AND ABS
The sqrt operator returns the square root. The abs operator returns the absolute value. Both are pure, take 0 arguments, and return the same type.
  root <- sqrt value
  magnitude <- abs negativeNumber

MOD VS REM
Both mod and rem are pure, take 1 argument, and return a result. The critical difference:
  mod (modulus): Result ALWAYS has the same sign as the divisor (mathematical modulus).
  rem (remainder): Result has the same sign as the dividend (truncated division remainder).

With positive numbers they are identical:
  7 mod 3   yields 1
  7 rem 3   yields 1

With negative numbers they differ:
  -7 mod 3  yields 2  (always positive when divisor is positive)
  -7 rem 3  yields -1 (preserves dividend sign)

Use mod for cyclic operations (array indexing, clock arithmetic). Use rem for mathematical remainder after division.

See Q238 for the complete operator set. See Q241 for mutation operators (+=, -=). See Q39 for integer and float basics.

defines module qa.operators.arithmetic

  defines program

    ArithmeticDemo()
      stdout <- Stdout()

      // === BINARY ARITHMETIC ===

      a <- 10
      b <- 3
      stdout.println(`Add: ${a + b}`)
      stdout.println(`Subtract: ${a - b}`)
      stdout.println(`Multiply: ${a * b}`)
      stdout.println(`Divide: ${a / b}`)

      // === POWER ===

      base <- 2
      squared <- base ^ 3
      stdout.println(`Power: ${squared}`)

      // === MOD VS REM ===

      stdout.println(`7 mod 3: ${7 mod 3}`)
      stdout.println(`7 rem 3: ${7 rem 3}`)

      // With negative numbers - the critical difference
      stdout.println(`-7 mod 3: ${-7 mod 3}`)
      stdout.println(`-7 rem 3: ${-7 rem 3}`)

      // === UNARY NEGATE (unary minus) ===

      positive <- 42
      negative <- -positive
      stdout.println(`Negate: ${negative}`)

      // === ABS AND SQRT ===

      negNum <- -25
      stdout.println(`Abs: ${abs negNum}`)

      floatVal <- 16.0
      stdout.println(`Sqrt: ${sqrt floatVal}`)

Java: % is remainder (not modulus), Math.floorMod() for true modulus, Math.abs(), Math.sqrt(). Python: % is true modulus (matches divisor sign), divmod() for both. Rust: % is remainder, rem_euclid() for modulus. Go: % is remainder, no built-in modulus. Kotlin: % is remainder, mod() for modulus. JavaScript: % is remainder, no built-in modulus. EK9: separate mod (true modulus) and rem (remainder) operators, plus sqrt, abs, ! (factorial), unary - (negate), ^ (power).

abs add arithmetic divide expression factorial math mod modulus multiply negate operator power rem remainder sqrt subtract

arithmetic
math operator
add subtract multiply

Explanation: EK9 Integer has no absoluteValue() method. Use the prefix operator 'abs value'. While value.abs() also works, Java-style verbose names like absoluteValue() do not exist. See ek9 -h E50060 for details.

Explanation: EK9 Float has no squareRoot() method. Use the prefix operator 'sqrt value'. While value.sqrt() also works, Java-style verbose names like squareRoot() do not exist. See ek9 -h E50060 for details.


Question: What are mutation operators and how do they differ from pure operators?

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

How do increment and decrement work in EK9?

Which EK9 operators modify the object in place?

Answer:
Mutation operators modify an object in place. They CANNOT be marked pure and MUST NOT return a value. This is the opposite of arithmetic operators like + which create NEW values and MUST be pure.

COMPOUND ASSIGNMENT
The +=, -=, *=, /= operators modify the object in place. They take 1 argument and return nothing.
  score += 10
  balance -= withdrawal
  count *= factor
These are equivalent to reassigning: score: score + 10, but more concise.

INCREMENT AND DECREMENT
The ++ and -- operators take 0 arguments and return nothing.
  counter++
  remaining--

COPY, MERGE, REPLACE
The :=: (copy), :~: (merge), and :^: (replace) operators are mutation operators. They take 1 argument and return nothing.
  target :=: source
  config :~: partial
  record :^: replacement
Copy overwrites all fields. Merge copies only SET fields. Replace is a full content replacement.

PIPE OPERATOR
The | (pipe) operator is a mutation operator used in stream processing. It takes 1 argument and returns nothing.

PURE VS MUTATION: THE KEY DISTINCTION
Pure operator + creates a NEW value:
  c <- a + b
The original 'a' is unchanged. A new value is created and assigned to 'c'.

Mutation operator += modifies IN PLACE:
  a += b
The object 'a' is directly modified. No new value is created.

IMPLEMENTING MUTATION OPERATORS
Mutation operators CANNOT have 'as pure' and MUST NOT have a return declaration:
  operator +=
    -> other as MyType
    this.value: this.value + other.value

  operator ++
    this.value: this.value + 1

See Q98 for record copy, merge, replace. See Q240 for pure arithmetic operators. See Q238 for the complete operator set. See Q273 for how purity prevents mutation-based security attacks.

defines module qa.operators.mutation

  defines class

    Counter
      count <- Integer()

      Counter() as pure
        -> initial as Integer
        this.count :=: initial

      // Pure: creates NEW value
      operator + as pure
        -> other as Counter
        <- rtn as Counter: Counter(count + other.count)

      // Mutation: modifies IN PLACE
      operator +=
        -> other as Counter
        count += other.count

      operator ++
        count++

      operator --
        count--

      operator :=:
        -> source as Counter
        count :=: source.count

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

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

  defines program

    MutationDemo()
      stdout <- Stdout()

      // === PURE + CREATES NEW VALUE ===

      a <- Counter(10)
      b <- Counter(5)
      c <- a + b
      stdout.println(`a after +: ${a}`)
      stdout.println(`c (new value): ${c}`)

      // === MUTATION += MODIFIES IN PLACE ===

      a += b
      stdout.println(`a after +=: ${a}`)

      // === INCREMENT AND DECREMENT ===

      counter <- Counter(0)
      counter++
      counter++
      counter++
      stdout.println(`After 3 increments: ${counter}`)
      counter--
      stdout.println(`After decrement: ${counter}`)

      // === COPY ===

      original <- Counter(42)
      copied <- Counter(0)
      copied :=: original
      stdout.println(`Copied: ${copied}`)

Java: += is syntactic sugar for x = x + op, ++ is increment, no copy/merge/replace operators. Python: __iadd__ for +=, no ++ operator, copy via copy.copy(). Rust: AddAssign trait for +=, no ++ operator, Clone trait for copy. Go: += is built-in, ++ is statement-only (not expression), no custom operators. Kotlin: plusAssign for +=, ++ via inc(), no merge/replace. JavaScript: += and ++ are built-in, no custom operator definitions. EK9: +=, -=, *=, /= (compound assignment), ++, -- (increment/decrement), :=: (copy), :~: (merge), :^: (replace), | (pipe), all CANNOT be pure, MUST NOT return value.

assignment compound copy decrement effect expression immutable increment merge modify mutate mutation operator pipe place pure replace side side-effect

operator += as pure
        -> other as Counter
        count += other.count

Explanation: Mutation operators (+=, -=, :=:, :~:, :^:, ++, --) modify the object in place and CANNOT be marked pure. Adding 'as pure' to a mutation operator triggers E07510. See ek9 -h E07510 for details.


Question: How do conversion and introspection operators work in EK9?

How does string interpolation use the $ operator in EK9?

What do the prefix and suffix operators do in EK9?

How do I convert my type to String or JSON in EK9?

Answer:
EK9 provides conversion and introspection operators that extract information from objects. All are pure, take 0 arguments, and return strictly enforced types.

STRING CONVERSION ($)
The $ operator returns a String representation. It is called implicitly in string interpolation.
  operator $ as pure
    <- rtn as String: ...
When you write `${myObj}`, the compiler calls the $ operator.

JSON CONVERSION ($$)
The $$ operator returns a JSON representation as a String. The JSON type has this built-in.
  operator $$ as pure
    <- rtn as String: ...

HASHCODE (#?)
The #? operator returns an Integer hash code for use in Dict keys and equality checking.
  operator #? as pure
    <- rtn as Integer: ...

ISSET (?)
The ? operator returns Boolean indicating whether the object has a meaningful, usable value.
  operator ? as pure
    <- rtn as Boolean: ...
This is central to EK9's tri-state semantics (absent, present-unset, present-set).

PROMOTE (#^)
The #^ operator returns a DIFFERENT type (type promotion). It MUST return a type different from self. The compiler uses this for automatic type widening.
  operator #^ as pure
    <- rtn as Float: ...

PREFIX AND SUFFIX (#< and #>)
The #< operator extracts a prefix, the #> operator extracts a suffix. On String, #< returns the first character and #> returns the last character.
  first <- #< fullName
  last <- #> fullName

EMPTY AND LENGTH
The 'empty' operator returns Boolean (true if the object is empty). The 'length' operator returns Integer (size/count).
  if empty collection
    stdout.println("Nothing here")
  size <- length myList

CLOSE
The 'close' operator performs resource cleanup. It is pure and returns nothing.

See Q25 for the promote operator in detail. See Q96 for class operators. See Q116 for default operator generation. See Q238 for the complete operator set. See Q245 for a complete custom type example.

defines module qa.operators.conversion

  defines class

    Money
      amount <- Float()
      currency <- String()

      Money() as pure
        ->
          amount as Float
          currency as String
        this.amount :=: amount
        this.currency :=: currency

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

      operator #? as pure
        <- rtn as Integer: #?amount + #?currency

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

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

      operator <=> as pure
        -> other as Money
        <- rtn as Integer: amount <=> other.amount

      operator == as pure
        -> other as Money
        <- rtn as Boolean: amount == other.amount and currency == other.currency

  defines program

    ConversionDemo()
      stdout <- Stdout()

      price <- Money(19.99, "USD")

      // === STRING CONVERSION $ (implicit in interpolation) ===

      stdout.println(`Price: ${price}`)

      // === ISSET ? ===

      stdout.println(`Is set: ${price?}`)

      // === HASHCODE #? ===

      hash <- #? price
      stdout.println(`Hash: ${hash}`)

      // === PROMOTE #^ ===

      floatValue as Float: price
      stdout.println(`Promoted to Float: ${floatValue}`)

      // === PREFIX AND SUFFIX ON STRING ===

      name <- "Hello"
      first <- #< name
      last <- #> name
      stdout.println(`Prefix: ${first}`)
      stdout.println(`Suffix: ${last}`)

      // === LENGTH ===

      text <- "Hello World"
      size <- length text
      stdout.println(`Length: ${size}`)

Java: toString() for String, no built-in JSON, hashCode() for hash, equals() check not isSet, no prefix/suffix operators. Python: __str__() and __repr__() for String, json.dumps() for JSON, __hash__() for hash, __bool__() for truthiness. Rust: Display trait for String, serde for JSON, Hash trait for hash, no isSet concept. Go: String() method by convention, json.Marshal() for JSON, no hash interface. Kotlin: toString() for String, no built-in JSON operator, hashCode() for hash. JavaScript: toString() for String, JSON.stringify() for JSON, no custom hash. EK9: $ returns String, $$ returns JSON, #? returns Integer hashcode, ? returns Boolean isSet, #^ returns promoted type, #< prefix, #> suffix, empty returns Boolean, length returns Integer.

backtick close conversion empty expression hashcode interpolation introspection isset json length operator prefix promote string suffix

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

Explanation: Conversion operators like $, #?, and ? must be marked 'as pure' because they extract information without side effects. Omitting 'as pure' triggers E07500. See ek9 -h E07500 for details.

Explanation: The #? (hashcode) operator must return Integer. Returning String instead triggers E07550. See ek9 -h E07550 for details.


Question: How do coalescing operators handle unset values in EK9?

What is the null coalescing operator in EK9?

How does the Elvis operator work in EK9?

How do I pick the lesser or greater of two possibly unset values?

Answer:
EK9 provides coalescing operators that safely handle unset values. These are expression-level operators (not implementable on your types) that the compiler handles directly.

NULL COALESCING (??)
The ?? operator checks if the left operand exists in memory. If the left is invalid (absent), it returns the right.
  result <- possiblyAbsent ?? fallback
This is a memory-level check, not an isSet check.

ELVIS OPERATOR (?:)
The ?: operator checks if the left is both present AND set (via isSet). If the left is absent or unset, it returns the right.
  result <- possiblyUnset ?: fallback
This is more thorough than ?? because it also checks the isSet state.

COALESCING COMPARISONS
These operators compare two values, handling the case where one or both might be unset:
  <? (lesser coalescing): returns the lesser value, or whichever is valid
  <=? (lesser-or-equal coalescing): returns the lesser-or-equal value
  >? (greater coalescing): returns the greater value, or whichever is valid
  >=? (greater-or-equal coalescing): returns the greater-or-equal value

Priority rules: If left is invalid, return right. If right is invalid, return left. If both valid, compare and return the winner.
  minimum <- a <? b
  maximum <- a >? b

NOTE: :=? is NOT a coalescing operator. It is an ASSIGNMENT operator that assigns only if the target is unset. See Q1152 for :=? details. See Q1226 for <? vs :=? contrast.

REPLACING VERBOSE IF/ELSE
Without coalescing:
  if a?
    if b?
      if a < b
        result: a
      else
        result: b
    else
      result: a
  else
    result: b

With coalescing:
  result <- a <? b
One line replaces a nested if/else tree.

See Q75 for guard switch. See Q161 for Dict safe access. See Q168 for fallback values. See Q239 for comparison operators. See Q238 for the complete operator set.

defines module qa.operators.coalescing

  defines program

    CoalescingDemo()
      stdout <- Stdout()

      // === ELVIS OPERATOR ?: ===

      name <- String()
      displayName <- name ?: "Anonymous"
      stdout.println(`Display name: ${displayName}`)

      // === COALESCING COMPARISONS ===

      a <- 10
      b <- 20
      minimum <- a <? b
      maximum <- a >? b
      stdout.println(`Minimum: ${minimum}`)
      stdout.println(`Maximum: ${maximum}`)

      // === CHAINED COALESCING ===

      primary <- String()
      secondary <- String()
      fallback <- "last-resort"
      chosen <- primary ?: secondary ?: fallback
      stdout.println(`Chosen: ${chosen}`)

Java: ternary x != null ? x : default, no null coalescing operator, Optional.orElse(). Python: x if x is not None else default, or shorthand x or default (falsy gotcha). Rust: unwrap_or(), unwrap_or_else(), no null concept. Go: if x == nil then default, no ternary or coalescing. Kotlin: ?: elvis operator for null, no coalescing comparisons. Swift: ?? nil coalescing (same operator as EK9!), no equivalent of ?: isSet check or <? >? coalescing comparisons. JavaScript: ?? null coalescing (ES2020), || logical or fallback. EK9: ?? (memory check), ?: (isSet check), <? <=? >? >=? (coalescing comparisons), :=? (conditional assignment).

absent assignment coalescing conditional default elvis expression fallback greater isset lesser migrate null operator safe swift unset

coalescing
null coalescing
default value operator

Explanation: Coalescing operators require both operands to be the same type. Mixing String and Integer triggers E04020 — incompatible types in coalescing expression. See ek9 -h E04020 for details.