Question: What is the structure of an EK9 source file?

What keywords do I need for an EK9 program?

Why do I need defines module and defines program?

Answer:
Every EK9 file starts with '#!ek9' then 'defines module <name>'. Inside the module you place sections like 'defines program', 'defines function', 'defines class', 'defines record'. A runnable program needs 'defines program' with a named entry point. Indentation (2 spaces) defines scope — no braces or semicolons.

See Q1 for minimal program. See Q5 for file structure. See Q17 for entry points.

defines module qa.getting.started.structure

  defines function

    greet() as pure
      -> who as String
      <- rtn as String: `Hello, ${who}!`

  defines program

    ShowStructure()
      stdout <- Stdout()
      message <- greet("EK9 developer")
      stdout.println(message)

Java: package + class + main method. Python: just code. Go: package main + func main. EK9: defines module + defines program + named entry.

defines file indent module program skeleton structure

file structure
defines module
defines program


Question: How do I define a record with fields in EK9?

How are records different from classes in EK9?

Answer:
If you are coming from Java, think Java record. From Rust, think struct. From Kotlin, think data class.

EK9 records are pure data containers. Their fields are PUBLIC (unlike class fields which are always private). They cannot have regular methods — only constructors and operators.

IMPORTANT RULES
- Every field needs a default value: 'xPos <- 0.0' not 'xPos as Float'
- Add 'operator <=>' (comparator) then 'default operator' to auto-generate ==, <>, $, #?
- Use ':=:' in constructors to copy parameter values to fields

See Q97 for class vs record differences. See Q98 for record operators. Use 'ek9 -h record' for full syntax.

defines module qa.classes.record.basics

  defines record

    Coordinate
      xPos <- 0.0
      yPos <- 0.0

      Coordinate()
        ->
          initialX as Float
          initialY as Float
        xPos :=: initialX
        yPos :=: initialY

      operator <=> as pure
        -> arg0 as Coordinate
        <- rtn as Integer: xPos <=> arg0.xPos

      default operator

  defines program

    RecordDemo()
      stdout <- Stdout()

      origin <- Coordinate(0.0, 0.0)
      point <- Coordinate(3.0, 4.0)

      if point?
        stdout.println("Point: " + $point)
        stdout.println(`Equal: ${origin == point}`)

Java: record keyword (Java 16+). Kotlin: data class. Python: dataclass. Rust: struct. EK9: defines record with public fields and auto-generated operators.

data default field operator public record struct

record definition
record fields
record vs class

Oracle can generate a record with public fields, constructors, and default operator declarations.


Question: What does the '?' operator do in EK9 and what does 'default operator ?' mean?

What is the difference between set and unset in EK9?

How do I check if a variable has a value in EK9?

Answer:
Forget null. EK9 has no null. Instead it has three states: absent, unset, and set.

The '?' operator is the isSet check — it returns true when a value has meaningful data:
  name <- "Steve"    // name? returns true (set)
  empty <- String()   // empty? returns false (unset)
  if name?
    stdout.println(name)  // safe — we checked first

For your own classes, add 'default operator ?' at the end of the class. The compiler generates the check from your fields. Or write 'override operator ?' for custom logic.

The ':=?' operator assigns only if the target is currently unset — perfect for defaults:
  config :=? "fallback"  // only assigns if config is unset

This is NOT null coalescing. There is no null. See Q29 for tri-state details. See Q84 for Optional.

defines module qa.operators.isset

  defines class

    UserProfile
      userName <- String()
      email <- String()

      UserProfile()
        ->
          name as String
          addr as String
        userName :=: name
        email :=: addr

      describe()
        <- rtn as String: `${userName} <${email}>`

      default operator ?

  defines program

    IsSetDemo()
      stdout <- Stdout()

      //Set profile — '?' returns true
      profile <- UserProfile("Steve", "steve@example.com")
      if profile?
        stdout.println(profile.describe())

      //Unset string — '?' returns false
      unsetName <- String()
      if not unsetName?
        stdout.println("Name is unset")

      //Guard assignment — only assigns if unset
      unsetName :=? "default name"
      stdout.println(unsetName)

EK9 replaces null checks from other languages with the ? suffix operator. Append ? after any variable to check if it holds a value.

check default guard isset operator question set tristate unset

isSet operator
tri-state
default operator ?


Question: How does EK9 handle return values without a return statement?

How do I return a value from a function in EK9?

What is the '<-' return declaration in EK9?

Answer:
Look at this function — notice there is no 'return' keyword anywhere:

  maxOfTwo() as pure
    -> firstNum as Integer, secondNum as Integer
    <- rtn as Integer: firstNum
    if secondNum > firstNum
      rtn: secondNum

The '<- rtn as Integer: firstNum' line DECLARES the return variable and gives it an initial value. The compiler then checks that ALL possible code paths leave 'rtn' with a valid value. If any path could leave it uninitialised, the code won't compile.

This design eliminates an entire category of bugs: forgotten returns, early returns bypassing cleanup, and unreachable code after return. The 'return' keyword simply does not exist in EK9's grammar.

See Q146 for the design rationale. See Q144 for no break/continue/return.

defines module qa.classes.no.return

  defines function

    maxOfTwo() as pure
      ->
        firstNum as Integer
        secondNum as Integer
      <- rtn as Integer: firstNum

      //All paths must set rtn — compiler enforces this
      if secondNum > firstNum
        rtn: secondNum

  defines class

    Formatter
      prefix <- "["
      suffix <- "]"

      default Formatter()

      Formatter()
        ->
          p as String
          s as String
        prefix :=: p
        suffix :=: s

      format()
        -> text as String
        <- rtn as String: `${prefix}${text}${suffix}`

      default operator ?

  defines program

    ReturnDemo()
      stdout <- Stdout()

      bigger <- maxOfTwo(10, 20)
      stdout.println(`Max: ${bigger}`)

      formatter <- Formatter("<", ">")
      if formatter?
        stdout.println(formatter.format("hello"))

Java: return keyword required. Python: return keyword. Go: return keyword. EK9: '<-' declares return variable, no return statement exists.

arrow declaration flow initialise no-return return variable

return declaration
no return statement
<- syntax


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

EK9 has too many assignment operators — which one do I use?

Explain the arrow and colon operators in EK9

Answer:
Three operators, three purposes:

  name <- "Steve"        // DECLARE: creates new variable, type inferred
  name := "Different"    // ASSIGN: changes existing variable
  other :=: name          // COPY: deep copies value from name into other

Think of it this way:
- '<-' is for BIRTH — the variable didn't exist before this line
- ':=' is for CHANGE — the variable already exists, you're updating it
- ':=:' is for CLONING — copy the contents, not the reference

There are also two conditional variants:
  config :=? "default"   // assign only if config is currently UNSET
  if guard ?= getValue()  // guard: execute block only if result is SET

Common mistake from Java/Python developers: using '<-' to reassign. This creates a NEW variable that shadows the old one. Use ':=' to change an existing variable.

See Q22 for variable declaration. See Q29 for unset variables. See Q75 for guard expressions.

defines module qa.getting.started.three.operators

  defines program

    ThreeOperators()
      stdout <- Stdout()

      //DECLARE with <-
      counter <- 0
      stdout.println(`Created counter: ${counter}`)

      //ASSIGN with :=
      counter := 10
      stdout.println(`Changed counter: ${counter}`)

      //COPY with :=:
      backup <- 0
      backup :=: counter
      counter := 99
      stdout.println(`Counter now: ${counter}`)
      stdout.println(`Backup is still: ${backup}`)

      //CONDITIONAL with :=?
      serverName <- String()
      serverName :=? "localhost"
      stdout.println(`Server: ${serverName}`)

Java: single '=' for everything. Python: single '=' for everything. Rust: 'let' for declare, '=' for assign. EK9: '<-' declare, ':=' assign, ':=:' deep copy — each has distinct semantics.

arrow assign colon copy declare difference operator variable

<- operator
:= operator
:=: operator
assignment


Question: What features from other languages does EK9 deliberately exclude?

What can't I do in EK9 that I can in Java or Python?

Why is EK9 missing break continue and return?

Answer:
EK9 DELIBERATELY EXCLUDES these features. They are not missing — they were removed based on decades of production bug evidence:

NO break — use 'head N' in streams or guard expressions instead
NO continue — use 'filter by' in streams instead
NO return — declare the return variable with '<-', compiler ensures all paths set it
NO null — EK9 uses tri-state (absent/unset/set) with the '?' operator
NO type casts — use promotion '#^' or explicit conversion
NO switch fallthrough — each case is independent, use comma-separated values
NO public keyword — methods are public by default, 'public' is redundant and rejected
NO new keyword — constructors are called directly: 'MyClass()' not 'new MyClass()'
NO semicolons — indentation defines scope
NO braces — indentation defines scope

The grammar literally does not contain these keywords. You cannot write 'break' — the parser will reject it.

See Q144 for no break/continue/return. See Q145 for replacements. See Q29 for no null.

defines module qa.getting.started.exclusions

  defines constant

    TEEN_AGE <- 13
    ADULT_AGE <- 18

  defines function

    classifyAge() as pure
      -> years as Integer
      <- rtn as String: "unknown"

      //No 'return' — set rtn on each path
      if years < TEEN_AGE
        rtn: "child"
      else if years < ADULT_AGE
        rtn: "teenager"
      else
        rtn: "adult"

  defines program

    ExclusionsDemo()
      stdout <- Stdout()

      //No 'new' keyword — just call the constructor
      names <- List() of String
      names += "Steve"
      names += "Limb"
      stdout.println(`Names: ${length names}`)

      //No 'null' — use ? to check
      unsetName <- String()
      if not unsetName?
        stdout.println("Name is unset, not null")

      stdout.println(classifyAge(25))

All languages: these features exist everywhere else. EK9: removed from grammar entirely. Not deprecated, not warned — gone.

break cast continue design exclude fallthrough missing null return

excluded features
design decisions
no break
no return
no null


Question: I keep getting errors mixing up <- and := in EK9. What is the rule?

When do I use the arrow <- vs the colon-equals := in EK9?

My variable already exists but <- creates a new one — help!

Answer:
Simple rule: '<-' is for the FIRST TIME. ':=' is for EVERY TIME AFTER.

  score <- 100      // first time: score is born here
  score := 200      // after: score already exists, update it
  score <- 300      // WRONG: creates a NEW variable, shadows the old one

If you use '<-' on a variable that already exists in an outer scope, you create a NEW local variable with the same name. The outer variable is unchanged. This is almost always a bug.

The compiler may warn you with E11031 if the variable name is too generic, or E08090 if the shadowed variable is never used after the shadowing declaration.

For function returns, '<-' declares the return variable:
  calculateTotal() as pure
    -> items as List of Integer
    <- total as Integer: 0    // '<-' declares return
    for item in items
      total += item            // ':=' implicit via +=

See Q22 for declaration rules. See Q879 for all three assignment operators.

defines module qa.getting.started.declare.vs.assign

  defines function

    sumList()
      -> numbers as List of Integer
      <- total as Integer: 0

      for item in numbers
        total += item

  defines program

    DeclareVsAssign()
      stdout <- Stdout()

      //Correct: declare then assign
      message <- "hello"
      message := "updated"
      stdout.println(message)

      //Demonstrate with a list
      scores <- List() of Integer
      scores += 10
      scores += 20
      scores += 30

      result <- sumList(scores)
      stdout.println(`Total: ${result}`)

Python: rebinding with = is always reassignment. Java: type declaration vs assignment. EK9: <- declares, := reassigns — using <- twice creates shadowing.

arrow assign declare error rebind scope shadow variable


Question: Why can't I access class fields from outside the class in EK9?

What is the visibility of fields in EK9 classes vs records?

Answer:
ALL class fields are private. Always. There is no 'public' or 'protected' modifier for fields in EK9's grammar — it does not exist.

To expose state, write a method:
  describe()
    <- rtn as String: `${userName} <${email}>`

Record fields are the opposite — ALL public. That is the key difference:
- CLASS: fields private, has methods — for objects with behaviour
- RECORD: fields public, no methods — for pure data

You cannot write 'public name as String' on a class. You cannot write a method on a record. The compiler enforces this separation.

To copy field values, use ':=:' in a constructor:
  MyClass()
    -> initialName as String
    name :=: initialName

See Q97 for class vs record. See Q95 for field visibility. See Q876 for record basics.

defines module qa.classes.fields.private

  defines record

    Address
      street <- String()
      city <- String()

      Address()
        ->
          s as String
          c as String
        street :=: s
        city :=: c

      operator <=> as pure
        -> arg0 as Address
        <- rtn as Integer: street <=> arg0.street

      default operator

  defines class

    Person
      personName <- String()
      homeAddress <- Address("", "")

      Person()
        ->
          pName as String
          addr as Address
        personName :=: pName
        homeAddress :=: addr

      //Method exposes state — fields are private
      location()
        <- rtn as String: `${personName} lives in ${homeAddress.city}`

      default operator ?

  defines program

    FieldVisibility()
      stdout <- Stdout()

      //Record fields are public
      addr <- Address("123 Main", "Springfield")
      stdout.println(addr.city)

      //Class fields accessed via methods
      person <- Person("Steve", addr)
      if person?
        stdout.println(person.location())

Java: public/private/protected on fields. Python: convention-only _prefix. Kotlin: var/val with getters. EK9: class fields ALWAYS private, record fields ALWAYS public — no modifier needed or allowed.

access class field method private public record visibility

field visibility
private fields
class vs record


Question: How does EK9 handle the concept of 'no value' without null?

EK9 has no null — so what do I use instead?

How do absent, unset, and set work in EK9?

Answer:
There is no null in EK9. Not deprecated, not hidden — it does not exist in the language.

Instead, every value has three possible states:

1. ABSENT — the variable does not exist (e.g., missing Dict key)
2. UNSET — the variable exists but has no meaningful data yet
3. SET — the variable has valid, usable data

You create UNSET values with the type constructor and no arguments:
  name <- String()     // exists but unset — name? returns false
  count <- Integer()   // exists but unset — count? returns false

You create SET values by providing data:
  name <- "Steve"      // set — name? returns true
  count <- 42          // set — count? returns true

Collections are different — they are SET even when empty:
  items <- List() of String   // SET (empty list is a valid list)

Always check with '?' before using a value that might be unset:
  if name?
    stdout.println(name)    // safe

See Q29 for full tri-state details. See Q84 for Optional type. See Q877 for the ? operator.

defines module qa.getting.started.tristate

  defines constant

    KNOWN_USER_ID <- 42

  defines function

    findUser() as pure
      -> userId as Integer
      <- rtn as String: String()

      //Returns unset String if user not found
      if userId == KNOWN_USER_ID
        rtn: "Steve"

  defines program

    TriStateDemo()
      stdout <- Stdout()

      //Set value
      known <- findUser(KNOWN_USER_ID)
      if known?
        stdout.println(`Found: ${known}`)

      //Unset value
      unknown <- findUser(99)
      if not unknown?
        stdout.println("User not found — but no null, no exception")

      //Unset with guard assignment
      fallback <- String()
      fallback :=? "anonymous"
      stdout.println(`Fallback: ${fallback}`)

Java: null + NullPointerException. Python: None. Rust: Option<T>. Kotlin: nullable types. EK9: tri-state (absent/unset/set) with ? operator — NullPointerException is structurally impossible.

absent isset null optional safe set tristate unset


Question: Why does EK9 report E06330 when using a function with the wrong type in a stream pipeline?

What triggers E06330 INCOMPATIBLE_TYPE_ARGUMENTS in streams?

Why must stream function parameter types match the stream element type?

What happens when a uniq function takes the wrong type?

Answer:
Stream pipeline operations require functions whose parameter types match the current stream element type. When you use `uniq` with a function that accepts a different type, the compiler raises E06330.

TYPE MATCHING IN PIPELINES
Each pipeline stage knows the current stream type. A function used with uniq must accept the stream element type:
  cat [1, 2, 3] | uniq hashInteger | ...       // Correct: hashInteger takes Integer
  cat [1, 2, 3] | uniq hashString | ...        // ERROR: hashString takes String

WHY ENFORCED
A type mismatch in a pipeline would produce a runtime ClassCastException in Java. EK9 catches this at compile time by validating that each function's parameter type matches the stream type at that stage.

See Q235 for stream operations reference. See Q237 for streams vs loops. See Q857 for split predicate requirements.

defines module qa.streams.typemismatch

  defines function

    <?-
      Hash function for Integer — matches stream of Integer.
    -?>
    hashInteger() as pure
      -> item as Integer
      <- rtn as Integer: #? item

    <?-
      Hash function for String — does NOT match stream of Integer.
      Exists to demonstrate the type mismatch.
    -?>
    hashString() as pure
      -> item as String
      <- rtn as Integer: #? item

  defines program

    StreamTypeMismatchDemo()
      stdout <- Stdout()

      collection <- cat [1, 2, 3, 2, 1] | uniq hashInteger | collect as List of Integer

      stdout.println(`Unique count: ${length collection}`)

Java: Stream type mismatches produce verbose generic errors. Python: map/filter type mismatches caught at runtime only. Go: strict typing on range functions. Rust: iterator adaptor trait bounds checked at compile time. EK9: per-operation type validation with clear error messages.

E06330 argument function incompatible mismatch parameter pipeline stream type uniq

Explanation: The function hashString takes a String parameter, but the stream contains Integer elements. Use a function whose parameter type matches the stream element type. See ek9 -h E06330 for details.


Question: Why must a default constructor be private when a class has uninitialised fields?

What triggers E07175 DEFAULT_CONSTRUCTOR_MUST_BE_PRIVATE?

Why does EK9 require private default constructors with uninitialised properties?

How do I prevent creation of objects with uninitialised fields?

Answer:
When a class has properties that are not initialised at declaration and the developer provides explicit constructors to initialise them, the default no-argument constructor must be made private. Otherwise callers could bypass the developer constructors and create objects with uninitialised properties.

THE PROBLEM
Without `default private`, the no-arg constructor is public:
  Greeter()          // Creates Greeter with uninitialised 'name'
  Greeter("Steve")   // Creates Greeter with name = "Steve"
Both are callable — the first leaves 'name' uninitialised.

THE FIX
Option 1: Make default constructor private:
  default private Greeter()

Option 2: Initialise all fields at declaration:
  name <- ""   // Always initialised, no E07175

See Q580 for constructor delegation. See Q582 for constructor chaining. See Q584 for abstract constructor patterns.

defines module qa.constructordelegation.defaultprivate

  defines class

    <?-
      Greeter with initialised name property.
      Because name is initialised at declaration, there is
      no E07175 — the default constructor is safe.
    -?>
    Greeter
      name <- ""

      Greeter()
        -> inName as String
        name: inName

      greet()
        <- rtn as String: `Hello ${name}`

      default operator ?

  defines program

    DefaultConstructorPrivateDemo()
      stdout <- Stdout()

      greeter <- Greeter("Steve")
      stdout.println(greeter.greet())

Java: adding any constructor suppresses the default constructor. C#: same as Java. Python: __init__ replaces default. Rust: no default constructor unless Default trait implemented. EK9: default constructor always exists but must be made private when uninitialised fields present.

E07175 class constructor default field initialisation private property safety uninitialised

Explanation: When a class has uninitialised properties and developer constructors but no private default constructor, callers can create objects with uninitialised fields. Either initialise all fields at declaration or add 'default private ClassName()'. See ek9 -h E07175 for details.


Question: Why must the $$ operator always return JSON in EK9?

Why does the $$ operator require a JSON return type?

How do I implement the JSON serialisation operator?

Answer:
The $$ operator is the JSON serialisation operator. It MUST return JSON — returning any other type (String, Integer, etc.) triggers E07580.

OPERATOR CONTRACT
Each accessor operator has a strict return type:
  $ — MUST return String (toString equivalent)
  $$ — MUST return JSON (serialisation)
  #? — MUST return Integer (hashcode)
  ? — MUST return Boolean (isSet)

CORRECT PATTERN
  operator $$ as pure
    <- rtn as JSON: JSON()

INCORRECT PATTERN
  operator $$ as pure
    <- rtn as String: ...   // ERROR: E07580

WHY ENFORCED
JSON serialisation must produce a JSON value, not a String representation. The `$` operator already handles String conversion. Having `$$` return String would be redundant and semantically wrong.

See Q238 for operator overview. See Q242 for conversion operators.

defines module qa.operators.jsonreturn

  defines class

    <?-
      Configuration class with JSON serialisation.
    -?>
    AppConfig
      appName <- "MyApp"
      appVersion <- "1.0.0"

      getName()
        <- rtn as String: String(appName)

      getVersion()
        <- rtn as String: String(appVersion)

      operator $ as pure
        <- rtn as String: `${appName} v${appVersion}`

      operator $$ as pure
        <- rtn as JSON: JSON()

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

  defines program

    JsonOperatorReturnDemo()
      stdout <- Stdout()

      config <- AppConfig()
      stdout.println(`Config: ${config}`)

Java: no operator overloading, toString() returns String. Python: __repr__ and __str__ both return str, no JSON operator. Rust: Serialize trait via serde. Go: json.Marshaler returns []byte. EK9: separate $ (String) and $$ (JSON) operators with enforced return types.

E07580 JSON accessor class contract enforce operator return serialisation type

Explanation: The $$ operator must return JSON, not String. Use `<- rtn as JSON: JSON()` and populate the JSON object. The $ operator handles String conversion. See ek9 -h E07580 for details.


Question: Why can EK9 programs only return Integer?

What triggers E07590 PROGRAM_CAN_ONLY_RETURN_INTEGER?

Why can't my program return a String or Boolean?

Answer:
EK9 programs can only return Integer (or nothing). The return value becomes the process exit code, which the operating system uses to determine success or failure.

EXIT CODE CONVENTION
  0 — success
  1 — general error
  2 — usage error
  Non-zero — failure

CORRECT PATTERNS
  MyProgram()
    <- exitCode as Integer: 0     // Returns exit code

  MyProgram()
    ... body ...                   // No return (implicit 0)

INCORRECT PATTERNS
  MyProgram()
    <- message as String: "done"  // ERROR: E07590
  MyProgram()
    <- success as Boolean: true   // ERROR: E07590

WHY ENFORCED
The OS can only receive integer exit codes. Returning Float, Boolean, or custom types has no meaning at the OS level. EK9 makes this explicit rather than silently discarding non-integer values.

See Q871 for hello world. See Q872 for program variables.

defines module qa.syntaxrules.programreturn

  defines program

    ProgramReturnIntegerDemo()
      <- exitCode as Integer: 0
      stdout <- Stdout()

      stdout.println("Program running successfully")
      stdout.println(`Exit code will be: ${exitCode}`)

Java: main returns void, exit code via System.exit(int). C: main returns int (exit code). Python: sys.exit(int) or implicit 0. Go: os.Exit(int). Rust: main returns () or Result. EK9: program return type restricted to Integer at compile time.

E07590 Integer code entry exit main point process program return

Explanation: Programs can only return Integer because the return value is the process exit code. Float, Boolean, String, and custom types cannot be used as exit codes. See ek9 -h E07590 for details.


Question: What arguments can an EK9 program accept?

What triggers E07610 PROGRAM_ARGUMENTS_INAPPROPRIATE?

Why can't I mix List of String with typed parameters?

How do EK9 program argument styles work?

Answer:
EK9 programs support two argument styles, but you cannot mix them in a single program.

STYLE 1: C-STYLE LIST
  MyProgram()
    -> argv as List of String
    // Parse arguments manually

STYLE 2: TYPED PARAMETERS
  MyProgram()
    ->
      name as String
      count as Integer
    // EK9 parses from command-line strings automatically

CANNOT MIX
  MyProgram()
    ->
      count as Integer
      argv as List of String    // ERROR: E07610
Mixing typed parameters with List of String is ambiguous.

ALLOWED TYPED PARAMETERS
Only types that can be parsed from command-line strings: String, Integer, Float, Boolean, Date, DateTime, Duration, Millisecond, Colour, Dimension, Money, Path, RegularExpression.

See Q871 for hello world. See Q888 for program return type.

defines module qa.syntaxrules.programarguments

  defines program

    <?-
      Program using typed arguments.
      EK9 automatically parses command-line strings to the declared types.
    -?>
    ProgramArgumentsDemo()
      ->
        userName as String
        repeatCount as Integer

      stdout <- Stdout()

      for i in 1 ... repeatCount
        stdout.println(`${i}: Hello, ${userName}`)

Java: String[] only, all parsing manual. Python: sys.argv is list of strings, argparse for typed. Go: os.Args is []string, flag for typed. Rust: std::env::args(), clap for typed. EK9: two styles — C-style List of String or compiler-parsed typed parameters, but not both.

E07610 List String argument command line mix parameter program typed

Explanation: You cannot mix typed parameters with List of String in a program. Choose one style: either a single List of String parameter, or typed parameters that EK9 parses automatically. See ek9 -h E07610 for details.


Question: Why must service operations return HTTPResponse in EK9?

What triggers E07750 SERVICE_INCOMPATIBLE_RETURN_TYPE?

Why can't my service method return String or Boolean?

What return type do EK9 service methods require?

Answer:
Every service method must return HTTPResponse. Returning Boolean, String, Integer, or any other type triggers E07750.

WHY HTTPRESPONSE
HTTP responses carry status codes, headers, content type, and body. Only HTTPResponse expresses all of these:
  status() — 200, 404, 500, etc.
  content() — response body
  contentType() — MIME type
  cacheControl() — caching directives
  contentLanguage() — response language

CORRECT PATTERN
  healthCheck() as GET for :/status
    <- response as HTTPResponse: ...

INCORRECT PATTERN
  healthCheck() as GET for :/status
    <- response <- true            // ERROR: E07750

See Q659 for HTTPResponse details. See Q657 for URI mapping. See Q660 for CRUD patterns.

defines module qa.webdeep.servicereturntype

  defines service

    <?-
      API service with correct HTTPResponse return types.
    -?>
    StatusApi :/api open

      healthCheck() as GET for :/health
        <- response as HTTPResponse: () with trait of HTTPResponse
          override content()
            <- rtn as String: `{"status": "healthy"}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: "application/json"
          override cacheControl() as pure
            <- rtn as String: "no-cache"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    StatusApp
      register StatusApi()

  defines program

    ServiceReturnTypeDemo()
      stdout <- Stdout()
      stdout.println("Service methods must return HTTPResponse")

Java: Spring returns any type or ResponseEntity. Python: Flask returns (body, status). Go: writes to http.ResponseWriter. Rust: HttpResponse value. EK9: mandates HTTPResponse trait, explicit status/content/type.

E07750 GET HTTPResponse POST content incompatible return service status web

Explanation: Service methods must return HTTPResponse, not Boolean. The HTTP protocol requires status codes, headers, and content type, which only HTTPResponse can express. See ek9 -h E07750 for details.


Question: Why must I sort before grouping in an EK9 stream pipeline?

What triggers E10040 SORT_REQUIRED_BEFORE_GROUP?

Why does group need a preceding sort in streams?

How does change-detection grouping work in EK9?

Answer:
EK9 stream group uses change-detection: it creates a new group when the hashcode changes between adjacent items. Without sorted input, items with the same key scattered throughout the stream produce multiple incomplete groups.

CORRECT PATTERN
  cat items | sort | group > collection
Sorting ensures identical items are adjacent, so group detects each run correctly.

INCORRECT PATTERN
  cat items | group > collection        // ERROR: E10040
Without sort, group may split identical items into separate groups.

TYPE-CHANGING INVALIDATES SORT
  cat items | sort | map with transform | group > collection  // ERROR: E10040
The map changes the stream type, making the preceding sort irrelevant. Add another sort after the map.

FILTER PRESERVES SORT
  cat items | sort | filter by predicate | group > collection  // OK
Filter removes items without changing type, so sort remains valid.

See Q235 for stream operations. See Q237 for streams vs loops.

defines module qa.streams.sortbeforegroup

  defines program

    SortBeforeGroupDemo()
      stdout <- Stdout()

      grouped <- List() of List of String
      cat ["A", "B", "A", "C", "B"] | sort | group > grouped

      stdout.println(`Groups found: ${length grouped}`)

Java: Collectors.groupingBy uses HashMap, order irrelevant. Python: itertools.groupby requires sorted input but no compile-time check. Go: no built-in group. Rust: group_by requires sorted, no compile check. EK9: compiler enforces sort before group at compile time.

E10040 adjacent change collect detection group order pipeline sort stream

cat ["A", "B", "A", "C", "B"] | group > grouped

Explanation: Group uses change-detection on adjacent items. Without sort, identical items scattered in the stream produce incomplete groups. Add sort before group. See ek9 -h E10040 for details.


Question: What is the maximum number of DI registrations in an EK9 application block?

What triggers E11042 EXCESSIVE_APPLICATION_REGISTRATIONS?

How many register statements can an application have?

Why does EK9 limit application registrations to twelve?

Answer:
An application block can have at most 12 registrations. More than 12 triggers E11042, indicating the application is trying to be a monolith.

WHY TWELVE
Research in component-based software engineering shows that systems with focused, modular composition are more maintainable. A large registration block often means the application mixes unrelated concerns.

SPLIT INTO SUB-APPLICATIONS
  defines application
    InfrastructureApp
      register DbService() as AbstractDb
      register CacheService() as AbstractCache

  defines application
    BusinessApp with application of InfrastructureApp
      register UserService() as AbstractUser
      register OrderService() as AbstractOrder

USE COMPOSITION
The 'with application of' clause composes smaller applications. Each sub-application has focused responsibilities, and the combined application inherits all registrations.

See Q667 for abstract injection. See Q798 for missing/duplicate registrations. See Q324 for DI basics.

defines module qa.divalidation.applicationregistrations

  defines component

    Api01 as abstract
      work01() as abstract
        <- rtn as String?
      default operator ?
    Svc01 extends Api01
      override work01()
        <- rtn <- "s01"
      default operator ?

    Api02 as abstract
      work02() as abstract
        <- rtn as String?
      default operator ?
    Svc02 extends Api02
      override work02()
        <- rtn <- "s02"
      default operator ?

    Api03 as abstract
      work03() as abstract
        <- rtn as String?
      default operator ?
    Svc03 extends Api03
      override work03()
        <- rtn <- "s03"
      default operator ?

    Api04 as abstract
      work04() as abstract
        <- rtn as String?
      default operator ?
    Svc04 extends Api04
      override work04()
        <- rtn <- "s04"
      default operator ?

    Api05 as abstract
      work05() as abstract
        <- rtn as String?
      default operator ?
    Svc05 extends Api05
      override work05()
        <- rtn <- "s05"
      default operator ?

    Api06 as abstract
      work06() as abstract
        <- rtn as String?
      default operator ?
    Svc06 extends Api06
      override work06()
        <- rtn <- "s06"
      default operator ?

    Api07 as abstract
      work07() as abstract
        <- rtn as String?
      default operator ?
    Svc07 extends Api07
      override work07()
        <- rtn <- "s07"
      default operator ?

    Api08 as abstract
      work08() as abstract
        <- rtn as String?
      default operator ?
    Svc08 extends Api08
      override work08()
        <- rtn <- "s08"
      default operator ?

    Api09 as abstract
      work09() as abstract
        <- rtn as String?
      default operator ?
    Svc09 extends Api09
      override work09()
        <- rtn <- "s09"
      default operator ?

    Api10 as abstract
      work10() as abstract
        <- rtn as String?
      default operator ?
    Svc10 extends Api10
      override work10()
        <- rtn <- "s10"
      default operator ?

    Api11 as abstract
      work11() as abstract
        <- rtn as String?
      default operator ?
    Svc11 extends Api11
      override work11()
        <- rtn <- "s11"
      default operator ?

    Api12 as abstract
      work12() as abstract
        <- rtn as String?
      default operator ?
    Svc12 extends Api12
      override work12()
        <- rtn <- "s12"
      default operator ?

    Api13 as abstract
      work13() as abstract
        <- rtn as String?
      default operator ?
    Svc13 extends Api13
      override work13()
        <- rtn <- "s13"
      default operator ?

  defines application

    <?-
      Application with exactly 12 registrations. At the limit but valid.
      Adding a 13th would trigger E11042.
    -?>
    FocusedApp
      register Svc01() as Api01
      register Svc02() as Api02
      register Svc03() as Api03
      register Svc04() as Api04
      register Svc05() as Api05
      register Svc06() as Api06
      register Svc07() as Api07
      register Svc08() as Api08
      register Svc09() as Api09
      register Svc10() as Api10
      register Svc11() as Api11
      register Svc12() as Api12

  defines program

    ApplicationRegistrationsDemo() with application of FocusedApp
      stdout <- Stdout()
      svc01 as Api01!
      result <- svc01.work01()
      stdout.println(`Result: ${result}`)

Java: Spring has no limit on beans per context. Python: no DI framework limit. Go: no DI framework limit. Rust: no standard DI. EK9: compiler enforces 12-registration limit per application block, encouraging modular composition.

DI E11042 application composition limit modular monolith registration split twelve

      register Svc12() as Api12
      register Svc13() as Api13

Explanation: An application block can have at most 12 registrations. This mutation adds a 13th, triggering E11042. Split large applications into focused sub-applications. See ek9 -h E11042 for details.


Question: Why does EK9 require 'defines class' and 'defines function' section headers?

What are EK9 section headers and why are they mandatory?

Why can't I just write a class without 'defines class'?

Answer:
EK9 uses mandatory section headers to organize code into typed blocks. This works like Pascal's type/var/procedure sections — every construct MUST appear under its appropriate 'defines' header.

Like Pascal's 'type' section where you declare types, EK9's 'defines class' section is where you declare classes. Like Pascal's 'procedure' section, EK9's 'defines function' section is where you declare functions.

MANDATORY SECTION HEADERS
EK9 requires the following section headers:
- defines module — declares the module (package), MUST be first
- defines class — contains class definitions
- defines record — contains record (value type) definitions
- defines function — contains standalone function definitions
- defines trait — contains trait (interface) definitions
- defines program — contains executable entry points
- defines component — contains DI-injectable components
- defines type — contains constrained types and enumerations
- defines constant — contains module-level constants
- defines service — contains HTTP service definitions
- defines application — contains DI wiring

WITHOUT SECTION HEADERS = PARSE FAILURE
Writing a class without 'defines class' above it is a grammar error — the parser rejects it immediately, before any compilation even starts.

WHY THIS DESIGN
- Like Pascal's structured declarations, sections make code self-documenting
- The compiler knows what kind of construct to expect
- AI assistants can generate correct section placement
- Modules can have multiple 'defines class' sections for grouping

MULTIPLE SECTIONS
You can have multiple sections of the same type in one module — useful for grouping related types.

See Q93 for class definitions. See Q49 for function definitions. See Q97 for records vs classes.

defines module qa.syntax.sectionheaders

  defines constant

    DEFAULT_GREETING <- "Hello"

  defines record

    <?-
      Records go under 'defines record' section.
      Like Pascal's record type declaration under 'type'.
    -?>
    Coordinate
      xPos as Float: 0.0
      yPos as Float: 0.0

      Coordinate()
        ->
          xPos as Float
          yPos as Float
        this.xPos :=: xPos
        this.yPos :=: yPos

      default operator

  defines function

    <?-
      Functions go under 'defines function' section.
      Like Pascal's 'procedure'/'function' section.
    -?>
    calculateDistance() as pure
      ->
        pointA as Coordinate
        pointB as Coordinate
      <-
        rtn as Float: 0.0

      diffX <- pointA.xPos - pointB.xPos
      diffY <- pointA.yPos - pointB.yPos
      rtn := sqrt(diffX * diffX + diffY * diffY)

  defines trait

    <?-
      Traits go under 'defines trait' section.
      No braces, no semicolons — just indentation.
    -?>
    Describable
      describe() as pure abstract
        <- rtn as String?

  defines class

    <?-
      Classes go under 'defines class' section.
      Like Pascal's type section for class declarations.
    -?>
    Circle
      origin as Coordinate: Coordinate()
      radius as Float: 1.0

      Circle()
        ->
          origin as Coordinate
          radius as Float
        this.origin :=: origin
        this.radius :=: radius

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

      default operator

  defines program

    SectionHeadersDemo()
      stdout <- Stdout()

      origin <- Coordinate()
      target <- Coordinate(3.0, 4.0)
      distance <- calculateDistance(origin, target)
      stdout.println(`Distance: ${distance}`)

      circle <- Circle(origin, 5.0)
      stdout.println(`Circle area: ${circle.area()}`)

Pascal: uses type/var/procedure/function section headers — EK9 follows this pattern with 'defines class/function/record'. Java: no section headers, class/interface at top level. Python: no section headers, class/def anywhere. Go: type/func/var at top level, no grouping required. Kotlin: class/fun at top level. EK9: mandatory 'defines class/function/record/program' section headers organize code — think Pascal's structured declarations.

Pascal class defines function header mandatory module program record section syntax trait

section headers
defines keyword
code structure

Explanation: EK9 requires 'defines class' section header before any class definition. Without it, the parser cannot determine the construct type. Like Pascal requires 'type' before type declarations. See ek9 -h E01000 for details.

  function greet()
    <- rtn as String: "hello"

Explanation: EK9 requires 'defines function' section header before function definitions. The 'function' keyword alone is not valid syntax. Like Pascal's 'procedure' section. See ek9 -h E01000 for details.


Question: How do EK9 constructors work with parameters?

What is the constructor parameter syntax in EK9?

How do I pass parameters to an EK9 constructor?

Why does my EK9 constructor fail to parse?

How do multi-parameter constructors work in EK9?

Answer:
EK9 constructor parameters use the '->' data-in port syntax on a SEPARATE LINE after the constructor name — like Go's named return values, the parameters are declared on their own indented block.

BASIC CONSTRUCTOR
The constructor shares the class name. Parameters use '->' on a separate line:
  MyClass()
    -> paramName as Type
    this.field :=: paramName

The '->' means 'data coming IN' (like Go's named return parameters or Ada's 'in' port). The '<-' means 'data going OUT'.

MULTI-PARAMETER CONSTRUCTOR
Multiple parameters each get their own line under '->':
  MyClass()
    ->
      firstName as String
      lastName as String
      birthYear as Integer
    this.firstName :=: firstName
    this.lastName :=: lastName
    this.birthYear :=: birthYear

BODY USES :=: FOR COPY ASSIGNMENT
Inside the constructor body, use ':=:' (copy operator) to copy parameter values into fields. This creates a COPY, ensuring the class owns its data.

SINGLE-LINE vs MULTI-LINE
Single parameter: -> on same line as parameter
Multiple parameters: -> on its own line, then parameters indented below

COMMON MISTAKES
- Putting -> and parameters on the class name line (parse error)
- Using := instead of :=: for field assignment in constructor body
- Forgetting the () after the class name in the constructor

See Q93 for class definitions. See Q95 for field visibility. See Q881 for declaration vs assignment.

defines module qa.syntax.constructors

  defines class

    <?-
      Single-parameter constructor.
      Like Go: func NewPerson(name string) *Person
      EK9: -> on separate line for data-in.
    -?>
    Greeting
      message as String: String()

      Greeting()
        -> message as String
        this.message :=: message

      getMessage() as pure
        <- rtn as String: message

      default operator

    <?-
      Multi-parameter constructor.
      Like Go named return params, each on own line.
      Body uses :=: (copy) to assign fields.
    -?>
    Employee
      employeeName as String: String()
      department as String: String()
      yearJoined as Integer: 0

      Employee()
        ->
          employeeName as String
          department as String
          yearJoined as Integer
        this.employeeName :=: employeeName
        this.department :=: department
        this.yearJoined :=: yearJoined

      describe() as pure
        <- rtn as String: `${employeeName} in ${department} since ${yearJoined}`

      default operator

    <?-
      Constructor with default values via multiple constructors.
      Like Go's functional options pattern.
    -?>
    Configuration
      hostAddress as String: "localhost"
      portNumber as Integer: 8080
      isSecure as Boolean: false

      Configuration()
        ->
          hostAddress as String
          portNumber as Integer
          isSecure as Boolean
        this.hostAddress :=: hostAddress
        this.portNumber :=: portNumber
        this.isSecure :=: isSecure

      Configuration()
        ->
          hostAddress as String
          portNumber as Integer
        this.hostAddress :=: hostAddress
        this.portNumber :=: portNumber

      describe() as pure
        <- rtn as String: `${hostAddress}:${portNumber} secure=${isSecure}`

      default operator

  defines program

    ConstructorDemo()
      stdout <- Stdout()

      hello <- Greeting("Welcome to EK9")
      stdout.println(hello.getMessage())

      engineer <- Employee("Alice", "Engineering", 2023)
      stdout.println(engineer.describe())

      defaultConfig <- Configuration("api.example.com", 443, true)
      stdout.println(defaultConfig.describe())

      simpleConfig <- Configuration("localhost", 3000)
      stdout.println(simpleConfig.describe())

Go: func NewPerson(name string, age int) *Person — named returns on separate lines. Java: public MyClass(String name, int age) { this.name = name; } with braces. Python: def __init__(self, name, age): self.name = name. Kotlin: class MyClass(val name: String, val age: Int) — primary constructor inline. EK9: MyClass() with -> parameters on separate lines, body uses :=: copy operator — think Go's named parameters on separate lines.

Go arrow assign class constructor copy data-in multi-parameter parameter syntax

constructor syntax
parameter passing
copy assignment

    Employee(employeeName as String, department as String)
      this.employeeName :=: employeeName
      this.department :=: department

Explanation: EK9 constructors use -> on a SEPARATE line for parameters, not inline parentheses like Java. The -> means 'data coming in' like Go's named parameters. See ek9 -h E01000 for details.


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

Why does EK9 have two assignment operators?

What is the declaration operator in EK9?

Answer:
EK9 has THREE distinct assignment operators, each with precise semantics. Getting these wrong is one of the most common mistakes.

THREE OPERATORS
<- is DECLARATION: creates a NEW variable with its first value.
:= is ASSIGNMENT: assigns to an EXISTING variable.
:=? is GUARDED ASSIGNMENT: only assigns if the variable is currently UNSET.

RULE: Use <- exactly ONCE per variable (when you create it). Use := for all subsequent assignments.

EXAMPLES
  counter <- 0         declaration, creates counter
  counter := counter + 1   reassignment, counter already exists
  counter := 10        reassignment again

COMMON MISTAKE
Using <- when you mean to reassign:
  score <- 100         declaration — OK
  score <- 200         ERROR — score already exists, use := instead

Using := when the variable doesn't exist yet:
  total := 0           ERROR — total not declared yet, use <- instead

GUARDED ASSIGNMENT :=?
Only assigns if the variable is currently unset:
  nickname <- String()     declared but unset
  nickname :=? "Default"   assigns because nickname is unset
  nickname :=? "Other"     does NOTHING because nickname is now set

IN FUNCTIONS
<- declares the return variable:
  myFunction()
    -> inputValue as String
    <- outputResult as String: inputValue
The <- creates outputResult as the return. Any updates to it use :=

See Q22 for variable declarations. See Q93 for class definitions. See Q881 for more assignment examples.

defines module qa.gettingstarted.declarevsassign

  defines function

    <?-
      Shows <- for declaration and := for reassignment.
      EK9 separates these into distinct operators.
    -?>
    countPositives() as pure
      -> numbers as List of Integer
      <- positiveCount as Integer: 0

      for n in numbers
        if n > 0
          positiveCount := positiveCount + 1

    <?-
      Shows :=? guarded assignment.
      Like Kotlin's ?: elvis operator.
    -?>
    firstNonEmpty() as pure
      ->
        primary as String
        fallback as String
      <-
        chosen as String: String()

      chosen :=? primary
      chosen :=? fallback

    <?-
      Shows all three operators in one function.
    -?>
    buildGreeting() as pure
      ->
        prefix as String
        userName as String
      <-
        greeting as String: String()

      greeting :=? prefix
      greeting :=? "Hello"

      separator <- ", "
      greeting := `${greeting}${separator}${userName}`

  defines program

    DeclarationVsAssignmentDemo()
      stdout <- Stdout()

      // <- DECLARATION: creates the variable
      score <- 100
      stdout.println(`Initial score: ${score}`)

      // := ASSIGNMENT: updates existing variable
      score := score + 50
      stdout.println(`Updated score: ${score}`)

      score := score - 20
      stdout.println(`Final score: ${score}`)

      // :=? GUARDED ASSIGNMENT: only if unset
      nickname <- String()
      nickname :=? "DefaultUser"
      stdout.println(`Nickname: ${nickname}`)

      nickname :=? "WontChange"
      stdout.println(`Still: ${nickname}`)

      // Function examples
      positives <- countPositives([3, -1, 7, -2, 5])
      stdout.println(`Positives: ${positives}`)

      chosen <- firstNonEmpty(String(), "fallback")
      stdout.println(`Chosen: ${chosen}`)

      msg <- buildGreeting("Hi", "Steve")
      stdout.println(msg)

Go separates declaration from reassignment with different operators — EK9 does the same with <- and :=. Python and Java use = for both (ambiguous). Kotlin uses val/var keywords. Rust uses let/let mut. EK9 uses <- to declare, := to reassign, :=? for guarded assignment.

assignment coalesce create declaration declare guard operator reassign variable

declaration operator
assignment operator
guarded assignment

Explanation: Use ':=' to reassign an existing variable; re-using '<-' (declaration only) on the already-declared 'score' duplicates the variable. See ek9 -h E50050 for details.


Question: How do I output stream results to stdout in EK9?

How do EK9 stream pipelines output data?

Answer:
EK9 stream pipelines use '>' for output redirection — just like Unix shell pipes. The '>' sends stream results to a target like stdout.

BASIC PATTERN
Like Unix: ls | grep foo > output.txt
EK9:  cat items | filter by predicate > stdout

The '>' is the stream terminal — it sends each item to the target.

STREAM TO STDOUT
  cat items | filter by isPositive > stdout
This is like Unix: cat file | grep pattern > /dev/stdout

STREAM TO COLLECTION
  filtered <- cat items | filter by isPositive | collect as List of Integer
The 'collect' terminal gathers items into a new collection.

STREAM PIPELINE OPERATORS
- cat: source items (like Unix cat)
- filter: keep items matching predicate (like Unix grep)
- map: transform items (like Unix awk/sed)
- sort: order items
- head: take first N items (like Unix head)
- tail: take last N items (like Unix tail)
- skip: skip first N items
- tee: fork output (like Unix tee)
- group: group items (like Unix uniq)
- join: merge groups
- flatten: expand nested structures
- > : redirect output (like Unix >)
- collect: gather into collection

COMMON MISTAKE
Do NOT use <- to assign stream output directly:
  stdout <- cat items   WRONG — this declares stdout
  cat items > stdout     CORRECT — this redirects output

See Q140 for stream basics. See Q141 for filter and map.

defines module qa.streams.redirect

  defines function

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

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

  defines program

    StreamOutputDemo()
      stdout <- Stdout()

      numbers <- [5, -3, 8, -1, 12, -7, 3]

      // Like Unix: cat numbers | grep positive > stdout
      stdout.println("Positive numbers:")
      cat numbers | filter by isPositive > stdout

      // Like Unix: cat numbers | grep positive | awk '{print $1*2}' > stdout
      stdout.println("Doubled positives:")
      cat numbers | filter by isPositive | map by doubleIt > stdout

      // Like Unix: cat numbers | sort > stdout
      stdout.println("Sorted:")
      cat numbers | sort > stdout

      // Like Unix: cat numbers | head -3 > stdout
      stdout.println("First 3:")
      cat numbers | head 3 > stdout

      // Tail — like Unix: cat numbers | tail -3 > stdout
      stdout.println("Last 3:")
      cat numbers | tail 3 > stdout

Unix: ls | grep foo | sort > output.txt — EK9 streams follow the same model. Java: stream().filter().map().forEach() — method chaining instead of pipes. Python: list comprehensions or generator expressions. Rust: iter().filter().map().collect() — similar to Java. EK9: cat items | filter by predicate | sort > stdout — Unix pipe syntax with typed pipeline stages.

Unix cat collect filter map output pipe pipeline redirect stdout stream

stream output
redirect operator
Unix pipes


Question: What do the $ and #? operators mean in EK9?

What is the $ operator in EK9?
What is the #? operator in EK9?
How does string conversion work in EK9?
How does hashcode work in EK9?

Answer:
EK9 has dedicated operator symbols for string conversion and hashcode. These are NOT what you might expect from other languages.

$ OPERATOR — STRING CONVERSION (NOT interpolation)
The $ operator calls the _string() method to convert any value to its String representation:
  age <- 25
  ageText <- $age         converts Integer 25 to String "25"
  point <- Point(3.0, 4.0)
  pointText <- $point     calls point._string()

IMPORTANT: $ is NOT string interpolation. For interpolation, EK9 uses backtick strings with ${...}:
  greeting <- `Hello ${name}`   interpolation inside backtick string
  nameStr <- $name               standalone string conversion

Inside backtick strings, ${$variable} uses $ to convert to string:
  stdout.println(`Age is ${$age}`)   ${...} is the interpolation, $ inside converts

#? OPERATOR — HASHCODE (lowercase 'c')
The #? operator calls _hashcode() and returns an Integer hash value:
  hashValue <- #? myObject    calls myObject._hashcode()

Note: it is _hashcode (lowercase c), not _hashCode (Java style).

#^ OPERATOR — PROMOTE (type conversion)
The #^ operator calls _promote() to convert to a wider type:
  anInteger <- 42
  asFloat <- #^ anInteger   promotes Integer to Float

IMPORTANT: #^ is NOT Python list comprehension. It is type PROMOTION only.

OPERATOR METHOD NAMES
- $ calls _string()
- #? calls _hashcode()
- #^ calls _promote()
- ? calls _isSet()
- <=> calls _cmp()
- == calls _eq()
- <> calls _neq()

See Q96 for all operators. See Q238 for operator enforcement. See Q245 for implementing operators.

defines module qa.operators.stringhash

  defines record

    Coordinate
      xPos as Float: 0.0
      yPos as Float: 0.0

      Coordinate()
        ->
          initialX as Float
          initialY as Float
        this.xPos :=: initialX
        this.yPos :=: initialY

      default operator

  defines function

    showConversions() as pure
      ->
        label as String
        coordinate as Coordinate
      <-
        rtn as String: String()

      // $ operator converts to String via _string()
      coordText <- $coordinate
      hashVal <- #? coordinate
      rtn := `${label}: text=${coordText}, hash=${hashVal}`

  defines program

    StringAndHashDemo()
      stdout <- Stdout()

      // $ operator — string CONVERSION (not interpolation)
      age <- 25
      ageText <- $age
      stdout.println(`Age as string: ${ageText}`)

      // Inside backtick strings: ${variable} for conversion
      price <- 19.99
      stdout.println(`Price: ${price}`)

      // #? operator — hashcode
      greeting <- "Hello"
      greetingHash <- #? greeting
      stdout.println(`Hash of greeting: ${greetingHash}`)

      // #^ operator — promote (type conversion)
      wholeNumber <- 42
      promoted <- #^ wholeNumber
      stdout.println(`Promoted: ${promoted}`)

      // Custom type with operators
      origin <- Coordinate()
      target <- Coordinate(3.0, 4.0)
      stdout.println(showConversions("Origin", origin))
      stdout.println(showConversions("Target", target))

Java: toString() and hashCode() are method calls. Python: str() and hash() are built-in functions, $ has no meaning. Rust: Display trait for string, Hash trait for hashing. Go: String() method convention. Kotlin: toString() and hashCode() like Java. EK9: $ for _string() conversion, #? for _hashcode(), #^ for _promote() — these are operator symbols, not method calls.

backtick conversion dollar hashcode interpolation operator promote string

$ operator
#? operator
#^ operator
string conversion

Explanation: Use $ to convert an Integer to String before concatenation — "Age: " + age has no defined + operator for a String and an Integer. See ek9 -h E07620 for details.


Question: What does the ? operator mean in EK9?

What does the question mark operator do in EK9?

How do I check if a variable is set in EK9?

What does appending ? after a variable do in EK9?

Answer:
In EK9, the ? operator checks if a value is SET. It is a SUFFIX operator — append it after the variable name.

WHAT ? DOES
The ? operator goes AFTER the variable:
  name <- String()       declared but UNSET
  if name?               false — name has no value yet
    stdout.println(name) skipped

  name := "Alice"        now SET
  if name?               true — name has a value
    stdout.println(name) prints "Alice"

The ? operator calls _isSet() internally and returns Boolean.

TRI-STATE MODEL
EK9 objects have three states:
1. ABSENT — object doesn't exist (Optional empty, Dict missing key)
2. PRESENT but UNSET — object exists but has no meaningful value
3. PRESENT and SET — object exists with valid, usable value

The ? operator checks for state 3 (set with meaningful value).

USE IN CONDITIONS
  if userName?                    single check
  if firstName? and lastName?     compound check
  isReady <- connection?          assign Boolean result

COLLECTION SEMANTICS
Collections (List, Dict) are ALWAYS set when created, even if empty:
  emptyList <- List() of Integer
  emptyList?              true — empty list IS set

See Q24 for tri-state semantics. See Q981 for more ? examples.

defines module qa.operators.issetnotternary

  defines function

    <?-
      Shows ? operator checking if value is SET.
      Like Rust: Option.is_some()
      NOT like JavaScript: condition ? a : b
    -?>
    describeState() as pure
      -> userName as String
      <- rtn as String: String()

      // ? checks if userName is SET (has meaningful value)
      if userName?
        rtn := `User: ${userName}`
      else
        rtn := "No user set"

    <?-
      Shows guard expression as alternative to ternary.
      EK9 has no ternary — use :=? instead.
    -?>
    getDisplayName() as pure
      ->
        firstName as String
        fallbackName as String
      <-
        displayName as String: String()

      // :=? only assigns if displayName is currently UNSET
      displayName :=? firstName
      displayName :=? fallbackName
      displayName :=? "Anonymous"

  defines program

    IsSetDemo()
      stdout <- Stdout()

      // ? operator — like Rust's is_some()
      emptyName <- String()
      stdout.println(describeState(emptyName))

      filledName <- "Steve"
      stdout.println(describeState(filledName))

      // Collections are ALWAYS set when created
      // Note: List() is always set, so no need for isSet check
      emptyNumbers <- List() of Integer
      emptyCount <- length emptyNumbers
      stdout.println(`Empty list has ${emptyCount} items (but IS set)`)

      // Guard expression instead of ternary
      displayA <- getDisplayName("Alice", "Default")
      stdout.println(`Display: ${displayA}`)

      displayB <- getDisplayName(String(), "Fallback")
      stdout.println(`Display: ${displayB}`)

EK9's suffix ? checks if a value is set — append it after any variable name. Returns Boolean, no parentheses needed.

Option Rust check isSet operator question set suffix tri-state unset

? operator
isSet
tri-state
suffix operator

      if userName?
        rtn := `User: ${userName}`

      if userName != null
        stdout.println(userName)

Explanation: 'null' does not exist in EK9. Append ? after the variable to check if it is set: 'if userName?' See ek9 -h E01073.


Question: What are the coalescing operators in EK9?

How do coalescing comparators work in EK9?

Answer:
EK9 has coalescing COMPARISON operators that return a value while handling UNSET gracefully. These are unique to EK9.

?? and ?: — VALUE COALESCING
Returns the left value if SET, otherwise the right:
  result <- maybeValue ?? defaultValue
  result <- maybeValue ?: fallback

COALESCING COMPARATORS (unique to EK9)
These COMPARE two values and RETURN a result. If either is UNSET, return the SET one:
  <? — coalescing minimum: returns the SMALLER value
  >? — coalescing maximum: returns the LARGER value
  <=? — coalescing less-or-equal minimum
  >=? — coalescing greater-or-equal maximum

Examples:
  bestPrice <- price1 <? price2    returns the cheaper price
  topScore <- score1 >? score2     returns the higher score

All coalescing operators RETURN a value — use them on the RIGHT side of <-.

NOTE: :=? is NOT a coalescing operator. It is an ASSIGNMENT operator (see Q1152). :=? assigns a value only if the target is unset. Do not confuse :=? (assignment) with <? (comparison).

See Q24 for tri-state semantics. See Q898 for the ? operator. See Q1226 for <? vs :=? contrast.

defines module qa.operators.coalescingdetail

  defines function

    <?-
      Shows ?? value coalescing.
      Like Kotlin's ?: or JavaScript's ??
    -?>
    getDisplayName() as pure
      ->
        userName as String
        defaultName as String
      <-
        rtn as String: userName ?? defaultName

    <?-
      Shows <? coalescing minimum.
      Unique to EK9 — returns smaller, or the SET one.
    -?>
    cheaperPrice() as pure
      ->
        priceA as Float
        priceB as Float
      <-
        rtn as Float: priceA <? priceB

    <?-
      Shows >? coalescing maximum.
      Unique to EK9 — returns larger, or the SET one.
    -?>
    higherScore() as pure
      ->
        scoreA as Integer
        scoreB as Integer
      <-
        rtn as Integer: scoreA >? scoreB

  defines program

    CoalescingDemo()
      stdout <- Stdout()

      // ?? value coalescing — like Kotlin's ?: or JS's ??
      userName <- String()
      displayName <- getDisplayName(userName, "Guest")
      stdout.println(`Display: ${displayName}`)

      setName <- "Alice"
      displayName2 <- getDisplayName(setName, "Guest")
      stdout.println(`Display: ${displayName2}`)

      // <? coalescing minimum — RETURNS a value
      cheapest <- cheaperPrice(29.99, 19.99)
      stdout.println(`Cheapest: ${cheapest}`)

      // >? coalescing maximum
      topScore <- higherScore(85, 92)
      stdout.println(`Top: ${topScore}`)

Kotlin: ?: elvis operator — similar to EK9's ??. JavaScript: ?? nullish coalescing — similar to EK9's ??. Swift: ?? nil coalescing — similar to EK9's ??. C#: ?? null coalescing — similar to EK9's ??. None of these languages have coalescing COMPARATORS (<?, >?, <=?, >=?) — these are unique to EK9.

coalesce coalescing comparator elvis maximum minimum null unset

coalescing operators
?? operator
<? >? operators


Question: What is the difference between record and class field visibility in EK9?

Are record fields public or private in EK9?

Why can I access record fields but not class fields?

How does visibility differ between records and classes in EK9?

What is the default visibility for EK9 records vs classes?

Answer:
EK9 records and classes have OPPOSITE default field visibility. This catches many developers coming from Java where both are the same.

RECORD FIELDS: PUBLIC by default
Like Kotlin data classes or Rust structs, record fields are directly accessible:
  defines record
    Point
      x as Float: 0.0
      y as Float: 0.0

  origin <- Point()
  xValue <- origin.x         OK — fields are public

CLASS FIELDS: PRIVATE by default
Like Java, class fields are encapsulated — access through methods only:
  defines class
    Person
      name <- String()

  person <- Person()
  person.name               ERROR — field is private
  person.getName()           OK — access through method

WHY THE DIFFERENCE
- Records are VALUE TYPES — transparent data carriers like database rows or JSON objects
- Classes are BEHAVIOR TYPES — encapsulated objects with invariants to protect
- Records can't have additional methods (only operators)
- Classes can have methods and protect internal state

RECORD RULES
- Records CAN have operators (default operator generates them)
- Records CANNOT have additional methods
- Records are closed-by-default like classes — mark a base record 'as open' to allow extension (see Q1297 for the full record-inheritance pattern)
- Record fields are PUBLIC — there's nothing to protect since records are transparent
- Records are mutable; mutability is gated by 'as pure' on functions and methods, not on the type (see Q97)

CLASS FEATURES
- Classes CAN have methods and operators
- Class fields are PRIVATE — encapsulation protects invariants
- Classes can use 'as open' to allow extension (same rule as records)
- Classes can implement traits

See Q93 for class basics. See Q97 for records vs classes overview. See Q1297 for record-to-record inheritance.

defines module qa.classes.recordvsclass

  defines record

    <?-
      Record fields are PUBLIC — transparent data carrier.
      Like Kotlin data class or Rust struct with pub fields.
    -?>
    Measurement
      sensorId as String: String()
      reading as Float: 0.0
      timestamp as Integer: 0

      Measurement()
        ->
          sensorId as String
          reading as Float
          timestamp as Integer
        this.sensorId :=: sensorId
        this.reading :=: reading
        this.timestamp :=: timestamp

      default operator

  defines class

    <?-
      Class fields are PRIVATE — encapsulated behavior.
      Access through methods only, like Java.
    -?>
    SensorMonitor
      sensorName as String: String()
      latestReading as Float: 0.0
      readingCount as Integer: 0

      SensorMonitor()
        -> sensorName as String
        this.sensorName :=: sensorName

      recordReading()
        -> newReading as Float
        latestReading :=: newReading
        readingCount++

      getLatestReading() as pure
        <- rtn as Float: latestReading

      getReadingCount() as pure
        <- rtn as Integer: readingCount

      getSensorName() as pure
        <- rtn as String: sensorName

      default operator

  defines program

    RecordVsClassDemo()
      stdout <- Stdout()

      // RECORD: fields are PUBLIC — direct access
      measurement <- Measurement("sensor-1", 23.5, 1000)
      stdout.println(`Sensor: ${measurement.sensorId}`)
      readingVal <- measurement.reading
      timeVal <- measurement.timestamp
      stdout.println(`Reading: ${readingVal}`)
      stdout.println(`Time: ${timeVal}`)

      // CLASS: fields are PRIVATE — access through methods
      monitor <- SensorMonitor("temperature")
      monitor.recordReading(23.5)
      monitor.recordReading(24.1)
      stdout.println(`Monitor: ${monitor.getSensorName()}`)
      stdout.println(`Latest: ${monitor.getLatestReading()}`)
      stdout.println(`Count: ${monitor.getReadingCount()}`)

Java: both class and record fields are private by default. Kotlin: data class properties are public, class properties are public unless private. Rust: struct fields are private by default, pub required. Go: exported if capitalized (uppercase), unexported if lowercase — applies to both struct and interface fields. Python: all attributes are public, _prefix convention for private. EK9: records are PUBLIC (like Kotlin data class), classes are PRIVATE (like Java) — opposite defaults based on type purpose.

access class encapsulation field private public record visibility

record visibility
class visibility
public vs private

Explanation: Class fields are PRIVATE by default in EK9 — access them through methods (getSensorName()), not by direct field reference. See ek9 -h E06180 for details.


Question: Can switch be used as an expression in EK9?

Is switch an expression or statement in EK9?

How does switch work as an expression in EK9?

Answer:
YES — switch CAN be an expression in EK9. Like Kotlin's 'when' expression, EK9's switch can produce a value that gets assigned.

SWITCH AS EXPRESSION
Use <- to capture the switch result. The switch needs a return declaration:
  label <- switch category
    <- rtn as String: String()
    case "A"
      rtn: "Alpha"
    default
      rtn: "Unknown"

The '<- rtn as String' declares the return variable. Each case assigns to rtn.

SWITCH AS STATEMENT
Without <-, switch is a statement:
  switch direction
    case "north"
      moveNorth()
    default
      stay()

NO FALLTHROUGH
EK9 has NO switch fallthrough — each case is independent. No break needed because break does not exist. Multiple values on one case:
  switch dayOfWeek
    case "MONDAY", "TUESDAY", "WEDNESDAY"
      rtn: "weekday"
    case "SATURDAY", "SUNDAY"
      rtn: "weekend"

See Q63 for switch basics. See Q68 for switch as expression. See Q782 for switch return rules.

defines module qa.controlflow.switchexpr

  defines function

    <?-
      Switch as expression — like Kotlin's when.
      Needs <- rtn declaration inside switch.
    -?>
    classifyTemperature() as pure
      -> degrees as Float
      <-
        rtn as String: String()

      rtn := switch degrees
        <- category as String: String()
        case < 0.0
          category: "freezing"
        case < 10.0
          category: "cold"
        case < 20.0
          category: "cool"
        case < 30.0
          category: "warm"
        default
          category: "hot"

    <?-
      Multiple case values — no fallthrough needed.
    -?>
    isWeekend() as pure
      -> dayName as String
      <-
        rtn as Boolean: false

      rtn := switch dayName
        <- weekend as Boolean: false
        case "SATURDAY", "SUNDAY"
          weekend: true
        default
          weekend: false

    <?-
      Switch as expression returning String.
    -?>
    describeSeason() as pure
      -> month as Integer
      <-
        rtn as String: String()

      rtn := switch month
        <- seasonName as String: String()
        case 3, 4, 5
          seasonName: "Spring"
        case 6, 7, 8
          seasonName: "Summer"
        case 9, 10, 11
          seasonName: "Autumn"
        default
          seasonName: "Winter"

  defines program

    SwitchExpressionDemo()
      stdout <- Stdout()

      forecast <- classifyTemperature(15.5)
      stdout.println(`15.5 degrees is: ${forecast}`)

      hot <- classifyTemperature(35.0)
      stdout.println(`35.0 degrees is: ${hot}`)

      saturdayWeekend <- isWeekend("SATURDAY")
      stdout.println(`Saturday weekend: ${saturdayWeekend}`)

      mondayWeekend <- isWeekend("MONDAY")
      stdout.println(`Monday weekend: ${mondayWeekend}`)

      season <- describeSeason(7)
      stdout.println(season)

Kotlin: when expression — EK9 switch as expression works similarly but needs explicit return declaration. Java: switch expressions (Java 14+) with -> — similar concept. Rust: match expression — similar, always returns a value. Go: switch is a statement, not an expression. Python: match (3.10+) is a statement. C: switch with mandatory break, fallthrough by default — EK9 has NO fallthrough and NO break. EK9: switch is BOTH expression and statement, like Kotlin's when. No fallthrough, no break needed.

Kotlin case default expression fallthrough return statement switch when

switch expression
switch statement
no fallthrough


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

How do I catch exceptions in EK9?
What is the catch syntax in EK9?
Does EK9 have try/catch?
How do I handle exceptions in EK9?

Answer:
EK9 has try/catch/finally similar to Java, but the catch clause uses '->' for the exception parameter — like Go's 'in' parameter or Ada's 'in' port.

BASIC TRY/CATCH
  try
    riskyOperation()
  catch
    -> ex as Exception
    stdout.println(`Error: ${ex}`)      the -> means 'data coming in'

IMPORTANT: The catch uses '->' on its own line, like function parameters. The exception is data flowing IN to the catch handler.

TRY/CATCH/FINALLY
  try
    openConnection()
    sendRequest()
  catch
    -> ex as Exception
    logError(ex)
  finally
    closeConnection()    always runs, like Java's finally

TRY AS EXPRESSION
Like switch, try can be an expression:
  result <- try
    parseInput(rawText)
  catch
    -> ex as Exception
    "default value"

GUARD TRY
Try can use a guard variable:
  try result <- riskyComputation()
    stdout.println(result)
  catch
    -> ex as Exception
    stdout.println(`Failed: ${ex}`)

EXCEPTION TYPES
EK9 has a fixed exception hierarchy:
- Exception (base type)
Use '$' on an exception to get its message as String.

NO CHECKED EXCEPTIONS
EK9 does not have checked exceptions. All exceptions are runtime.

See Q130 for control flow. See Q893 for section headers.

defines module qa.controlflow.trycatch

  defines function

    <?-
      Basic function that may throw.
    -?>
    safeDivide() as pure
      ->
        numerator as Integer
        denominator as Integer
      <-
        rtn as Float: #^ numerator / #^ denominator

  defines program

    TryCatchDemo()
      stdout <- Stdout()

      // Basic try/catch — note -> on catch for exception parameter
      // Like Go/Ada: -> means 'data flowing in' to the handler
      try
        quotient <- safeDivide(10, 2)
        stdout.println(`10 / 2 = ${quotient}`)
      catch
        -> ex as Exception
        stdout.println(`Error: ${ex}`)

      // Try/catch/finally — finally always runs
      try
        result <- safeDivide(100, 4)
        stdout.println(`100 / 4 = ${result}`)
      catch
        -> ex as Exception
        stdout.println(`Failed: ${ex}`)
      finally
        stdout.println("Division complete")

      // Nested try/catch — works like Java
      try
        firstResult <- safeDivide(20, 5)
        stdout.println(`20 / 5 = ${firstResult}`)
      catch
        -> ex as Exception
        stdout.println(`Nested failed: ${ex}`)

Java: catch (Exception e) { ... } — EK9 uses -> ex as Exception on separate line. Go: if err != nil — EK9 has try/catch, not error values. Python: except Exception as e: — similar but EK9 uses -> prefix. Kotlin: catch (e: Exception) — EK9 uses -> for incoming data. Rust: Result and ? operator — EK9 has both try/catch and Result type. EK9: catch with -> ex as Exception — the -> means 'data flowing in' like Go/Ada parameter ports.

arrow catch data-in error exception finally handle throw try

try/catch
exception handling
catch arrow syntax

Explanation: The catch clause requires an Exception type, not String or other types. The variable after -> must be declared 'as Exception'. See ek9 -h E04030 for details.


Question: What operators do NOT exist in EK9?

What is the complete list of EK9 operators?

What common operators from other languages are absent in EK9?

Answer:
EK9 has a FIXED set of approximately 50 operators. Only these exist — no others can be created or used.

THE COMPLETE EK9 OPERATOR SET
Assignment: <- (declaration), := : = (assignment), :=? (guarded assignment)
Copy/Replace/Merge: :=: (copy), :^: (replace), :~: (merge)
Comparison: == (eq), <> (neq), <=> (compare), < > <= >=
Coalescing: ?? (value), <? >? <=? >=? (coalescing comparators)
Arithmetic: + - * / mod
Mutation: ++ -- += -= (postfix statement-only)
Conversion: $ (string via _string), #? (hashcode via _hashcode), #^ (promote via _promote)
Checking: ? (isSet via _isSet), ~ (negate/bitwise not)
Logical: not and or xor
Collection/String: contains matches
Unary: length abs sqrt close empty

CORRECT ALTERNATIVES FOR PATTERNS FROM OTHER LANGUAGES
Conditional value: use switch expression or if/else assignment
Collection access: use get() method on List/Dict
Safe value access: use guard expression (if v <- expr())
Bit shifting: use Bits type
Prefix increment: use postfix (variable++ not ++variable)

RULE: If an operator is not in this list, it does not exist in EK9.

See Q96 for operator definitions. See Q238 for operator enforcement rules.

defines module qa.aimistakes.invalidoperators

  defines function

    <?-
      Shows correct alternatives to operators that don't exist.
    -?>
    classify() as pure
      -> score as Integer
      <-
        rtn as String: String()

      rtn := switch score
        <- grade as String: String()
        case >= 90
          grade: "excellent"
        case >= 70
          grade: "good"
        case >= 50
          grade: "pass"
        default
          grade: "fail"

    <?-
      Shows correct list access — no [] operator in EK9.
      Use get() method, not [] indexing.
    -?>
    getItemCount() as pure
      -> items as List of String
      <- rtn as Integer: length items

    <?-
      Shows correct approach instead of safe navigation (?.)
      Use guard expression instead.
    -?>
    safeLength() as pure
      -> text as String
      <- rtn as Integer: 0

      if text?
        rtn := length text

  defines program

    CorrectOperatorsDemo()
      stdout <- Stdout()

      // Switch expression — NOT ternary
      grade <- classify(85)
      stdout.println(`Grade: ${grade}`)

      // Method call — NOT [] indexing
      names <- ["Alice", "Bob", "Charlie"]
      nameCount <- getItemCount(names)
      stdout.println(`Names: ${nameCount}`)

      // Guard expression — NOT ?. safe navigation
      textLength <- safeLength("hello")
      stdout.println(`Length: ${textLength}`)

      emptyLength <- safeLength(String())
      stdout.println(`Empty length: ${emptyLength}`)

      // Postfix ++ only — NOT prefix ++
      counter <- 0
      counter++
      counter++
      stdout.println(`Counter: ${counter}`)

EK9 has a fixed operator set. Collection access uses get() methods. Conditional values use switch expressions. Safe access uses guard expressions.

AI
complete list
fixed set
indexing
mistake
operator
valid operators

invalid operators
operator hallucination
correct alternatives


Question: Why can't I extend a class in EK9?

What does 'closed by default' mean in EK9?

How do I make a class extensible in EK9?

Why does E05030 say 'not open to be extended'?

Answer:
EK9 types are CLOSED by default — like Kotlin's classes which are final unless marked 'open'. You must explicitly opt into extensibility with 'as open'.

CLOSED BY DEFAULT
All classes, records, and built-in types cannot be extended unless marked:
  defines class
    Animal          closed — cannot extend
      name <- String()

    Dog extends Animal   ERROR E05030 — Animal is not open

OPT INTO EXTENSION
Use 'as open' to allow extension:
  defines class
    Animal as open       now extensible
      name <- String()

    Dog extends Animal   OK — Animal is open

BUILT-IN TYPES CANNOT BE EXTENDED
List, Dict, Optional, Result, and all built-in concrete types are CLOSED:
  MyList extends List of String    ERROR — List is closed

Use composition/delegation instead:
  defines class
    ValidatedList
      items as List of String: List() of String
      addValidated()
        -> item as String
        if length item > 0
          items += item

WHY CLOSED BY DEFAULT
- Like Kotlin's final by default — prevents fragile base class problem
- Forces explicit design for inheritance
- Encourages composition over inheritance (more flexible)
- Simplifies generated code (no virtual dispatch needed)
- Modern consensus: Java's open-by-default was a design mistake

RECORDS ARE ALWAYS CLOSED
Records cannot be marked 'as open' — they are pure value types:
  defines record
    Point
      x as Float: 0.0
      y as Float: 0.0

See Q93 for class basics. See Q97 for records vs classes.

defines module qa.classes.closedtypes

  defines class

    <?-
      CLOSED class — cannot be extended.
      Like Kotlin's final (default behavior).
    -?>
    DatabaseConnection
      connectionUrl as String: String()

      DatabaseConnection()
        -> connectionUrl as String
        this.connectionUrl :=: connectionUrl

      getUrl() as pure
        <- rtn as String: connectionUrl

      default operator

    <?-
      OPEN class — explicitly allows extension.
      Like Kotlin's 'open class'.
    -?>
    Shape as abstract
      shapeName as String: String()

      Shape()
        -> shapeName as String
        this.shapeName :=: shapeName

      area() as pure abstract
        <- rtn as Float?

      getName() as pure
        <- rtn as String: shapeName

      default operator

    <?-
      Extends an open class — this is allowed.
    -?>
    Rectangle extends Shape
      rectWidth as Float: 0.0
      rectHeight as Float: 0.0

      Rectangle()
        ->
          rectWidth as Float
          rectHeight as Float
        super("Rectangle")
        this.rectWidth :=: rectWidth
        this.rectHeight :=: rectHeight

      override area() as pure
        <- rtn as Float: rectWidth * rectHeight

      default operator

    <?-
      Composition instead of inheritance.
      Used when the base type is closed.
    -?>
    ValidatedItems
      items as List of String: List() of String

      addItem()
        -> item as String
        if length item > 0
          items += item

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

      default operator

  defines program

    ClosedTypesDemo()
      stdout <- Stdout()

      // Closed class — used directly, not extended
      conn <- DatabaseConnection("jdbc:postgresql://localhost:5432/mydb")
      stdout.println(`Connection: ${conn.getUrl()}`)

      // Open class — can be extended
      rect <- Rectangle(5.0, 3.0)
      stdout.println(`Shape: ${rect.getName()}, Area: ${rect.area()}`)

      // Composition — when you can't extend
      validItems <- ValidatedItems()
      validItems.addItem("hello")
      validItems.addItem("")
      validItems.addItem("world")
      stdout.println(`Valid items: ${validItems.getCount()}`)

Kotlin: classes are final by default, use 'open' keyword — identical to EK9's approach. Java: classes are open by default — opposite of EK9, considered a historical design mistake. Rust: no inheritance at all — composition via traits only. Swift: classes are not final by default but 'final' keyword available. Go: no inheritance — composition only. C++: classes are extensible by default. EK9: closed by default like Kotlin — use 'as open' to allow extension.

E05030 Kotlin closed composition extend final inherit open

closed by default
as open
composition over inheritance


Question: What happens when I use mutating operators in EK9?

Do mutating operators create aliases in EK9?

Why does y <- x++ make x and y the same object?

Answer:
Mutating operators in EK9 return 'this' — the SAME object, not a copy. This creates aliasing, similar to C++ references or Java mutable objects.

MUTATING OPERATORS
These operators modify the object IN PLACE and return the same object:
  += -= *= /= — compound arithmetic
  ++ -- — increment/decrement
  :=: — copy (overwrites all fields)
  :^: — replace
  :~: — merge

ALIASING DANGER
  x <- 10
  y <- x++    ERROR — ++ is statement-only in EK9

EK9 prevents this by making ++ and -- STATEMENT-ONLY operators. You CANNOT use them in expressions.

CORRECT USAGE
  counter <- 0
  counter++         OK — statement
  counter += 5      OK — statement

  // To get a copy, use a separate declaration
  original <- 100
  copied <- original    creates independent copy
  copied++              only copied changes

NON-MUTATING OPERATORS
These create NEW objects and do NOT alias:
  + - * / — arithmetic (return new value)
  == <> < > <= >= — comparison (return Boolean)
  <=> — three-way compare (return Integer)
  $ — string conversion (return String)
  #? — hashcode (return Integer)
  #^ — promote (return wider type)
  ? — isSet (return Boolean)

RULE: If the operator changes the object, it is mutating and statement-only. If it produces a new value, it is non-mutating and can be used in expressions.

See Q96 for all operators. See Q238 for operator enforcement.

defines module qa.operators.mutating

  defines function

    <?-
      Shows correct use of mutating operators as statements.
      ++ -- += -= are STATEMENT-ONLY, not expressions.
    -?>
    accumulateTotal()
      -> numbers as List of Integer
      <- runningTotal as Integer: 0

      for n in numbers
        runningTotal += n

    <?-
      Shows non-mutating operators that create new values.
      + - * / return NEW objects, safe in expressions.
    -?>
    calculateAverage() as pure
      ->
        total as Integer
        divisor as Integer
      <-
        rtn as Float: 0.0

      if divisor > 0
        rtn := #^ total / #^ divisor

  defines program

    MutatingOperatorsDemo()
      stdout <- Stdout()

      // Mutating operators are STATEMENT-ONLY
      counter <- 0
      counter++
      counter++
      counter++
      stdout.println(`Counter after 3 increments: ${counter}`)

      counter += 10
      stdout.println(`Counter after += 10: ${counter}`)

      counter--
      stdout.println(`Counter after --: ${counter}`)

      // Non-mutating operators create NEW values (safe in expressions)
      productA <- 5 * 3
      productB <- 7 + 2
      stdout.println(`5 * 3 = ${productA}, 7 + 2 = ${productB}`)

      // Copying creates independent values
      original <- 100
      copied <- original
      copied += 50
      stdout.println(`Original: ${original}, Copied: ${copied}`)

      // Accumulation with +=
      numbers <- [10, 20, 30, 40]
      total <- accumulateTotal(numbers)
      avg <- calculateAverage(total, length numbers)
      stdout.println(`Total: ${total}, Average: ${avg}`)

C++: mutating operators return references (aliasing risk). Java: += modifies in place, primitives are copied but objects are aliased. Rust: no implicit aliasing — ownership system prevents it. Go: no operator overloading. Python: += modifies in place for mutable types (lists), creates new for immutable (int). EK9: mutating operators are STATEMENT-ONLY — prevents aliasing bugs at compile time.

aliasing compound copy decrement expression increment mutating operator statement

mutating operators
aliasing
statement-only operators

Explanation: The ++ operator is statement-only and cannot appear inside an expression; increment first, then use the value. See ek9 -h E07950 for details.


Question: What should I use instead of banned variable names in EK9?

What are good alternatives to temp, value, data in EK9?

How do I rename banned variables in EK9?

What descriptive names replace banned identifiers?

Give me a renaming table for EK9 banned names

Answer:
EK9 bans generic variable names at compile time (E11031). Here is a complete replacement guide.

REPLACEMENT TABLE
Instead of 'temp' use: swapHolder, intermediateResult, transformedInput
Instead of 'value' use: price, score, threshold, measurement, reading
Instead of 'data' use: payload, sensorReading, customerRecord, configEntry
Instead of 'flag' use: isValid, hasPermission, isActive, wasProcessed
Instead of 'buffer' use: inputBuffer, readChunk, outputAccumulator
Instead of 'result' use: computedScore, lookupOutcome, validationStatus
Instead of 'count' use: itemCount, retryAttempt, errorTally
Instead of 'index' use: currentPosition, insertionPoint, searchOffset
Instead of 'item' use: currentOrder, selectedProduct, queueEntry
Instead of 'obj' use: the specific type name — person, connection, response
Instead of 'str' use: formattedName, rawInput, encodedPayload
Instead of 'num' use: quantity, threshold, portNumber

THE PRINCIPLE
The name should answer: WHAT does this represent in the problem domain? Not WHAT type is it.

BAD: temp <- customer.getAddress()    what IS temp?
GOOD: billingAddress <- customer.getAddress()    clear purpose

BAD: data <- sensor.read()    could be anything
GOOD: temperatureReading <- sensor.read()    obvious meaning

ALWAYS ALLOWED
- Single characters: x, y, z, i, j, k (math and loop convention)
- Compound words containing banned words: connectionState, errorHandler, dataProcessor
- Type-qualified names: the type carries semantics (amount as Money, thrust as Dimension)

See Q290 for the complete banned list. See Q296 for research evidence.

defines module qa.naming.alternatives

  defines function

    <?-
      Shows descriptive alternatives to banned names.
      Every variable name tells you what it represents.
    -?>
    processOrder() as pure
      ->
        unitPrice as Float
        orderQuantity as Integer
        discountPercent as Float
      <-
        finalPrice as Float: 0.0

      // NOT: temp <- unitPrice * orderQuantity
      subtotal <- unitPrice * #^ orderQuantity

      // NOT: value <- subtotal * (1.0 - discountPercent)
      discountedAmount <- subtotal * (1.0 - discountPercent)

      finalPrice := discountedAmount

    <?-
      Shows compound words with banned roots — these ARE allowed.
    -?>
    describeConnection() as pure
      ->
        connectionState as String
        errorHandler as String
        dataProcessor as String
      <-
        rtn as String: `${connectionState}: ${errorHandler} via ${dataProcessor}`

  defines program

    NamingAlternativesDemo()
      stdout <- Stdout()

      // GOOD: descriptive names
      totalPrice <- processOrder(29.99, 3, 0.1)
      stdout.println(`Order total: ${totalPrice}`)

      // GOOD: compound words with banned roots are allowed
      summary <- describeConnection("active", "retryHandler", "jsonProcessor")
      stdout.println(summary)

      // GOOD: single-character math variables
      x <- 3.0
      y <- 4.0
      hypotenuse <- sqrt(x * x + y * y)
      stdout.println(`Hypotenuse: ${hypotenuse}`)

      // GOOD: type carries semantics
      orderCount <- 5
      isActive <- true
      customerName <- "Alice"
      stdout.println(`${customerName}: ${orderCount} orders, active=${isActive}`)

Java: no naming enforcement, relies on optional SonarQube rules. Python: PEP 8 naming is voluntary. Rust: clippy provides optional naming suggestions. Go: golint suggests naming conventions. EK9: compile-time naming enforcement — generic names are compiler errors, not warnings.

E11031 alternative banned descriptive naming rename replace variable

banned name alternatives
descriptive naming
renaming guide

      data <- sqrt(x * x + y * y)
      stdout.println(`Hypotenuse: ${data}`)

Explanation: 'data' is a banned non-descriptive variable name in EK9 — use a name that says what it represents (e.g. hypotenuse). See ek9 -h E11031 for details.


Question: How is EK9 structured compared to Pascal?

What is the Pascal-like structure of EK9?

How does EK9 code organization compare to other languages?

Answer:
EK9 source files have a Pascal-like structure with mandatory section headers. Like Pascal's type/var/procedure/function sections, EK9 uses 'defines class/record/function/program' sections.

FILE STRUCTURE (top to bottom)
1. #!ek9 — shebang line (required)
2. defines module — module declaration (like Pascal's unit/program name)
3. defines constant — constants (like Pascal's const section)
4. defines type — enumerations and constrained types (like Pascal's type section)
5. defines record — value types (like Pascal's record types)
6. defines function — standalone functions (like Pascal's function/procedure section)
7. defines trait — interfaces (no Pascal equivalent — like Java interfaces)
8. defines class — classes (like Pascal's object types)
9. defines component — DI-injectable components
10. defines program — entry points (like Pascal's program body)
11. //EOF — end marker (like Pascal's end.)

PASCAL COMPARISON
Pascal:                       EK9:
  unit MyUnit;                  defines module my.package
  type                          defines record
    TPoint = record               Point
      x: Real;                      x as Float: 0.0
      y: Real;                      y as Float: 0.0
    end;
  var                           defines constant
    MaxSize: Integer = 100;       MAX_SIZE <- 100
  procedure DoWork;             defines function
                                  doWork()
  begin ... end.                defines program
                                  Main()

KEY DIFFERENCES FROM PASCAL
- No semicolons or 'begin/end' — indentation defines scope
- No 'var' section — variables declared inline with <-
- Multiple sections of same type allowed (for grouping)
- Sections can appear in any order (except 'defines module' must be first)

See Q893 for section header details. See Q93 for class definitions.

defines module qa.syntax.pascalstructure

  defines constant

    <?-
      Constants section — like Pascal's 'const' section.
    -?>
    MAX_RETRIES <- 3
    GREETING_PREFIX <- "Hello"

  defines record

    <?-
      Record section — like Pascal's 'type' with record types.
    -?>
    Dimensions
      widthMm as Float: 0.0
      heightMm as Float: 0.0

      Dimensions()
        ->
          widthMm as Float
          heightMm as Float
        this.widthMm :=: widthMm
        this.heightMm :=: heightMm

      default operator

  defines function

    <?-
      Function section — like Pascal's 'function'/'procedure'.
    -?>
    calculateArea() as pure
      -> dims as Dimensions
      <- rtn as Float: dims.widthMm * dims.heightMm

    formatGreeting() as pure
      -> recipientName as String
      <- rtn as String: `${GREETING_PREFIX}, ${recipientName}`

  defines class

    <?-
      Class section — like Pascal's 'object' type section.
    -?>
    RetryCounter
      attemptNumber as Integer: 0

      increment()
        if attemptNumber < MAX_RETRIES
          attemptNumber++

      hasRetriesLeft() as pure
        <- rtn as Boolean: attemptNumber < MAX_RETRIES

      getCurrentAttempt() as pure
        <- rtn as Integer: attemptNumber

      default operator

  defines program

    <?-
      Program section — like Pascal's main program body.
    -?>
    PascalStructureDemo()
      stdout <- Stdout()

      // Using constants
      stdout.println(formatGreeting("Steve"))

      // Using records
      boxSize <- Dimensions(200.0, 150.0)
      areaValue <- calculateArea(boxSize)
      stdout.println(`Box area: ${areaValue} sq mm`)

      // Using classes
      retries <- RetryCounter()
      retries.increment()
      retries.increment()
      stdout.println(`Attempt: ${retries.getCurrentAttempt()}, retries left: ${retries.hasRetriesLeft()}`)

Pascal: unit name; type TMyType = ...; var x: Integer; procedure DoWork; begin end. EK9 follows the same pattern with 'defines' sections. Java: no sections, classes/interfaces at top level. Python: no sections, mix everything. Go: package name; type/func/var at top level. EK9: Pascal-like 'defines class/function/record/program' mandatory sections.

Pascal defines file layout module organization section structure unit

file structure
Pascal comparison
code organization