Question: What does the $ operator actually do in EK9?

How do I convert a value to String in EK9?

Answer:
The $ operator in EK9 is the STRING CONVERSION operator. It calls the _string() method on any value and returns a String representation. That is ALL it does.

$ IS NOT STRING INTERPOLATION
In JavaScript and shell scripting, $variable inserts a value into a string. In EK9, $ only converts to String. String interpolation uses backtick strings with ${...} syntax.

$ IS NOT SAFE ACCESS
In Kotlin, $variable inside strings does interpolation. In TypeScript, $ has no special meaning. In EK9, $ is purely _string() conversion.

BASIC USAGE
  age <- 25
  ageText <- $age
This calls age._string() and stores the result "25" in ageText.

WITH DIFFERENT TYPES
  price <- 19.99
  priceText <- $price       converts Float to String
  flag <- true
  flagText <- $flag          converts Boolean to String
  name <- "Steve"
  nameText <- $name          identity on String (already String)

IN BACKTICK STRINGS
When you write `Age: ${$age}`, the outer ${...} is the interpolation syntax and the inner $ converts age to String. Both are needed when interpolating non-String values.

IMPLICIT CONVERSION
Inside backtick interpolation, the $ conversion is called implicitly if needed. But as a standalone operator, you must write $value explicitly.

CUSTOM TYPES
Any class can define operator $ to control its String representation. The operator must be pure and return String.

See Q897 for $ and #? together. See Q242 for all conversion operators. See Q909 for $ inside backtick strings.

defines module qa.operators.dollarstringconv

  defines function

    describeAge() as pure
      -> age as Integer
      <- rtn as String: String()

      // $ converts Integer to String via _string()
      ageText <- $age
      rtn: `Age is ${ageText}`

    describePrice() as pure
      -> price as Float
      <- rtn as String: String()

      // $ converts Float to String
      priceText <- $price
      rtn: `Price: ${priceText}`

  defines program

    DollarConversionDemo()
      stdout <- Stdout()

      // === $ converts Integer to String ===

      age <- 25
      ageText <- $age
      stdout.println(ageText)

      // === $ converts Float to String ===

      price <- 19.99
      priceText <- $price
      stdout.println(priceText)

      // === $ converts Boolean to String ===

      isActive <- true
      activeText <- $isActive
      stdout.println(activeText)

      // === Using functions that demonstrate $ ===

      stdout.println(describeAge(30))
      stdout.println(describePrice(9.99))

      // === $ inside backtick: ${variable} ===

      count <- 42
      stdout.println(`Count: ${count}`)

Java: toString() method call. Python: str() built-in function. JavaScript: String() or template literals with ${...}. Ruby: to_s method. Go: fmt.Sprintf or String() method. EK9: $value is a prefix operator that calls _string(), completely separate from backtick interpolation.

_string backtick conversion dollar interpolation operator prefix string

$ operator
string conversion
_string method

Explanation: Double-quoted strings do NOT support interpolation — '${age}' is a literal string, so the 'age' variable becomes unreferenced. Use backtick strings for interpolation (`${...}`) or the $ prefix operator ($age). See ek9 -h E08090 for details.


Question: How does $ work inside backtick strings in EK9?

How do I interpolate non-String values in EK9 backtick strings?

Answer:
In EK9 backtick strings, there are TWO separate mechanisms at work. Understanding the difference is critical.

BACKTICK INTERPOLATION: ${...}
The ${...} syntax inside backtick strings is the INTERPOLATION mechanism. It evaluates the expression inside and inserts the result into the string. This ONLY works in backtick strings, never in double-quoted strings.

DOLLAR CONVERSION: $
The $ operator converts any value to String by calling _string(). It is a PREFIX operator used outside or inside interpolation.

COMBINED: ${$variable}
When you write `Age: ${$age}`, two things happen:
1. The inner $age calls age._string() to convert Integer to String
2. The outer ${...} interpolates that String result into the backtick string

WHEN $ IS OPTIONAL INSIDE ${}
For String variables, you can write ${name} without the inner $ because the value is already a String. For non-String types (Integer, Float, Boolean, custom types), the $ conversion happens implicitly inside ${} interpolation.

EXAMPLES
  name <- "Steve"
  stdout.println(`Hello ${name}`)       String, no $ needed
  age <- 25
  stdout.println(`Age: ${$age}`)        Integer, $ converts
  stdout.println(`Age: ${age}`)         also works, implicit conversion

DOUBLE-QUOTED STRINGS
Double-quoted strings like "Hello ${name}" do NOT support interpolation. The ${...} is treated as literal text. Always use backtick strings for interpolation.

See Q908 for $ as standalone operator. See Q242 for all conversion operators. See Q897 for operator overview.

defines module qa.operators.dollarbacktick

  defines function

    formatPerson() as pure
      ->
        name as String
        age as Integer
      <- rtn as String: String()

      // String variable: ${name} works directly
      // Integer variable: ${age} uses $ to convert
      rtn: `${name} is ${age} years old`

  defines program

    DollarBacktickDemo()
      stdout <- Stdout()

      // === String inside backtick — no $ needed ===

      name <- "Steve"
      stdout.println(`Hello ${name}`)

      // === Integer inside backtick — $ converts ===

      age <- 25
      stdout.println(`Age: ${age}`)

      // === Float inside backtick ===

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

      // === Boolean inside backtick ===

      isEnabled <- true
      stdout.println(`Active: ${isEnabled}`)

      // === Multiple values ===

      stdout.println(formatPerson("Alice", 30))

      // === Expression inside backtick ===

      x <- 10
      y <- 20
      stdout.println(`Sum: ${(x + y)}`)

Java: String.format() or + concatenation, no interpolation. Python: f-strings with {variable}. JavaScript: template literals with ${expression}. Kotlin: "$variable" or "${expression}". Ruby: "#{expression}". EK9: backtick strings with ${expression}, $ operator for explicit conversion, double-quoted strings have NO interpolation.

backtick conversion dollar expression interpolation string template

backtick interpolation
${} syntax
$ inside interpolation

Explanation: Double-quoted strings do NOT interpolate, so '${age}' is literal text and 'age' becomes unreferenced; use backtick strings for interpolation. See ek9 -h E08090 for details.


Question: How do I implement the $ operator on a custom class in EK9?

How do I define _string() for my type in EK9?

How does a class customize its String representation in EK9?

How do I override the $ operator in EK9?

Answer:
Any EK9 class can define the $ operator to control how it converts to a String. The operator must be declared 'as pure', take no parameters, and return a String.

BASIC PATTERN
  MyClass
    name as String: String()

    operator $ as pure
      <- rtn as String: name

The $ operator returns whatever String representation makes sense for the type.

RULES FOR operator $
1. Must be declared 'as pure' — it extracts information without side effects
2. Must return String — the return type is always String
3. Takes no parameters — it operates on 'this' only
4. Can use backtick interpolation to build complex strings

USING BACKTICK IN operator $
You can use backtick interpolation inside the operator to combine fields:
  operator $ as pure
    <- rtn as String: `${name} (${$score})`

IMPLICIT USAGE
When you write `${myObject}` in a backtick string, the compiler calls the $ operator automatically. When you write $myObject as a standalone expression, it also calls the $ operator.

DEFAULT OPERATOR
If you use 'default operator' on a class, EK9 generates a default $ operator that combines all fields. You can override it with your own implementation.

See Q908 for $ basics. See Q242 for all conversion operators. See Q245 for complete custom type examples.

defines module qa.operators.dollarcustom

  defines class

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

      Colour() as pure
        ->
          r as Integer
          g as Integer
          b as Integer
        this.red :=: r
        this.green :=: g
        this.blue :=: b

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

      override operator ? as pure
        <- rtn as Boolean: red? and green? and blue?

    NamedColour
      colourName as String: String()
      colour as Colour: Colour()

      NamedColour()
        ->
          colourName as String
          colour as Colour
        this.colourName: colourName
        this.colour: colour

      operator $ as pure
        <- rtn as String: `${colourName}: ${colour}`

      override operator ? as pure
        <- rtn as Boolean: colourName? and colour?

  defines program

    CustomDollarDemo()
      stdout <- Stdout()

      // === Custom $ on Colour ===

      red <- Colour(255, 0, 0)
      stdout.println($red)

      // === Custom $ on NamedColour (delegates to Colour.$) ===

      namedRed <- NamedColour("Red", red)
      stdout.println($namedRed)

      // === $ inside backtick interpolation ===

      blue <- Colour(0, 0, 255)
      stdout.println(`Blue is: ${blue}`)

      // === Multiple custom types ===

      green <- Colour(0, 255, 0)
      namedGreen <- NamedColour("Green", green)
      stdout.println(`Colour: ${namedGreen}`)

Java: override toString() method. Python: define __str__() or __repr__(). Rust: implement Display trait. Go: define String() method. Kotlin: override toString(). JavaScript: define toString() method. EK9: define 'operator $ as pure' returning String.

class conversion custom dollar operator override pure string

operator $
custom string conversion
class operators

Explanation: The $ operator must be declared 'as pure' because it extracts information without side effects. Omitting 'as pure' triggers E07500. See ek9 -h E07500 for details.

Oracle can add the $ (string) operator to an existing class with the correct pure signature.


Question: What does the #? operator mean in EK9?

Is # a comment in EK9?
How does hashcode work in EK9?
What does #? return in EK9?

Answer:
The #? operator in EK9 calls _hashcode() and returns an Integer hash value. It is NOT a Python-style comment and NOT a Ruby-style method reference.

#? IS _hashcode()
The #? operator is read as 'hash-query' and calls the _hashcode() method:
  hashVal <- #? myObject
This returns an Integer suitable for use as a Dict key or equality checking.

IMPORTANT DISTINCTIONS
- In Python, # starts a comment. In EK9, // starts a comment.
- In Ruby, #method is an unbound method reference. EK9 has no such syntax.
- In EK9, # is part of TWO operator symbols: #? (hashcode) and #^ (promote).
- The # character has NO meaning on its own in EK9.

RETURN TYPE
#? always returns Integer. The method signature is:
  operator #? as pure
    <- rtn as Integer: ...

NOTE THE LOWERCASE 'c'
The method is _hashcode (lowercase c), NOT _hashCode (Java style camelCase). This is a common mistake.

IMPLEMENTING #? ON CUSTOM TYPES
  operator #? as pure
    <- rtn as Integer: #?field1 + #?field2
Combine hash values of fields using arithmetic.

DEFAULT OPERATOR
Using 'default operator' generates a #? implementation that combines all fields.

See Q897 for $ and #? together. See Q912 for the #^ promote operator. See Q242 for all conversion operators.

defines module qa.operators.hashqhashcode

  defines record

    Tag
      name as String: String()
      priority as Integer: 0

      Tag()
        ->
          name as String
          priority as Integer
        this.name :=: name
        this.priority :=: priority

      default operator

  defines program

    HashcodeDemo()
      stdout <- Stdout()

      // === #? on String ===

      greeting <- "Hello"
      greetingHash <- #? greeting
      stdout.println(`String hash: ${greetingHash}`)

      // === #? on Integer ===

      number <- 42
      numberHash <- #? number
      stdout.println(`Integer hash: ${numberHash}`)

      // === #? on Float ===

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

      // === #? on custom record ===

      versionTag <- Tag("version", 1)
      versionHash <- #? versionTag
      stdout.println(`Tag hash: ${versionHash}`)

      // === Different values produce different hashes ===

      buildTag <- Tag("build", 2)
      buildHash <- #? buildTag
      stdout.println(`Build hash: ${buildHash}`)

Java: hashCode() method (camelCase). Python: hash() built-in, # is comment. Ruby: hash method, # is interpolation in strings. Rust: Hash trait derive. Go: no built-in hash interface. Kotlin: hashCode() like Java. EK9: #? operator calls _hashcode() (lowercase c), returns Integer.

comment dict equality hash hashcode integer key operator python

#? operator
_hashcode method
hash values

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

Explanation: The #? operator must return Integer, not String. Defining #? to return String triggers E07550 because hashcode must always return an Integer value. See ek9 -h E07550 for details.


Question: What does the #^ operator mean in EK9?

How does type promotion work in EK9?
What is the promote operator in EK9?
How do I widen a type in EK9?

Answer:
The #^ operator in EK9 calls _promote() for type widening. It converts a value to a wider or more general type. It is NOT list comprehension and NOT reflection.

#^ IS _promote()
The #^ operator promotes a value to a wider type:
  anInteger <- 42
  asFloat <- #^ anInteger
This converts Integer 42 to Float 42.0 via the _promote() method.

IMPORTANT DISTINCTIONS
- In Python, [x**2 for x in list] is list comprehension. EK9 has NO list comprehension.
- In some languages, ^ is XOR or power. In EK9, #^ is specifically the promote operator.
- The # and ^ characters together form this specific operator. Neither has meaning alone as an operator.

TYPE WIDENING EXAMPLES
- Integer to Float (narrower numeric to wider numeric)
- Character to String (single char to string)
- Any type with a defined #^ operator to its target type

RETURN TYPE RULE
The #^ operator MUST return a DIFFERENT type from the source. If you define #^ on a class, the return type cannot be the same class. This is enforced by the compiler.

IMPLEMENTING #^ ON CUSTOM TYPES
  operator #^ as pure
    <- rtn as Float: Float(internalValue)
Must be pure, takes no parameters, returns the wider type.

AUTOMATIC PROMOTION
The compiler uses #^ automatically when a narrower type is used where a wider type is expected, such as passing an Integer to a function expecting Float.

See Q911 for the #? hashcode operator. See Q839 for promote return type rules. See Q242 for all conversion operators.

defines module qa.operators.hashcaretpromote

  defines class

    Measurement
      amount as Integer: 0

      Measurement() as pure
        -> amount as Integer
        this.amount :=: amount

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

      operator $ as pure
        <- rtn as String: `Measurement(${amount})`

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

  defines program

    PromoteDemo()
      stdout <- Stdout()

      // === #^ on Integer — promotes to Float ===

      wholeNumber <- 42
      promoted <- #^ wholeNumber
      stdout.println(`Integer ${wholeNumber} promoted to Float: ${promoted}`)

      // === #^ on custom class ===

      measurement <- Measurement(100)
      asFloat <- #^ measurement
      stdout.println(`Measurement promoted: ${asFloat}`)

      // === Promotion preserves value ===

      small <- 1
      smallFloat <- #^ small
      stdout.println(`Small promoted: ${smallFloat}`)

      large <- 999999
      largeFloat <- #^ large
      stdout.println(`Large promoted: ${largeFloat}`)

Java: implicit widening (int to double). Python: implicit numeric promotion. Rust: explicit 'as' casting (i32 as f64). Go: explicit conversion float64(intVal). Kotlin: toDouble() method. JavaScript: implicit coercion. EK9: #^ operator calls _promote(), explicit and type-safe, must return different type.

caret conversion float hash integer operator promote type widening

#^ operator
_promote method
type widening

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

Explanation: The #^ promote operator must return a DIFFERENT type from the class it is defined on. Returning the same type (Measurement) defeats the purpose of promotion and triggers E07420. See ek9 -h E07420 for details.


Question: How does $ work on different types in EK9?

What does $ return for Integer, Float, Boolean?

Answer:
The $ operator calls _string() on ANY type that implements it. Every built-in type has a $ operator, and custom classes can define their own. The result is always a String.

$ ON BUILT-IN TYPES
  $25           returns "25" (Integer to String)
  $19.99        returns "19.99" (Float to String)
  $true         returns "true" (Boolean to String)
  $"hello"      returns "hello" (String to String, identity)
  $'A'          returns "A" (Character to String)

$ ON COLLECTIONS
List and other collections implement $ to produce a string representation:
  names <- ["Alice", "Bob"]
  namesText <- $names
The exact format depends on the type's _string() implementation.

$ ON CUSTOM CLASSES
Any class with 'operator $ as pure' can be converted:
  colour <- Colour(255, 0, 0)
  colourText <- $colour     calls Colour's $ operator

CONSISTENCY
$ ALWAYS means _string(). There are no exceptions, no special cases, no context-dependent behavior. It is the single, universal way to get a String representation of any value.

IN BACKTICK STRINGS
Inside backtick interpolation ${...}, the $ conversion is called implicitly for non-String types. But as a standalone prefix operator, $ must be written explicitly.

See Q908 for $ basics. See Q909 for $ in backtick strings. See Q910 for implementing $ on custom types.

defines module qa.operators.dollarvaried

  defines class

    Score
      points as Integer: 0
      label as String: String()

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

      operator $ as pure
        <- rtn as String: `${label}=${points}`

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

  defines program

    DollarVariedDemo()
      stdout <- Stdout()

      // === $ on Integer ===

      count <- 42
      countText <- $count
      stdout.println(`Integer: ${countText}`)

      // === $ on Float ===

      ratio <- 3.14
      ratioText <- $ratio
      stdout.println(`Float: ${ratioText}`)

      // === $ on Boolean ===

      isActive <- true
      activeText <- $isActive
      stdout.println(`Boolean: ${activeText}`)

      // === $ on String (identity) ===

      name <- "Steve"
      nameText <- $name
      stdout.println(`String: ${nameText}`)

      // === $ on Character ===

      letter <- 'A'
      letterText <- $letter
      stdout.println(`Character: ${letterText}`)

      // === $ on custom class ===

      score <- Score(95, "Math")
      scoreText <- $score
      stdout.println(`Custom: ${scoreText}`)

      // === $ on List ===

      items <- ["one", "two", "three"]
      itemsText <- $items
      stdout.println(`List: ${itemsText}`)

Java: toString() on all objects. Python: str() works on all types. Rust: Display trait must be implemented. Go: Stringer interface optional. JavaScript: String() or toString(). EK9: $ operator universal, calls _string(), every built-in type supports it.

boolean conversion dollar float integer list string types universal

$ on various types
universal string conversion

Explanation: EK9 does not have a toString() method like Java. Use the $ prefix operator ($count) to convert any value to String. The $ calls the _string() method. See ek9 -h E50060 for details.


Question: Does # mean comment in EK9 like Python?

How is # different in EK9 versus Python?

Answer:
No. In EK9, # is NOT a comment character. EK9 uses // for single-line comments. The # character appears ONLY in two specific operator symbols: #? and #^. That is it.

#? = HASHCODE
The #? operator calls _hashcode() and returns an Integer:
  hash <- #? value
This is analogous to Java's hashCode() but uses operator syntax.

#^ = PROMOTE
The #^ operator calls _promote() for type widening:
  wider <- #^ narrowValue
This converts to a wider type (e.g., Integer to Float).

THAT IS THE COMPLETE LIST
There are exactly TWO operators using #:
- #? (hashcode) — returns Integer
- #^ (promote) — returns a wider type

There are also #< (prefix) and #> (suffix) operators:
- #< extracts a prefix (e.g., first character of String)
- #> extracts a suffix (e.g., last character of String)

NO OTHER # OPERATORS EXIST
These do NOT exist in EK9:
- # alone (not a comment, not an operator)
- ## (not an operator)
- #! (only valid on first line as shebang)
- #$ (not an operator)
- #@ (not an operator)

COMMENTS IN EK9
// This is a comment in EK9
/* This is also a comment in EK9 */

See Q911 for #? in detail. See Q912 for #^ in detail. See Q903 for all operators that do not exist.

defines module qa.operators.hashnotpython

  defines record

    Point
      xPos as Float: 0.0
      yPos as Float: 0.0

      Point()
        ->
          x as Float
          y as Float
        this.xPos :=: x
        this.yPos :=: y

      default operator

  defines program

    HashOperatorsDemo()
      stdout <- Stdout()

      // === #? on various types ===

      name <- "Hello"
      nameHash <- #? name
      stdout.println(`String hash: ${nameHash}`)

      number <- 42
      numberHash <- #? number
      stdout.println(`Integer hash: ${numberHash}`)

      point <- Point(3.0, 4.0)
      pointHash <- #? point
      stdout.println(`Point hash: ${pointHash}`)

      // === #^ promote ===

      intVal <- 100
      floatVal <- #^ intVal
      stdout.println(`Promoted: ${floatVal}`)

      // === #< prefix and #> suffix ===

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

      // === Comments use // not # ===
      // This is a valid EK9 comment

      stdout.println("# is NOT a comment in EK9")

Python: # is line comment. Ruby: # is line comment, #{ } is string interpolation. Shell: # is line comment. EK9: # is NOT a comment, // is line comment, #? is hashcode operator, #^ is promote operator, #< is prefix, #> is suffix.

comment hash hashcode not-comment operator prefix promote python suffix

# operators
not Python comment
#? and #^ only

Explanation: The #^ (promote) operator is not defined on the Point record. Point has #? (hashcode) via 'default operator' but not #^. Use #? for hashcode. See ek9 -h E07620 for details.


Question: Does EK9 have list comprehension like Python?

What replaces list comprehension in EK9?

How do I filter and transform lists in EK9?

Answer:
EK9 has NO list comprehension syntax. Python's [x for x in list if condition] does NOT exist in EK9. AI models trained on Python frequently generate this pattern. It will not compile.

THE AI MISTAKE
AI generates patterns like:
  result <- [item * 2 for item in items]
  filtered <- [x for x in data if x > 0]
None of these compile. EK9 has no square bracket comprehension syntax.

THE EK9 WAY: STREAM PIPELINES
EK9 uses stream pipelines with cat, filter, map, and collect:

FILTERING
  for item in items
    if item?
      process(item)

TRANSFORMING
Use functions to transform values and build new collections:
  doubled <- List() of Integer
  for item in items
    doubled += item * 2

FUNCTIONAL APPROACH
EK9 supports higher-order functions for transformation:
  doubleValue() as pure
    -> value as Integer
    <- rtn as Integer: value * 2

WHY NO COMPREHENSION
List comprehension is syntactic sugar that mixes iteration, filtering, and transformation into one dense expression. EK9 prefers explicit, readable steps. Each operation is visible and debuggable.

COLLECTION BUILDING
Build collections explicitly:
  result <- List() of String
  for name in names
    if name?
      result += name

See Q274 for return statement mistakes. See Q275 for break/continue mistakes. See Q281 for verifying AI code.

defines module qa.aimistakes.nocomprehension

  defines function

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

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

  defines program

    NoComprehensionDemo()
      stdout <- Stdout()

      // === Building a filtered list (replaces list comprehension) ===

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

      positives <- List() of Integer
      for num in numbers
        if isPositive(num)
          positives += num

      stdout.println(`Positives: ${positives}`)

      // === Building a transformed list ===

      doubled <- List() of Integer
      for num in numbers
        doubled += doubleNumber(num)

      stdout.println(`Doubled: ${doubled}`)

      // === Combined filter and transform ===

      doubledPositives <- List() of Integer
      for num in numbers
        if isPositive(num)
          doubledPositives += doubleNumber(num)

      stdout.println(`Doubled positives: ${doubledPositives}`)

      // === Simple collection from range ===

      squares <- List() of Integer
      for i in 1 ... 5
        squares += i * i

      stdout.println(`Squares: ${squares}`)

Python: [x for x in list if cond] comprehension. Haskell: [x | x <- list, cond] comprehension. Scala: for yield comprehension. Kotlin: list.filter{}.map{}. JavaScript: array.filter().map(). EK9: NO comprehension syntax, use for loops with explicit collection building or stream pipelines.

ai comprehension filter list map mistake pipeline python stream transform wrong

no list comprehension
stream pipelines
collection building

Explanation: EK9 has NO list comprehension. Use explicit for loops to build collections, or use stream pipelines for functional-style processing. See ek9 -h E01081 for details.


Question: How do I declare multiple parameters on a function in EK9?

Can I put all parameters on one line in EK9?

What does the -> arrow mean for parameters in EK9?

How do I use the parameter arrow in EK9?

Answer:
EK9 uses ONE -> arrow followed by indented parameters below it. Parameters are NOT comma-separated on one line.

CORRECT SYNTAX
One -> arrow, then each parameter on its own indented line:
  greet()
    ->
      name as String
      age as Integer
    <- rtn as String: `${name} is ${$age}`

SINGLE PARAMETER SHORTHAND
For a single parameter, you can put it on the same line as ->:
  greet()
    -> name as String
    <- rtn as String: `Hello ${name}`

WHAT NOT TO DO
- Do NOT use commas between parameters
- Do NOT put multiple parameters on one line
- Do NOT use multiple -> arrows
- Do NOT use parentheses around parameters

WRONG PATTERNS (do not compile)
  greet(name as String, age as Integer)    WRONG: not C-style
  greet()
    -> name as String, age as Integer      WRONG: no commas
  greet()
    -> name as String
    -> age as Integer                      WRONG: only one ->

CONSTRUCTOR PARAMETERS
Constructors follow the same rule:
  MyClass()
    ->
      name as String
      score as Integer
    this.name :=: name
    this.score :=: score

See Q894 for constructor parameter syntax. See Q893 for section headers. See Q907 for EK9 structure overview.

defines module qa.syntaxrules.arrowparams

  defines function

    // Single parameter — shorthand on same line as ->
    greetSimple() as pure
      -> name as String
      <- rtn as String: `Hello ${name}`

    // Multiple parameters — -> then indented below
    greetPerson() as pure
      ->
        name as String
        age as Integer
      <- rtn as String: `${name} is ${age}`

    // Three parameters
    formatEntry() as pure
      ->
        key as String
        setting as String
        index as Integer
      <- rtn as String: `${index}: ${key}=${setting}`

  defines program

    ParamArrowDemo()
      stdout <- Stdout()

      // === Single parameter ===

      stdout.println(greetSimple("Steve"))

      // === Multiple parameters ===

      stdout.println(greetPerson("Alice", 30))

      // === Three parameters ===

      stdout.println(formatEntry("userName", "Bob", 1))
      stdout.println(formatEntry("userRole", "dev", 2))

Java: method(String name, int age) comma-separated in parentheses. Python: def method(name, age) comma-separated. Rust: fn method(name: String, age: i32) comma-separated. Go: func method(name string, age int) comma-separated. EK9: ONE -> arrow then indented parameters, each on its own line.

arrow declaration function indent multiple parameter syntax

-> parameter syntax
multiple parameters
indentation

    greetPerson() as pure
      -> name as String
      -> age as Integer

Explanation: EK9 does not allow multiple -> arrows. Use ONE -> arrow then indent each parameter on its own line below. Multiple -> arrows trigger E01082. See ek9 -h E01082 for details.


Question: How do return values work in EK9 functions?

Can I have multiple return statements in EK9?

Answer:
EK9 uses ONE <- return declaration per function. There is NO return keyword. You declare a named return variable, give it a default value, then update it on different code paths.

BASIC PATTERN
  classify() as pure
    -> score as Integer
    <- label as String: "average"
    if score >= 90
      label: "excellent"
    else if score < 40
      label: "poor"

The variable 'label' is declared with <- and default "average". Different code paths update it with the : assignment operator. The compiler ensures all paths initialise the variable.

RULES
1. ONE <- per function (not multiple returns)
2. The return variable MUST have a default value or be initialised on ALL paths
3. Use : to update the return variable on conditional paths
4. There is NO return keyword — the named variable is automatically returned

NO EARLY RETURN
Instead of returning early, structure your logic so the return variable gets the right value:
  process() as pure
    -> text as String
    <- rtn as String: "empty"
    if text?
      rtn: text.upperCase()
If text is unset, rtn stays "empty". If set, rtn gets the uppercase version.

MULTIPLE RETURN VALUES
If you need multiple return values, return a record or class:
  splitName() as pure
    -> fullName as String
    <- parts as NameParts: NameParts()

WRONG PATTERNS
  <- a as String: "x"
  <- b as String: "y"       WRONG: only one <- allowed
  return "hello"             WRONG: no return keyword

See Q274 for why return was removed. See Q916 for parameter syntax. See Q50 for declared return variables.

defines module qa.syntaxrules.onereturn

  defines function

    // Simple return with default
    greet() as pure
      -> name as String
      <- message as String: "Hello, " + name

    // Conditional paths update the return variable
    classify() as pure
      -> score as Integer
      <- label as String: "average"

      excellentThreshold <- 90
      poorThreshold <- 40

      if score >= excellentThreshold
        label: "excellent"
      else if score < poorThreshold
        label: "poor"

    // Guard pattern instead of early return
    safeUpperCase() as pure
      -> text as String
      <- rtn as String: "empty"

      if text?
        rtn: text.upperCase()

  defines program

    OneReturnDemo()
      stdout <- Stdout()

      // === Simple return ===

      stdout.println(greet("Steve"))

      // === Conditional return ===

      stdout.println(`Score 95: ${classify(95)}`)
      stdout.println(`Score 30: ${classify(30)}`)
      stdout.println(`Score 60: ${classify(60)}`)

      // === Guard pattern ===

      stdout.println(`Upper: ${safeUpperCase("hello")}`)
      stdout.println(`Empty: ${safeUpperCase(String())}`)

Java: return statement, multiple returns common. Python: return statement, multiple returns common. Rust: implicit last expression or return. Go: named returns exist but return still needed. Kotlin: return statement. EK9: ONE named return variable with <-, NO return keyword, compiler verifies all paths.

arrow declaration function named return single value variable

<- return declaration
named return variable
no return keyword

<- label as String: "average"
      <- extra as Integer: 0

Explanation: EK9 allows only ONE <- return declaration per function. Multiple <- arrows trigger E01083. Declare a single named return variable and update it on different code paths. See ek9 -h E01083 for details.


Question: Is $ string interpolation or safe access in EK9?

Does $ do interpolation in EK9?
Is $ a safe access operator in EK9?
What does $ NOT do in EK9?

Answer:
No. $ does NOT do string interpolation. $ does NOT do safe access. $ converts ANY value to String by calling _string(). That is the ONLY thing it does.

WHAT $ DOES
$ is a PREFIX operator that calls _string() on any value and returns a String:
  age <- 25
  ageText <- $age         calls Integer._string(), returns "25"
  price <- 19.99
  priceText <- $price     calls Float._string(), returns "19.99"
  flag <- true
  flagText <- $flag       calls Boolean._string(), returns "true"

WHAT $ DOES NOT DO
1. NOT string interpolation — that is backtick ${...}
2. NOT safe access — use ? suffix for isSet checks
3. NOT variable sigil — EK9 variables have no sigils
4. NOT template literal marker — backtick strings handle that
5. NOT currency symbol — it is an operator

COMMON CONFUSION WITH BACKTICK STRINGS
Inside backtick strings:
  `Hello ${name}`      ${...} is interpolation, not $ operator
  `Age: ${$age}`       inner $ is the operator, outer ${} is interpolation

Standalone:
  text <- $value         this IS the $ operator (conversion)
  msg <- "${value}"     this does NOT interpolate (double-quoted string)

SAFE ACCESS USES ?
For checking if a value is set, use the ? suffix operator:
  if value?
    process(value)
$ has nothing to do with null safety or set checking.

See Q908 for $ basics. See Q909 for $ in backtick strings. See Q898 for ? isSet operator.

defines module qa.operators.dollarnotinterp

  defines function

    // $ converts to String, nothing else
    convertAll() as pure
      ->
        number as Integer
        ratio as Float
        isEnabled as Boolean
      <- rtn as String: String()

      numText <- $number
      ratioText <- $ratio
      enabledText <- $isEnabled
      rtn: `${numText}, ${ratioText}, ${enabledText}`

  defines program

    DollarNotInterpDemo()
      stdout <- Stdout()

      // === $ is _string() conversion ===

      age <- 25
      ageText <- $age
      stdout.println(`Age text: ${ageText}`)

      // === $ is NOT interpolation ===
      // Interpolation uses backtick ${...}

      name <- "Steve"
      stdout.println(`Hello ${name}`)

      // === $ inside backtick is conversion ===

      score <- 100
      stdout.println(`Score: ${score}`)

      // === ? is isSet, NOT $ ===

      count <- 42
      stdout.println(`Is set: ${count?}`)

      // === Multiple conversions ===

      stdout.println(convertAll(10, 3.14, true))

      // === $ on already-String is identity ===

      greeting <- "World"
      greetText <- $greeting
      stdout.println(`Greeting: ${greetText}`)

JavaScript: $ in template literals ${expr}. Kotlin: $variable in strings for interpolation. PHP: $variable as variable sigil. Perl: $scalar as variable sigil. Shell: $VAR for variable expansion. EK9: $ is ONLY a prefix operator calling _string(), never interpolation, never variable sigil, never safe access.

access conversion dollar interpolation misconception not operator prefix safe string

$ is not interpolation
$ is not safe access
$ is _string() only

Explanation: Double-quoted strings do NOT support interpolation — '${age}' is literal text, so the 'age' variable becomes unreferenced. Use backtick strings for interpolation or $ as a prefix operator. See ek9 -h E08090 for details.


Question: What is the complete # operator family in EK9?

Are there operators like ##, #!, #$ in EK9?

Answer:
EK9 has exactly FOUR operators that use the # character. No others exist.

THE COMPLETE # OPERATOR LIST

#? — HASHCODE
Calls _hashcode(), returns Integer:
  hash <- #? myObject
Used for Dict keys and equality support.

#^ — PROMOTE
Calls _promote(), returns a WIDER type:
  asFloat <- #^ anInteger
Converts a value to a more general type.

#< — PREFIX
Calls _prefix(), returns the opening part:
  first <- #< text
On String, returns the first character.

#> — SUFFIX
Calls _suffix(), returns the closing part:
  last <- #> text
On String, returns the last character.

OPERATORS THAT DO NOT EXIST
These are NOT valid EK9 operators:
- # alone (not a comment, not an operator)
- ## (does not exist)
- #! (only valid as shebang on line 1)
- #$ (does not exist)
- #@ (does not exist)
- #: (does not exist)
- #{ (does not exist)

SUMMARY TABLE
  #?  _hashcode()  -> Integer     hash value for Dict keys
  #^  _promote()   -> wider type  type widening/promotion
  #<  _prefix()    -> varies      extract prefix/first part
  #>  _suffix()    -> varies      extract suffix/last part

All four must be declared 'as pure' because they extract information without side effects.

See Q911 for #? in detail. See Q912 for #^ in detail. See Q242 for all conversion operators.

defines module qa.operators.hashsummary

  defines record

    Coord
      xPos as Float: 0.0
      yPos as Float: 0.0

      Coord()
        ->
          x as Float
          y as Float
        this.xPos :=: x
        this.yPos :=: y

      default operator

  defines program

    HashSummaryDemo()
      stdout <- Stdout()

      // === #? hashcode — returns Integer ===

      text <- "Hello"
      textHash <- #? text
      stdout.println(`#? String hash: ${textHash}`)

      number <- 42
      numHash <- #? number
      stdout.println(`#? Integer hash: ${numHash}`)

      coord <- Coord(3.0, 4.0)
      coordHash <- #? coord
      stdout.println(`#? Record hash: ${coordHash}`)

      // === #^ promote — returns wider type ===

      intVal <- 100
      floatVal <- #^ intVal
      stdout.println(`#^ Integer to Float: ${floatVal}`)

      // === #< prefix — returns first part ===

      word <- "Hello"
      first <- #< word
      stdout.println(`#< prefix: ${first}`)

      // === #> suffix — returns last part ===

      last <- #> word
      stdout.println(`#> suffix: ${last}`)

      // === All four on one value ===

      sample <- "World"
      stdout.println(`Value: ${sample}`)
      stdout.println(`  #? hash: ${(#? sample)}`)
      stdout.println(`  #< prefix: ${(#< sample)}`)
      stdout.println(`  #> suffix: ${(#> sample)}`)

Java: hashCode() method, no promote/prefix/suffix operators. Python: hash() function, # is comment. Rust: Hash trait, no prefix/suffix operators. Go: no hash interface, no operator symbols. Kotlin: hashCode() method, no operator symbols. EK9: #? hashcode, #^ promote, #< prefix, #> suffix — four operators, all pure.

complete family hash hashcode operator prefix promote suffix summary

# operator family
#? #^ #< #>
complete list

Explanation: EK9 does not have a hashCode() method like Java. Use the #? prefix operator to get the hashcode of any value. The #? calls the _hashcode() method. See ek9 -h E50060 for details.


Question: What is the complete list of banned variable names in EK9?

Show me all forbidden identifiers in EK9

Answer:
EK9 bans exactly 12 non-descriptive variable names at compile time via E11031: temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. The check is case-insensitive (Data, DATA, data all rejected).

These are NOT Java reserved keywords. They are generic, meaningless identifiers that hide intent. Names like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, arg, var, ret, res are NOT banned — only the 12 names above trigger E11031.

Separately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches. These shadow operators and cause confusing parse errors.

ALWAYS ALLOWED EXCEPTIONS:
- Single-character names: x, y, z, i, j, k (math variables, loop counters)
- Compound words containing banned roots: connectionState, errorHandler, dataProcessor
- Type-qualified descriptive names: orderCount, sensorReading, customerName

See Q290 for tiers of restriction. See Q906 for a renaming guide.

defines module qa.naming.completelist

  defines function

    <?-
      Demonstrates descriptive naming replacing banned names.
    -?>
    measureTemperature() as pure
      -> sensorReading as Float
      <- adjustedReading as Float: sensorReading + 0.5

  defines program

    CompleteListDemo()
      stdout <- Stdout()

      // GOOD: descriptive names instead of banned names
      sensorReading <- 42.5
      adjustedReading <- measureTemperature(sensorReading)
      stdout.println(`Reading: ${adjustedReading}`)

      // GOOD: single-character math variables (always allowed)
      x <- 3.0
      y <- 4.0
      stdout.println(`Coordinates: ${x}, ${y}`)

      // GOOD: compound words with banned roots (always allowed)
      connectionState <- "active"
      dataProcessor <- "json"
      stdout.println(`State: ${connectionState}, Processor: ${dataProcessor}`)

E11031 banned complete forbidden list naming variable

      data <- 42.5
      adjustedReading <- measureTemperature(data)

Explanation: The name 'data' is one of 12 banned non-descriptive names (E11031). Use a name describing what the variable represents. See ek9 -h E11031.


Question: Are EK9 banned names the same as Java reserved keywords?

Why is 'temp' banned if it is not a keyword?

Answer:
NO. EK9 banned names are descriptive names like temp, value, data — NOT language keywords. Java reserved keywords are things like 'class', 'public', 'void'. EK9 banned names are generic identifiers that carry no semantic meaning.

EK9 banned names are NOT Java reserved keywords. They are quality enforcement — the compiler rejects names that hide intent.

EK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive. Names like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, arg, var, ret, res are NOT banned.

Separately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.

ALWAYS ALLOWED EXCEPTIONS:
- Single-character names: x, y, z, i, j, k (math variables, loop counters)
- Compound words: connectionState, dataProcessor, bufferSize
- Descriptive names: customerName, orderTotal, retryLimit

defines module qa.naming.notjava

  defines function

    <?-
      Shows descriptive naming — banned names are NOT keywords.
    -?>
    computeDiscount() as pure
      ->
        originalPrice as Float
        discountRate as Float
      <- discountedPrice as Float: originalPrice * (1.0 - discountRate)

  defines program

    NotJavaKeywordsDemo()
      stdout <- Stdout()

      // 'result' is banned — NOT a keyword, just non-descriptive
      computedScore <- 88
      stdout.println(`Score: ${computedScore}`)

      // 'value' is banned — use what it represents
      originalPrice <- 49.99
      discountedPrice <- computeDiscount(originalPrice, 0.15)
      stdout.println(`Price: ${discountedPrice}`)

      // Compound words with banned roots: ALLOWED
      bufferSize <- 1024
      stateCode <- "TX"
      stdout.println(`Buffer size: ${bufferSize}, State: ${stateCode}`)

      // Single-char math vars: ALLOWED
      x <- 10.0
      y <- 20.0
      stdout.println(`Point: ${x}, ${y}`)

E11031 banned java keyword naming reserved

      val <- 88
      stdout.println(`Score: ${$val}`)

Explanation: The name 'val' is banned (E11031) — it is not a keyword but a generic non-descriptive name. Use a name like 'computedScore' that describes what the value represents.


Question: Why does my EK9 code fail with E11031 when I use 'data' as a variable name?

Why is 'data' rejected by the EK9 compiler?

Answer:
E11031 means you used a banned non-descriptive variable name. The name 'data' tells the reader nothing about what it represents. EK9 enforces naming quality at compile time — this is not a warning, it is an error.

EK9 banned names are NOT Java reserved keywords. They are generic identifiers rejected for quality reasons.

EK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive. Names like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, arg, var, ret, res are NOT banned.

Separately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.

FIX: Replace with a descriptive name. Instead of 'data', use 'payload', 'sensorReading', 'configEntry'.

ALWAYS ALLOWED EXCEPTIONS:
- Single-character names: x, y, z, i, j, k (math variables, loop counters)
- Compound words: dataProcessor, inputStream, configPath
- Descriptive alternatives: payload, threshold, customerRecord

defines module qa.naming.whycompileerror

  defines function

    <?-
      Shows how to fix E11031 by using descriptive names.
    -?>
    fetchPayload() as pure
      <- payload as String: "sensor-data-packet"

  defines program

    WhyCompileErrorDemo()
      stdout <- Stdout()

      // WRONG: data <- fetchPayload()   triggers E11031
      // FIXED: descriptive name
      payload <- fetchPayload()
      stdout.println(`Payload: ${payload}`)

      // WRONG: temp <- 42.5   triggers E11031
      // FIXED: descriptive name
      sensorReading <- 42.5
      stdout.println(`Sensor: ${sensorReading}`)

      // Compound words: ALLOWED (dataProcessor contains 'data' but is compound)
      dataProcessor <- "jsonParser"
      stdout.println(`Processor: ${dataProcessor}`)

      // Single-char loop counters: ALLOWED
      i <- 1
      k <- 10
      stdout.println(`Range: ${i} to ${k}`)

E11031 banned compile data error fix naming

E11031 troubleshooting
fixing banned names

      data <- fetchPayload()
      stdout.println(`Payload: ${data}`)

Explanation: The name 'data' is banned (E11031). Replace with a descriptive name like 'payload' that tells the reader what this variable holds.


Question: What variable names ARE allowed in EK9 despite the naming rules?

What exceptions exist to EK9 naming rules?

Are compound words with banned roots allowed?

Answer:
EK9 allows three categories of names that might seem like they would be banned:

1. SINGLE-CHARACTER NAMES: x, y, z, i, j, k, n, t — always allowed for math variables, loop counters, generic type parameters.

2. COMPOUND WORDS containing banned roots: connectionState, dataProcessor, bufferSize, inputValidator, configPath — the banned word is part of a larger descriptive name.

3. MATH/SCIENCE VARIABLES: theta, radius, alpha, delta — domain-specific short names that carry clear meaning in context.

EK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive. Names like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, arg, var, ret, res are NOT banned.

Separately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.

The rule is simple: bare generic names from the 12-name list are banned, but descriptive names (including compound words) are always fine.

defines module qa.naming.allowedexceptions

  defines function

    <?-
      Shows math variables — single-char and short science names.
    -?>
    computeHypotenuse() as pure
      ->
        x as Float
        y as Float
      <- hypotenuse as Float: sqrt(x * x + y * y)

  defines program

    AllowedExceptionsDemo()
      stdout <- Stdout()

      // ALLOWED: single-character math variables
      x <- 3.0
      y <- 4.0
      z <- computeHypotenuse(x, y)
      stdout.println(`Hypotenuse: ${z}`)

      // ALLOWED: single-character loop counters
      i <- 1
      j <- 2
      k <- 3
      stdout.println(`Counters: ${i}, ${j}, ${k}`)

      // ALLOWED: compound words with banned roots
      connectionState <- "active"
      dataProcessor <- "xml"
      bufferSize <- 4096
      configPath <- "/etc/app.conf"
      stdout.println(`State: ${connectionState}`)
      stdout.println(`Processor: ${dataProcessor}, Size: ${bufferSize}`)
      stdout.println(`Config: ${configPath}`)

      // ALLOWED: math/science domain names
      theta <- 1.57
      radius <- 10.0
      stdout.println(`Theta: ${theta}, Radius: ${radius}`)

E11031 allowed compound exception math naming single-char

      dat <- "active"
      dataProcessor <- "xml"
      bufferSize <- 4096
      configPath <- "/etc/app.conf"
      stdout.println(`State: ${dat}`)

Explanation: The bare name 'dat' is banned (E11031), but the compound word 'connectionState' is allowed because it describes what kind of state.


Question: How do I name function parameters without using banned names?

What parameter names are allowed in EK9 functions?

How do I fix E11031 in function signatures?

What should I name my function arguments in EK9?

Answer:
Function parameters follow the same banned name rules as all variables. Use names that describe what the parameter represents in the problem domain.

EK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive. Names like input, output, param, arg, str, num, item, result, count, index, list, map, set, key, status, state, type, config, var, ret, res are NOT banned.

Separately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.

INSTEAD OF BANNED PARAMS:
- 'val' -> 'price', 'threshold', 'measurement'
- 'tmp' -> 'swapHolder', 'intermediateResult'
- 'dat' -> 'payload', 'sensorReading'
- 'buf' -> 'readChunk', 'outputAccumulator'

ALWAYS ALLOWED EXCEPTIONS:
- Single-character: x, y, z, i, j, k (math variables, loop counters)
- Compound words: inputValidator, outputFormat, parameterCount
- Descriptive: customerName, orderTotal, maxRetries

defines module qa.naming.infunctions

  defines function

    <?-
      Shows descriptive parameter naming in functions.
    -?>
    formatUserGreeting() as pure
      ->
        customerName as String
        loyaltyTier as Integer
      <-
        greeting as String: String()

      goldThreshold <- 5
      tierLabel <- loyaltyTier > goldThreshold <- "Gold" : "Silver"
      greeting :=? `${customerName} (${tierLabel})`

    calculateShippingCost() as pure
      ->
        orderWeight as Float
        destinationZone as Integer
      <-
        shippingCost as Float: orderWeight * #^ destinationZone * 0.5

  defines program

    InFunctionsDemo()
      stdout <- Stdout()

      // GOOD: descriptive parameter names
      greeting <- formatUserGreeting("Alice", 7)
      stdout.println(`Greeting: ${greeting}`)

      shippingCost <- calculateShippingCost(2.5, 3)
      stdout.println(`Shipping: ${shippingCost}`)

      // Single-char math vars: ALLOWED
      x <- 5.0
      y <- 10.0
      stdout.println(`Dimensions: ${x} x ${y}`)

      // Compound words: ALLOWED
      inputValidator <- "regex"
      parameterCount <- 4
      stdout.println(`Validator: ${inputValidator}, Params: ${parameterCount}`)

E11031 argument banned function naming parameter

        val as Float
        destinationZone as Integer
      <-
        shippingCost as Float: val * #^ destinationZone * 0.5

Explanation: The parameter name 'val' is a banned non-descriptive name; use a meaningful identifier like 'orderWeight' that tells the caller what to pass. See ek9 -h E11031 for details.


Question: How do I name class fields without triggering E11031?

What field names are banned in EK9 classes?

How do I fix E11031 in class properties?

What should I name my class attributes in EK9?

Answer:
Class fields follow the same banned name rules as all variables. Use names that describe what the field represents in the domain model.

EK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive. Names like status, state, config, handle, str, num, item, result, input, output, count, index, list, map, set, key, type, param, arg, var, ret, res are NOT banned.

Separately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.

INSTEAD OF BANNED FIELDS:
- 'val' -> 'orderTotal', 'measurementReading'
- 'tmp' -> 'swapHolder', 'intermediateResult'
- 'dat' -> 'sensorPayload', 'configEntry'
- 'buf' -> 'readChunk', 'outputAccumulator'

ALWAYS ALLOWED EXCEPTIONS:
- Single-character: x, y, z, i, j, k (math variables, loop counters)
- Compound words: connectionState, errorCount, dataSource
- Domain descriptive: customerName, orderTotal, retryLimit

defines module qa.naming.inclasses

  defines class

    <?-
      Shows descriptive field naming in a class.
    -?>
    SensorDevice
      sensorId as String: String()
      currentReading as Float: Float()
      isCalibrated as Boolean: Boolean()

      SensorDevice()
        -> deviceId as String
        sensorId :=: deviceId

      recordMeasurement()
        -> measuredValue as Float
        currentReading := measuredValue
        isCalibrated := true

      operator $ as pure
        <- rtn as String: `${sensorId}: ${currentReading}`

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

  defines program

    InClassesDemo()
      stdout <- Stdout()

      // GOOD: descriptive field names inside class
      sensor <- SensorDevice("TH-001")
      sensor.recordMeasurement(22.5)
      stdout.println($sensor)

      // Compound words: ALLOWED as locals too
      errorCount <- 0
      dataSource <- "network"
      stdout.println(`Errors: ${errorCount}, Source: ${dataSource}`)

      // Single-char math: ALLOWED
      x <- 1.0
      y <- 2.0
      stdout.println(`Position: ${x}, ${y}`)

E11031 banned class field naming property

      data <- "network"
      stdout.println(`Errors: ${errorCount}, Source: ${data}`)

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


Question: Which of these names would be rejected by the EK9 compiler?

Can you identify which variable names are banned in EK9?

Help me spot banned names in my EK9 code

Answer:
Here is how to spot banned names. EK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive.

Names like str, num, item, result, input, output, count, index, list, map, set, key, status, state, type, config, param, arg, var, ret, res are NOT banned.

Separately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.

SPOTTING GUIDE:
- 'customerName' -> ALLOWED (descriptive)
- 'name' -> ALLOWED (not in banned list)
- 'data' -> BANNED (E11031)
- 'dataProcessor' -> ALLOWED (compound word)
- 'x' -> ALLOWED (single-char)
- 'temp' -> BANNED (E11031)
- 'temperature' -> ALLOWED (descriptive, not 'temp')
- 'result' -> ALLOWED (not in the 12 banned names)
- 'searchResult' -> ALLOWED (compound word)

ALWAYS ALLOWED EXCEPTIONS:
- Single-character: x, y, z, i, j, k (math variables, loop counters)
- Compound words: errorCount, inputValidator, configPath
- Descriptive domain names: threshold, customerName, retryLimit

defines module qa.naming.spotbanned

  defines program

    SpotBannedDemo()
      stdout <- Stdout()

      // ALLOWED: descriptive names
      customerName <- "Alice"
      temperature <- 22.5
      searchResult <- "found"
      retryLimit <- 3
      stdout.println(`Customer: ${customerName}`)
      stdout.println(`Temp: ${temperature}`)
      stdout.println(`Search: ${searchResult}, Limit: ${retryLimit}`)

      // ALLOWED: compound words (contain banned roots)
      dataProcessor <- "json"
      errorCount <- 0
      inputValidator <- "regex"
      configPath <- "/etc/app"
      stdout.println(`Processor: ${dataProcessor}, Errors: ${errorCount}`)
      stdout.println(`Validator: ${inputValidator}, Path: ${configPath}`)

      // ALLOWED: single-character
      x <- 10.0
      y <- 20.0
      i <- 1
      stdout.println(`Point: ${x}, ${y}, Index: ${i}`)

      // ALLOWED: domain names not in banned list
      threshold <- 0.95
      name <- "sensor-A"
      stdout.println(`Threshold: ${threshold}, Name: ${name}`)

E11031 allowed banned identify naming quiz spot

      temp <- 22.5
      searchResult <- "found"
      retryLimit <- 3
      stdout.println(`Customer: ${customerName}`)
      stdout.println(`Temp: ${$temp}`)
      stdout.println(`Search: ${searchResult}, Limit: ${$retryLimit}`)

Explanation: The name 'temp' is banned (E11031) but 'temperature' is allowed because it is a complete descriptive word, not the banned abbreviation.


Question: What naming mistakes do AI assistants commonly make in EK9?

Why does AI-generated EK9 code fail with E11031?

What banned names do LLMs use most often?

Answer:
AI assistants (ChatGPT, Copilot, Claude) constantly generate banned variable names because they learned from Java/Python where these names are legal. The most common AI mistakes from the banned list are: temp, data, value, buffer, flag, object.

EK9 bans exactly 12 non-descriptive names (E11031): temp, tmp, flag, flg, data, dat, object, obj, value, val, buffer, buf. These are case-insensitive. Names like result, count, item, input, output, status, str, num, index, list, map, set, key, state, type, config, param, arg, var, ret, res are NOT banned.

Separately, E11032 bans operator keywords as variable names: empty, length, contains, abs, sqrt, close, matches.

AI FIX TABLE (for the 12 actually banned names):
- AI writes 'temp'/'tmp' -> fix to 'swapHolder' or 'intermediateResult'
- AI writes 'data'/'dat' -> fix to 'payload', 'sensorReading'
- AI writes 'value'/'val' -> fix to 'price', 'threshold', 'measurement'
- AI writes 'flag'/'flg' -> fix to 'isEnabled', 'hasPermission'
- AI writes 'object'/'obj' -> fix to 'customer', 'sensorDevice'
- AI writes 'buffer'/'buf' -> fix to 'readChunk', 'outputAccumulator'

ALWAYS ALLOWED EXCEPTIONS:
- Single-character: x, y, z, i, j, k (math variables, loop counters)
- Compound words: dataProcessor, resultSet, countdownTimer
- Descriptive: customerName, orderTotal, retryLimit

defines module qa.naming.aimistakes

  defines function

    <?-
      Shows AI-corrected naming patterns.
    -?>
    fetchSensorData() as pure
      <- payload as String: "temperature=22.5"

    calculateTotal() as pure
      ->
        unitPrice as Float
        orderQuantity as Integer
      <- orderTotal as Float: unitPrice * #^ orderQuantity

  defines program

    AiMistakesDemo()
      stdout <- Stdout()

      // AI would write: data <- fetchSensorData()   BANNED
      // FIXED: descriptive name
      payload <- fetchSensorData()
      stdout.println(`Payload: ${payload}`)

      // AI would write: result <- calculateTotal(...)   BANNED
      // FIXED: descriptive name
      orderTotal <- calculateTotal(29.99, 3)
      stdout.println(`Total: ${orderTotal}`)

      // AI would write: count <- 5   BANNED
      // FIXED: descriptive name
      retryAttempt <- 5
      stdout.println(`Retries: ${retryAttempt}`)

      // Compound words: ALLOWED
      dataProcessor <- "xml"
      resultCache <- "warm"
      stdout.println(`Processor: ${dataProcessor}, Cache: ${resultCache}`)

      // Single-char math: ALLOWED
      x <- 1.0
      y <- 2.0
      stdout.println(`Point: ${x}, ${y}`)

AI E11031 LLM banned common fix mistake naming

      data <- fetchSensorData()
      stdout.println(`Payload: ${data}`)

Explanation: The non-descriptive name 'data' is banned by E11031; use a descriptive name such as 'payload' instead. See ek9 -h E11031 for details.


Question: Why can't I use a void function in a stream pipeline in EK9?

What triggers E10020 in EK9 stream pipelines?

Why does map reject my function that returns nothing?

Can I use a procedure in a stream pipeline?

Answer:
Void functions can't be used in stream pipelines. Streams pass values from stage to stage, and a void function produces nothing to pass along.

STREAM DATA FLOW
Every pipeline stage transforms or filters elements. The output of one stage feeds the next:
  cat items | map with transform | filter by predicate | collect as List of T
If 'transform' returns void, there is nothing for 'filter' to receive.

CORRECT: Function returns a value
  toUpper() as pure
    -> item as String
    <- rtn as String: item.upperCase()
  cat words | map with toUpper | collect as List of String

INCORRECT: Void function in pipeline
  logItem()
    -> item as String
    stdout.println(item)        // returns nothing
  cat words | map with logItem  // ERROR: void produces no output

For side effects, use 'tee' with a collection, or redirect with '> stdout'.

See Q235 for stream operations. See Q866 for void with call. See Q843 for return requirements.

defines module qa.streams.void.not.allowed

  defines function

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

  defines program

    StreamVoidDemo()
      stdout <- Stdout()

      //Demonstrate that toUpper returns a value — required for stream pipelines
      uppered <- toUpper("hello")
      stdout.println(`Uppercased: ${uppered}`)

      //A function that returns a value can be used in map
      stdout.println(toUpper("world"))

Java: Stream.map() requires Function<T,R> — Consumer (void) not allowed. Python: map() expects a return value. Rust: iter().map() requires FnMut(T) -> U. Go: no streams. EK9: pipeline stages require value-producing functions.

E10020 function map pipeline produce stream value void

void in streams
E10020
pipeline data flow


Question: How do filter and sort work together in EK9 streams?

Can I chain filter and sort in an EK9 stream?

How do I filter then sort a list in EK9?

What is the correct order for filter and sort in EK9 pipelines?

Answer:
Chain filter and sort with the pipe operator. Filter first to reduce the dataset, then sort the remaining elements.

PATTERN
  cat items | filter by predicate | sort | collect as List of T
  cat items | filter by predicate | sort > stdout

FILTER narrows the stream to elements matching the predicate (a function returning Boolean). SORT orders the remaining elements using their natural ordering (the <=> operator).

WITH STRINGS
  cat names | filter by startsWithA | sort > stdout
Filters to names starting with 'A', then alphabetically sorts them.

WITH FLOATS
  cat temperatures | filter by aboveFreezing | sort | collect as List of Float
Keeps temperatures above zero, sorts ascending.

Filtering before sorting is more efficient: sort operates on fewer elements.

See Q235 for all stream operations. See Q896 for output redirection. See Q237 for streams vs loops.

defines module qa.streams.filter.sort.example

  defines function

    aboveFreezing() as pure
      -> temperature as Float
      <- rtn as Boolean: temperature > 0.0

    isLongName() as pure
      -> nameToCheck as String
      <- rtn as Boolean?
      minLength <- 4
      rtn: length nameToCheck > minLength

  defines program

    FilterSortDemo()
      stdout <- Stdout()

      //Demonstrate filter predicates that streams would use
      stdout.println(`15.5 above freezing: ${aboveFreezing(15.5)}`)
      stdout.println(`-3.2 above freezing: ${aboveFreezing(-3.2)}`)

      //Demonstrate string predicate for stream filter
      stdout.println(`James is long: ${isLongName("James")}`)
      stdout.println(`Bob is long: ${isLongName("Bob")}`)

Java: stream().filter(pred).sorted().collect(). Python: sorted(filter(pred, items)). Rust: iter().filter(pred).sorted(). EK9: cat items | filter by pred | sort | collect.

chain filter order pipeline predicate sort stream

filter and sort
stream chaining
pipeline ordering


Question: How do I transform stream elements and take the first N in EK9?

How does map with head work in EK9 streams?

Can I transform then limit results in an EK9 pipeline?

How do I take the first few transformed items from a stream?

Answer:
Use 'map with' to transform each element, then 'head N' to take only the first N results.

BASIC PATTERN
  cat source | map with transformer | head N | collect as List of R

The map stage applies a function to every element, changing its type or value. The head stage stops after N elements pass through, discarding the rest.

EXAMPLE: Transform integers to strings, take first 3
  cat [10, 20, 30, 40, 50] | map with intToLabel | head 3 | collect as List of String
  Result: ["Item-10", "Item-20", "Item-30"]

EXAMPLE: Double values, take top 2
  cat [5, 1, 8, 3] | map with doubler | head 2 > stdout
  Output: 10 then 2

Head is efficient because it stops consuming from upstream once N elements have passed.

See Q235 for all operations. See Q125 for head/tail/skip details.

defines module qa.streams.map.head.example

  defines function

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

    intToLabel() as pure
      -> number as Integer
      <- rtn as String: `Item-${number}`

  defines program

    MapHeadDemo()
      stdout <- Stdout()

      //Demonstrate transform functions that streams would use with map
      stdout.println(`doubler(10): ${doubler(10)}`)
      stdout.println(`doubler(20): ${doubler(20)}`)

      //Demonstrate intToLabel transform
      stdout.println(intToLabel(30))
      stdout.println(intToLabel(40))

Java: stream().map(fn).limit(n).collect(). Python: list(map(fn, items))[:n]. Rust: iter().map(fn).take(n).collect(). EK9: cat items | map with fn | head n | collect.

first head limit map pipeline stream take transform

map and head
transform and limit
stream pipeline


Question: What do tail and skip do in EK9 stream pipelines?

How do I get the last N elements from a stream in EK9?

How do I skip elements at the start of an EK9 stream?

Answer:
Tail keeps the last N elements. Skip discards the first N elements. Both are positional operations in the pipeline.

TAIL N
Keeps only the final N elements that flow through the stream:
  cat [1, 2, 3, 4, 5] | tail 2 | collect as List of Integer
  Result: [4, 5]

SKIP N
Discards the first N elements, passing the rest downstream:
  cat [1, 2, 3, 4, 5] | skip 2 | collect as List of Integer
  Result: [3, 4, 5]

COMBINED
Skip the first 2, then take only the last 2 of what remains:
  cat [1, 2, 3, 4, 5, 6] | skip 2 | tail 2 | collect as List of Integer
  Result: [5, 6]  (skip gives [3,4,5,6], tail 2 gives [5,6])

Think of skip as Unix 'tail -n +N' and tail as Unix 'tail -N'.

See Q235 for all stream operations. See Q125 for head/tail/skip. See Q896 for redirect. See Q954 for collect as custom type.

defines module qa.streams.tail.skip.example

  defines program

    TailSkipDemo()
      stdout <- Stdout()

      values <- [10, 20, 30, 40, 50, 60, 70]

      //Demonstrate list operations that mirror tail and skip concepts
      stdout.println(`Full list: ${values}`)
      stdout.println(`List length: ${length values}`)

      //Access elements by index using getOrDefault
      defaultVal <- 0
      firstElement <- values.getOrDefault(0, defaultVal)
      if firstElement?
        stdout.println(`First element: ${firstElement}`)

      lastIndex <- length values - 1
      lastElement <- values.getOrDefault(lastIndex, defaultVal)
      if lastElement?
        stdout.println(`Last element: ${lastElement}`)

Java: stream().skip(n) and no direct tail — need collect then subList. Python: islice(iter, n, None) for skip, deque(iter, maxlen=n) for tail. Rust: iter().skip(n) and no direct last-n. Go: manual slicing. EK9: skip N and tail N as built-in pipeline stages.

discard last pipeline positional skip stream tail

tail operation
skip operation
positional stream stages


Question: Can I catch multiple exception types in one catch block in EK9?

What triggers E07850 in EK9 catch blocks?

How many exception types can a catch handle in EK9?

Answer:
Each EK9 catch block handles exactly ONE exception type. There is no multi-catch syntax.

SINGLE CATCH
  try
    riskyWork()
  catch
    -> ex as Exception
    stderr.println(`Failed: ${ex}`)

The catch uses -> on its own line to declare the incoming exception. This follows EK9's data-flow convention: -> means data flowing IN.

WHY ONE TYPE PER CATCH
Multi-catch (Java's catch (A | B e)) creates ambiguity about the exception's actual type inside the handler. EK9 enforces clarity: you know the exact type you are handling.

HANDLING DIFFERENT TYPES
Use separate try/catch blocks or catch the base Exception type:
  try
    riskyWork()
  catch
    -> ex as Exception
    stderr.println(`Caught: ${ex}`)

Since all EK9 exceptions descend from Exception, catching Exception handles everything.

See Q902 for try/catch basics. See Q934 for try/finally. See Q130 for control flow overview.

defines module qa.controlflow.singlecatch

  defines function

    safeDivision() as pure
      ->
        numerator as Integer
        denominator as Integer
      <-
        rtn as Float: #^ numerator / #^ denominator

  defines program

    SingleCatchDemo()
      stdout <- Stdout()
      stderr <- Stderr()

      //Single exception type per catch
      try
        outcome <- safeDivision(42, 7)
        stdout.println(`42 / 7 = ${outcome}`)
      catch
        -> ex as Exception
        stderr.println(`Division failed: ${ex}`)

      //Catching the base Exception covers all exception types
      try
        second <- safeDivision(100, 5)
        stdout.println(`100 / 5 = ${second}`)
      catch
        -> ex as Exception
        stderr.println(`Error: ${ex}`)
      finally
        stdout.println("Division attempt complete")

Java: catch (IOException | SQLException e) multi-catch since Java 7. Python: except (TypeError, ValueError) as e. C#: catch (Exception e) when — filter pattern. EK9: one type per catch block, no multi-catch.

E07850 catch exception handler multi-catch single try type

single catch type
E07850
exception handling

      catch
        ->
          ex1 as Exception
          ex2 as Exception
        stderr.println(`Failed: ${ex1}`)

Explanation: An EK9 catch block's arrow may declare only ONE exception variable (E07850). Declaring two (ex1, ex2) in a single catch is rejected. Use one catch block, catching Exception to handle all types. See ek9 -h E07850 for details.


Question: What happens if I write code after a throw statement in EK9?

Can I have statements after throw in EK9?

Answer:
EK9 detects unreachable code and flags it as a compile error. Code placed after an unconditional throw can never execute.

UNREACHABLE CODE DETECTION
The compiler performs flow analysis. When every path through a block ends with throw, any subsequent code is dead:
  try
    processData()
  catch
    -> ex as Exception
    stderr.println(`Error: ${ex}`)

EK9 treats unreachable statements as errors, not warnings. Dead code indicates a logic flaw that must be fixed.

PROPER PATTERN
Place all meaningful work before the throw. If conditional logic determines whether to throw, the compiler tracks each branch independently.

CONDITIONAL THROW IS FINE
  if not isValid
    throw Exception("invalid")
  //Code here IS reachable because the throw is conditional
  processValid()

See Q902 for try/catch basics. See Q932 for single catch type. See Q144 for control flow philosophy.

defines module qa.controlflow.unreachable

  defines function

    validateAge() as pure
      -> age as Integer
      <- rtn as Boolean: age >= 0

  defines program

    UnreachableDemo()
      stdout <- Stdout()
      stderr <- Stderr()

      age <- 25

      //Conditional throw — code after is reachable
      if not validateAge(age)
        throw Exception("Invalid age")

      //This line is reachable because throw is conditional
      stdout.println(`Valid age: ${age}`)

      //Proper try/catch with no dead code
      try
        if not validateAge(-1)
          throw Exception("Negative age")
        stdout.println("Age validated")
      catch
        -> ex as Exception
        stderr.println(`Caught: ${ex}`)

Java: unreachable statement is a compile error. C#: unreachable code warning (CS0162). Python: no unreachable detection. Rust: warns on unreachable code. Go: no unreachable detection. EK9: compile error for unreachable code after throw.

E07370 E07380 analysis code dead flow throw unreachable

unreachable code
throw flow
dead code detection

Explanation: Code placed after an unconditional throw can never execute and is rejected as unreachable. See ek9 -h E07370 for details.


Question: When should I use try/finally without catch in EK9?

Does EK9 support try/finally without catch?

How do I ensure cleanup code runs in EK9?

Answer:
The finally block runs regardless of whether an exception occurs. Use try/finally without catch when you want cleanup but not error handling at that level.

TRY/FINALLY PATTERN
  try
    doWork()
  finally
    cleanup()       // Always runs

The exception propagates up to the caller. The finally block still executes before propagation.

TRY/CATCH/FINALLY
  try
    doWork()
  catch
    -> ex as Exception
    logError(ex)
  finally
    cleanup()       // Runs after catch too

Finally runs in all scenarios: normal completion, exception caught, or exception propagating.

WHEN TO OMIT CATCH
Omit catch when the current scope cannot meaningfully handle the error. Let exceptions propagate to a higher-level handler while still guaranteeing local cleanup.

See Q902 for try/catch syntax. See Q932 for single catch type. See Q935 for nested try/catch.

defines module qa.controlflow.tryfinally

  defines function

    riskyCalculation() as pure
      ->
        x as Integer
        y as Integer
      <-
        rtn as Float: #^ x / #^ y

  defines program

    TryFinallyDemo()
      stdout <- Stdout()

      //try/finally without catch — cleanup guaranteed
      stdout.println("Starting work")
      try
        result <- riskyCalculation(20, 4)
        stdout.println(`Result: ${result}`)
      finally
        stdout.println("Cleanup done")

      //try/catch/finally — full pattern
      try
        second <- riskyCalculation(50, 10)
        stdout.println(`Second: ${second}`)
      catch
        -> ex as Exception
        stdout.println(`Handled: ${ex}`)
      finally
        stdout.println("All finished")

Java: try-finally for cleanup, try-with-resources preferred. Python: try/finally, context managers preferred. Rust: Drop trait, no try/finally. Go: defer for cleanup. EK9: try/finally with optional catch.

cleanup exception finally guarantee propagate resource try

try finally
cleanup pattern
exception propagation


Question: Can I nest try/catch blocks in EK9?

How do nested exception handlers work in EK9?

Can I put a try inside another try in EK9?

How do I handle different errors at different levels in EK9?

Answer:
Nested try/catch blocks work in EK9. An inner try/catch handles errors at a fine-grained level while the outer handler catches anything that escapes.

NESTED PATTERN
  try
    //Outer work
    try
      //Inner work that might fail
      riskyStep()
    catch
      -> innerEx as Exception
      handleInner(innerEx)
    //Continue outer work after inner handled
    moreWork()
  catch
    -> outerEx as Exception
    handleOuter(outerEx)

If the inner catch handles the exception, execution continues in the outer try. If the inner catch re-throws or if an exception occurs outside the inner try, the outer catch handles it.

WHEN TO NEST
Nest when different operations need different error handling strategies. The inner handler deals with a specific failure while the outer provides a safety net.

See Q902 for try/catch basics. See Q932 for single catch type. See Q934 for try/finally.

defines module qa.controlflow.nestedtry

  defines function

    parseInteger() as pure
      -> text as String
      <- rtn as Integer: Integer(text)

  defines program

    NestedTryCatchDemo()
      stdout <- Stdout()
      stderr <- Stderr()

      //First try/catch handles one operation
      try
        parsed <- parseInteger("42")
        stdout.println(`Parsed: ${parsed}`)
      catch
        -> firstEx as Exception
        stderr.println(`First parse failed: ${firstEx}`)

      //Second try/catch handles another operation
      try
        second <- parseInteger("99")
        stdout.println(`Second parse: ${second}`)
      catch
        -> secondEx as Exception
        stderr.println(`Second parse failed: ${secondEx}`)
      finally
        stdout.println("All operations complete")

Java: nested try/catch common for layered error handling. Python: nested try/except same pattern. Rust: nested match on Result values. Go: nested if err != nil checks. EK9: nested try/catch with -> syntax for exception parameter.

catch exception handler inner layered nested outer try

nested try catch
layered exception handling


Question: Is the ? operator prefix or suffix in EK9?

Where does the ? go — before or after the variable name?

How do I write an isSet check correctly in EK9?

Answer:
The ? operator is always SUFFIX in EK9. Write name? not ?name.

SUFFIX SYNTAX
  if userName?
    stdout.println(userName)
The ? comes after the variable, like Rust's .is_some() or Swift's optional unwrapping.

WHY SUFFIX
Suffix reads naturally as a question: 'is userName set?' The ? asks a question about the thing to its left. Prefix ? would be ambiguous with other operators.

USAGE CONTEXTS
  variable?           // Is this variable set?
  list?               // Is this list set? (always true for created lists)
  result?             // Is this result set?
  if connection?      // Guard: only enter if connection is set

COMBINING WITH NOT
  if not userName?
    stdout.println("No user name")

The pattern 'not variable?' reads as 'not (variable is set)'.

See Q877 for isSet details. See Q898 for more ? examples. See Q29 for tri-state.

defines module qa.operators.issetsuffix

  defines program

    IsSetSuffixDemo()
      stdout <- Stdout()

      //Unset variable — ? returns false
      greeting <- String()
      if not greeting?
        stdout.println("Variable is unset")

      //Guarded assignment with suffix check
      greeting :=? "Hello"
      if greeting?
        stdout.println(greeting)

      //Works on any type
      score <- Integer()
      if not score?
        stdout.println("Score is not yet set")

      score :=? 42
      if score?
        stdout.println(`Score is set: ${score}`)

EK9 uses value? as a suffix — append ? after the variable name to check if it is set. Returns Boolean, no parentheses needed.

check isset mark operator position question set suffix

suffix ? operator
isSet position
operator placement


Question: Why does EK9 reject non-Boolean values in if conditions?

Can I use an Integer as a condition in EK9?

Why won't if count work when count is an Integer?

Answer:
EK9 requires Boolean expressions in Boolean contexts. Unlike C or JavaScript, there is no implicit truthiness conversion.

BOOLEAN CONTEXT REQUIRES BOOLEAN
Conditions in if, while, and other control flow must evaluate to Boolean:
  if count > 0          // CORRECT: comparison produces Boolean
  if name?              // CORRECT: ? produces Boolean
  while isActive        // CORRECT: isActive is Boolean

NO IMPLICIT CONVERSION
These fail because the value is not Boolean:
  if count              // ERROR: Integer is not Boolean
  if name               // ERROR: String is not Boolean
  while connectionObj   // ERROR: object is not Boolean

USE ? FOR SET CHECKS
To check if a non-Boolean value is set, use the ? suffix operator:
  if count?             // Is count set? Returns Boolean
  if name?              // Is name set? Returns Boolean

USE COMPARISONS FOR VALUE CHECKS
  if count > 0          // Numeric comparison returns Boolean
  if length name > 0    // String has content

This eliminates an entire class of bugs from truthy/falsy confusion in JavaScript and Python.

See Q877 for isSet operator. See Q936 for ? suffix position. See Q30 for Boolean operators.

defines module qa.operators.booleancontext

  defines program

    BooleanContextDemo()
      stdout <- Stdout()

      //? operator produces Boolean — correct
      userName <- String()
      userName :=? "Alice"
      if userName?
        stdout.println(`Name is set: ${userName}`)

      //Comparison produces Boolean — correct
      score <- Integer()
      score :=? 5
      if score?
        if score > 0
          stdout.println(`Score is positive: ${score}`)

      //Compound Boolean expressions — correct
      threshold <- 10
      if score?
        if score > 0 and score < threshold
          stdout.println("Score in range")

JavaScript: if (count) truthy for non-zero. Python: if count truthy for non-zero/non-empty. C: if (ptr) truthy for non-null. Java: Boolean required, no implicit conversion. Rust: Boolean required, like EK9. EK9: Boolean required, use ? for isSet check or comparisons.

E07540 boolean condition context conversion implicit truthiness type

Boolean context
E07540
no implicit truthiness

        if score
          stdout.println(`Score is positive: ${score}`)

Explanation: A non-Boolean value like the Integer 'score' cannot be used as an 'if' condition — use a comparison such as 'score > 0'. See ek9 -h E07540 for details.


Question: How do I declare the return variable inside a switch expression in EK9?

What is the <- rtn pattern in switch expressions?

Why do I need a return declaration inside switch?

Answer:
A switch expression requires a return variable declaration immediately after the switch line. Each case assigns to that variable.

PATTERN
  result := switch value
    <- label as String: String()
    case "A"
      label: "Alpha"
    case "B"
      label: "Beta"
    default
      label: "Other"

The '<- label as String: String()' declares a variable named 'label' initialized to an empty String. Each case assigns to label. After the switch, result holds label's final value.

DECLARATION FORM
  result <- switch expr
    <- rtn as Type?                  // Unset, nullable style
  result := switch expr
    <- rtn as Type: initialValue     // With default
  result := switch expr
    <- rtn <- Type()                 // Constructor initialization

THE VARIABLE NAME IS YOURS TO CHOOSE
Use any meaningful name: rtn, category, label, outcome. The name should describe what the switch computes.

NO RETURN STATEMENT
EK9 has no return keyword. The declared variable's final value IS the result. The compiler verifies every path assigns it.

See Q68 for switch expression basics. See Q901 for expression vs statement. See Q782 for return rules.

defines module qa.controlflow.switchreturn

  defines function

    classifyHttpCode() as pure
      -> code as Integer
      <-
        rtn as String: String()

      rtn := switch code
        <- category as String: String()
        case 200
          category: "OK"
        case 301
          category: "Redirect"
        case 404
          category: "Not Found"
        case 500
          category: "Server Error"
        default
          category: "Unknown"

    describePriority() as pure
      -> level as Integer
      <-
        rtn as String: String()

      rtn := switch level
        <- desc as String: String()
        case 1
          desc: "Critical"
        case 2
          desc: "High"
        case 3
          desc: "Medium"
        default
          desc: "Low"

  defines program

    SwitchReturnDemo()
      stdout <- Stdout()

      httpResult <- classifyHttpCode(404)
      stdout.println(`HTTP 404: ${httpResult}`)

      okResult <- classifyHttpCode(200)
      stdout.println(`HTTP 200: ${okResult}`)

      priority <- describePriority(1)
      stdout.println(`Priority 1: ${priority}`)

      lowPriority <- describePriority(9)
      stdout.println(`Priority 9: ${lowPriority}`)

Java: switch expression with yield keyword. Kotlin: when returns last expression in branch. Rust: match returns last expression. C#: switch expression with =>. EK9: explicit return variable declaration with <- inside the switch block.

assign case declaration expression result return switch variable

switch return declaration
switch expression pattern

Explanation: Switch expressions require a return variable declaration (<- rtn as Type). Without it, the switch cannot produce a value. See ek9 -h E07405 for details.


Question: What do <? and >? mean in EK9 — are they null coalescing?

Is <? the null coalescing operator in EK9?

How do min/max coalescing operators work in EK9?

What is the difference between <? and ?? in EK9?

Answer:
No. <? and >? are MINIMUM and MAXIMUM coalescing operators, not null coalescing.

<? RETURNS THE SMALLER VALUE
  minimum <- 10 <? 3       // Result: 3
  minimum <- 7 <? 20       // Result: 7

>? RETURNS THE LARGER VALUE
  maximum <- 10 >? 3       // Result: 10
  maximum <- 7 >? 20       // Result: 20

UNSET HANDLING
When one operand is unset, the other is returned:
  a <- 5
  b <- Integer()
  result <- a <? b         // Result: 5 (b is unset, a wins)

NOT NULL COALESCING
For null/unset coalescing, EK9 has separate operators:
  ?? checks memory presence (absent check)
  ?: checks isSet state (elvis operator)
  :=? assigns only if target is unset

REMEMBER
  <? = minimum (pick smaller)
  >? = maximum (pick larger)
  ?? = present check (memory)
  ?: = set check (isSet)

See Q243 for all coalescing operators. See Q239 for comparison operators.

defines module qa.operators.coalescingminmax

  defines function

    minOf() as pure
      ->
        left as Integer
        right as Integer
      <- rtn as Integer: left <? right

    maxOf() as pure
      ->
        left as Integer
        right as Integer
      <- rtn as Integer: left >? right

    minOfFloat() as pure
      ->
        left as Float
        right as Float
      <- rtn as Float: left <? right

    maxOfFloat() as pure
      ->
        left as Float
        right as Float
      <- rtn as Float: left >? right

    chainedMin() as pure
      ->
        a as Integer
        b as Integer
        c as Integer
      <- rtn as Integer: a <? b <? c

    chainedMax() as pure
      ->
        a as Integer
        b as Integer
        c as Integer
      <- rtn as Integer: a >? b >? c

  defines program

    CoalescingMinMaxDemo()
      stdout <- Stdout()

      //<? gives the smaller value
      minResult <- minOf(15, 8)
      stdout.println(`min(15, 8) = ${minResult}`)

      //>? gives the larger value
      maxResult <- maxOf(15, 8)
      stdout.println(`max(15, 8) = ${maxResult}`)

      //Works with Float too
      smallFloat <- minOfFloat(3.14, 2.72)
      stdout.println(`min(3.14, 2.72) = ${smallFloat}`)

      bigFloat <- maxOfFloat(3.14, 2.72)
      stdout.println(`max(3.14, 2.72) = ${bigFloat}`)

      //Chained: find minimum of three values
      smallest <- chainedMin(20, 5, 12)
      stdout.println(`min(20, 5, 12) = ${smallest}`)

      largest <- chainedMax(20, 5, 12)
      stdout.println(`max(20, 5, 12) = ${largest}`)

Perl: min/max functions. Ruby: [a,b].min and [a,b].max. Python: min(a,b) and max(a,b). Rust: std::cmp::min/max. Java: Math.min/max. EK9: <? and >? as infix operators with unset-safe coalescing.

coalescing compare greater lesser maximum minimum operator unset


Question: Can I use ++ in an assignment like y <- x++ in EK9?

Are ++ and -- expressions or statements in EK9?

Why can't I assign the result of ++ in EK9?

How do increment and decrement work in EK9?

Answer:
EK9 has ++ and -- but they are STATEMENT-ONLY. They modify the variable in place and produce no value. You cannot use them inside expressions.

STATEMENT-ONLY MEANS
  counter++          CORRECT — standalone statement
  counter--          CORRECT — standalone statement
  y <- counter++     WRONG — ++ produces no value to assign
  if counter++       WRONG — ++ produces no Boolean

WHY THIS DESIGN
In C and Java, x++ returns the OLD value while ++x returns the NEW value. This causes subtle bugs:
  array[i++] = array[++j]    // Which updates first?
EK9 eliminates this entire bug category by making ++ and -- void operations.

IF YOU NEED THE OLD VALUE
  saved <- counter
  counter++
  // saved has old value, counter has new value

This is the same design choice as Go, which also makes ++ and -- statements only.

See Q780 for detailed explanation. See Q238 for operator overview.

defines module qa.operators.incrementstmt

  defines program

    IncrementDemo()
      stdout <- Stdout()

      counter <- 0

      //Correct: ++ as standalone statement
      counter++
      counter++
      counter++
      stdout.println(`After 3 increments: ${counter}`)

      //Correct: -- as standalone statement
      counter--
      stdout.println(`After decrement: ${counter}`)

      //Save before incrementing if you need the old value
      previous <- counter
      counter++
      stdout.println(`Was ${previous}, now ${counter}`)

C/C++: pre/post increment as expressions, undefined behavior in complex expressions. Java: pre/post increment as expressions. Go: ++ and -- are statements only (same as EK9). Python: no ++ or -- at all. Rust: no ++ or -- at all. EK9: ++ and -- are statements only, like Go.

E07950 assign bug decrement expression increment statement void

statement-only increment
++ and -- restrictions

Explanation: The ++ operator is statement-only in EK9 — it returns void and cannot be used in expressions. Increment on its own line, then read the value. See ek9 -h E07950 for details.


Question: Why can't I assign the result of a void function in EK9?

What happens when I try to store void in a variable?

Can a void function return a value in EK9?

Answer:
Void functions produce no value. Attempting to assign their result to a variable triggers E10010.

VOID FUNCTIONS HAVE NO RETURN
A function without a <- return declaration is void:
  logMessage()
    -> msg as String
    stdout.println(msg)       // No <- so this is void

You CANNOT assign void:
  result <- logMessage("hi")  // ERROR E10010 — nothing to assign

FUNCTIONS THAT RETURN VALUES
A function with <- produces a value you can capture:
  formatMessage() as pure
    -> msg as String
    <- rtn as String: `[LOG] ${msg}`

  result <- formatMessage("hi")   // CORRECT — function returns String

VOID IS FOR SIDE EFFECTS
Void functions perform actions: printing, writing files, sending messages. They are called as statements, not in expressions:
  logMessage("starting")        // CORRECT — standalone call
  sendAlert("warning")          // CORRECT — standalone call

See Q597 for function parameters and returns. See Q596 for function vs method.

defines module qa.operators.voidnotassign

  defines function

    formatEntry() as pure
      -> msg as String
      <- rtn as String: `[INFO] ${msg}`

  defines program

    VoidAssignDemo()
      stdout <- Stdout()

      //Functions with <- return values that CAN be assigned
      formatted <- formatEntry("server started")
      stdout.println(formatted)

      //Void calls are standalone statements
      stdout.println("Direct output is a void call")

      //Multiple return values demonstrated
      first <- formatEntry("step one")
      second <- formatEntry("step two")
      stdout.println(first)
      stdout.println(second)

Java: void methods cannot be assigned. C: void functions cannot be assigned. Python: None returned implicitly, assigning prints gives None. Rust: () unit type returned. EK9: void means no return, cannot assign.

E10010 assign effect function return side value void

void assignment error
E10010
void vs returning functions

voidResult <- stdout.println("Direct output is a void call")

Explanation: A void call returns nothing, so its result cannot be assigned to a variable. See ek9 -h E10010 for details.


Question: How do I pass a function as a parameter in EK9?

What triggers E07480 FUNCTION_DELEGATE_EXPECTED in EK9?

Can functions be first-class values in EK9?

How do I use higher-order functions in EK9?

Answer:
EK9 supports first-class functions. Define an abstract function as the parameter type, then pass a concrete function that matches its signature.

DEFINE THE FUNCTION TYPE
An abstract function acts as a type signature:
  Transformer() as pure abstract
    -> incoming as Integer
    <- rtn as Integer

ACCEPT IT AS A PARAMETER
  applyTransform()
    ->
      value as Integer
      fn as Transformer
    <- answer as Integer: fn(value)

PASS A MATCHING FUNCTION
  doubleIt() is Transformer as pure
    -> incoming as Integer
    <- rtn as Integer: incoming * 2

  answer <- applyTransform(5, doubleIt)

The concrete function must match the abstract function's signature exactly. If it does not, the compiler raises E07480.

STREAMS USE THIS PATTERN
Stream operations like 'filter by' and 'map with' accept function parameters internally. The predicate you pass to filter must match the expected signature.

See Q597 for function parameters. See Q601 for dynamic functions. See Q596 for function vs method.

defines module qa.functions.asparameter

  defines function

    Transformer() as pure abstract
      -> incoming as Integer
      <- rtn as Integer?

    doubleIt() is Transformer as pure
      -> incoming as Integer
      <- rtn as Integer: incoming * 2

    tripleIt() is Transformer as pure
      -> incoming as Integer
      <- rtn as Integer: incoming * 3

    applyTransform()
      ->
        operand as Integer
        fn as Transformer
      <- answer as Integer: fn(operand)

  defines program

    FunctionParamDemo()
      stdout <- Stdout()

      doubled <- applyTransform(7, doubleIt)
      stdout.println(`7 doubled: ${doubled}`)

      tripled <- applyTransform(7, tripleIt)
      stdout.println(`7 tripled: ${tripled}`)

Java: Functional interfaces (Function, Predicate, Consumer). Python: functions are objects, pass directly. Rust: Fn/FnMut/FnOnce trait bounds. Go: func types as parameters. Kotlin: lambda and function references. EK9: abstract functions as type signatures, concrete functions with 'is' relationship.

E07480 abstract delegate first-class function higher-order parameter pass

function as parameter
E07480
higher-order functions

Explanation: Function parameters expect a matching function delegate, not a String literal — pass the function name (doubleIt) without quotes so the argument types match. See ek9 -h E06270 for details.


Question: Why does EK9 reject empty defines blocks?

Why must every defines section have content?

Answer:
EK9 does not allow empty defines blocks. Every section header must contain at least one meaningful declaration.

EMPTY BLOCKS ARE ERRORS
  defines class           // ERROR E01087 — nothing inside

  defines function        // ERROR E01087 — nothing inside

WHY NOT ALLOWED
Empty blocks serve no purpose. They indicate incomplete code, placeholder stubs, or copy-paste errors. EK9 treats them as errors because the compiler enforces completeness.

EVERY SECTION NEEDS CONTENT
  defines class
    MyClass               // At least one class declaration
      value <- 0

  defines function
    doWork()              // At least one function
      stdout <- Stdout()
      stdout.println("working")

  defines program
    Main()                // At least one program
      stdout <- Stdout()
      stdout.println("running")

If you are not ready to fill a section, remove the section header entirely. Add it back when you have content.

See Q893 for section header requirements. See Q725 for class body ordering.

defines module qa.syntax.noemptydefines

  defines function

    greet() as pure
      -> name as String
      <- rtn as String: `Welcome, ${name}`

  defines class

    Greeter
      prefix <- "Hello"

      Greeter()
        -> p as String
        prefix :=: p

      makeGreeting() as pure
        -> name as String
        <- rtn as String: `${prefix}, ${name}`

      default operator ?

  defines program

    NoEmptyDefinesDemo()
      stdout <- Stdout()

      //Every section above has content
      stdout.println(greet("Steve"))

      greeter <- Greeter("Good morning")
      stdout.println(greeter.makeGreeting("World"))

Java: empty class bodies allowed (class Empty {}). Python: pass keyword for empty blocks. Rust: empty impl blocks allowed. Go: empty struct allowed. EK9: no empty blocks — every section must contain declarations.

E01087 block content defines empty required section stub

empty defines error
E01087
section content requirement

Explanation: Empty defines blocks are not allowed. Every section header (defines class, defines function, etc.) must contain at least one declaration. Remove the header if you have no content yet. See ek9 -h E01087 for details.