Question: How do I declare multiple variables at once?

Does EK9 have tuple unpacking or destructuring?

Can I declare several variables on one line in EK9?

How do I return multiple values from a function in EK9?

Answer:
EK9 declares one variable per line. There is no multi-variable declaration, no tuple unpacking, and no destructuring. This is a deliberate design decision for clarity and readability.

LOCAL VARIABLES: ONE PER LINE
  count <- 42
  name <- "Steve"
  ratio <- 3.14
  ready <- true
Each variable gets its own line with its own <- declaration. There is no syntax for count, name, ratio <- 42, "Steve", 3.14 or anything similar.

FUNCTION PARAMETERS: MULTIPLE BUT SEPARATE LINES
When a function takes multiple parameters, each is declared on its own line in a -> block:
  calculate()
    ->
      width as Float
      height as Float
    <- area as Float: width * height
The single-parameter shorthand puts it on the same line as ->:
  square()
    -> side as Float
    <- area as Float: side * side

MULTIPLE RETURN VALUES
EK9 does not have tuple unpacking like Python's a, b = get_pair(). Instead, use a record or class to group related return values:
  defines record
    Outcome
      message <- String()
      exitCode <- Integer()
      default operator ?
  processData()
    -> inputData as String
    <- outcome as Outcome: Outcome()
The caller accesses fields by name: result.message, result.exitCode. This is more readable than positional tuple unpacking because every value has a name.

WHY NO DESTRUCTURING?
Destructuring and tuple unpacking introduce positional coupling. In Python, a, b, c = get_values() breaks silently if the function starts returning four values or if someone swaps the order. Named fields make the contract explicit and changes visible.

OTHER LANGUAGES
  Python: a, b = 1, 2 and a, b, c = get_triple() with positional unpacking
  Go: var (a int; b string) blocks and a, b := getTwoValues() multi-return
  Rust: let (a, b) = (1, 2) destructuring and pattern matching
  JavaScript: const {a, b} = obj and const [x, y] = arr
  Kotlin: val (a, b) = Pair(1, 2) with componentN() convention

EK9 avoids all of these. One variable per line, named fields for grouped values, explicit and readable. The two extra lines you write are repaid every time someone reads the code.

See Q29 for unset variables. See Q22 for declaring variables.

defines module qa.multiple.variables

  defines record
    Outcome
      message <- String()
      exitCode <- Integer()
      default operator ?

  defines function

    processData()
      -> inputData as String
      <- outcome as Outcome: Outcome()
      require inputData?

  defines program
    MultipleVariablesDemo()
      stdout <- Stdout()

      // One variable per line
      count <- 42
      name <- "Steve"
      ratio <- 3.14
      ready <- true

      stdout.println(`${name}: ${count} ${ratio} ${ready}`)

      // Grouped return values via record
      result <- processData("test")
      stdout.println(`Message: ${result.message}`)
      stdout.println(`Exit code: ${result.exitCode}`)

Python: a, b = 1, 2 tuple unpacking, multiple return values. Go: var (...) blocks, a, b := f() multi-return. Rust: let (a, b) destructuring, pattern matching. JavaScript: const {a, b} = obj, const [x, y] = arr. Kotlin: val (a, b) = Pair with componentN(). EK9: one variable per line, no destructuring, no tuple unpacking. Use records for grouped return values with named fields.

beginner declare destructuring first group intro many migrate multiple record return several start tuple unpacking variable

Explanation: Record fields must be initialised inline. An uninitialised field triggers E08180. Use a constructor call like String() for an unset default. See ek9 -h E08180 for details.

Explanation: The ? operator is inherited from the base type and requires 'default' for auto-generation or 'override' for custom implementation. Declaring bare 'operator ?' triggers E07520 because operator semantics require a Boolean return. See ek9 -h E07520 for details.


Question: What is the default value of an unset variable?

What happens when a variable has no value in EK9?

How does the tri-state object model work in EK9?

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

Answer:
EK9 has no null. Every object exists in one of three states: absent, present but unset, or present and set. The ? operator checks whether an object is set.

THE THREE STATES
  Absent: object does not exist (Dict lookup miss, empty Optional).
  Present but unset: object exists, no meaningful value. String() is unset. ? returns false.
  Present and set: object has valid value. String("hello") is set. ? returns true.

CHECKING STATE WITH ?
  name <- String()
  if name?
    stdout.println("Has a value")

Guarded assignment :=? only assigns if unset:
  name :=? "default"    assigns because unset
  name :=? "other"      no-op because already set

GUARDS IN CONTROL FLOW
  if record <- database.find(key)
    stdout.println(`Found: ${record}`)
Works identically in if, switch, for, while, and try.

TYPE-SPECIFIC BEHAVIOUR
  Primitives (String, Integer, Float, Boolean, Character): unset when created with no argument.
  Collections (List, Dict): always set when created, even if empty.
  Containers (Optional, DictEntry): depends on contained value.

INVALID CONSTRUCTION CREATES UNSET
  parsed <- Integer("not a number")
  if parsed?    false — invalid input produces unset, no exception

BOOLEAN TRI-STATE
Boolean() is unset (not yet known), distinct from Boolean(false).

JSON MAPPING
  Absent: field omitted. Unset: field as null. Set: field has value.
Critical for REST PATCH distinguishing 'not provided' from 'clear value' from 'update'.

WHY NOT NULL?
Null is 'the billion-dollar mistake'. EK9's tri-state replaces it: objects always exist, ? checks for meaningful value, guards prevent processing unset values, compiler enforces handling.

See Q22 for :=? guarded assignment. See Q26 for unified object model. See Q30 for Boolean tri-state. See Q47 for Optional. See Q48 for Result. See Q104 for uninitialised properties. See Q141 for constants. See Q188 for JSON. See Q222 for enum unset state. See Q243 for coalescing operators. See Q251 for unset variable errors. See Q256 for Void type. See Q261 for idiomatic patterns. See Q269 for input validation. See Q277 for AI null checks vs guards. See Q286 for guarded assignment accumulation.

defines module qa.unset.variables

  defines program
    UnsetVariablesDemo()
      stdout <- Stdout()

      // Present but unset
      name <- String()
      stdout.println(`Name is set: ${name?}`)

      // Present and set
      greeting <- "Hello"
      stdout.println(`Greeting is set: ${greeting?}`)

      // Guarded assignment - only assigns if unset
      name :=? "default"
      stdout.println(`Name after guard: ${name}`)

      name :=? "other"
      stdout.println(`Name after second guard: ${name}`)

      // Guard in control flow
      items <- List() of String
      items += "first"

      // Collections are always set when created
      stdout.println(`Items is set: ${items?}`)

      emptyList <- List() of Integer
      stdout.println(`Empty list is set: ${emptyList?}`)

      // Unset integer
      count <- Integer()
      stdout.println(`Unset count: ${count?}`)
      count :=? 42
      stdout.println(`Count after guard: ${count}`)

Java: null/NullPointerException. Rust: Option<T>. Kotlin/Swift: nullable types. Python: None. JS: null AND undefined. EK9: tri-state (absent/unset/set), no null, ? operator, :=? guarded assignment, guards in control flow.

absent beginner billion default first intro migrate mistake null start swift tri-state tristate undefined unset value

unset variable
default value
uninitialized


Question: How do I work with Boolean values?

What logical operators does EK9 have?
Can a Boolean be unset in EK9?
How does Boolean tri-state work in EK9?

Answer:
In most languages, a Boolean is either true or false. In EK9, a Boolean has three states: true, false, and unset. An unset Boolean is neither true nor false. It means 'not yet determined' or 'we do not know yet'. This is a fundamental difference.

Consider a medical test result. 'Negative' (false) and 'we have not run the test yet' (unset) are completely different states. In Java, Python, or Go, an uninitialised boolean defaults to false, which silently conflates 'no' with 'unknown'. EK9 makes the distinction explicit.

CREATING BOOLEANS
  ready <- true
  failed <- false
  unknown <- Boolean()
The first two are set. The third exists but is unset. Use ? to check:
  if ready?
    stdout.println("ready has a value")
  if unknown?
    stdout.println("this will not print")

LOGICAL OPERATORS
EK9 provides four logical operators:
  and   Logical conjunction: true and false gives false
  or    Logical disjunction: true or false gives true
  xor   Exclusive or: true xor true gives false, true xor false gives true
  not   Logical negation: written as 'not' keyword
  ~     Complement operator: ~true gives false, ~false gives true

These operators work on set Boolean values. If either operand is unset, the result is unset. This propagates the 'unknown' state correctly rather than silently treating it as false.

COMPARISON OPERATORS
  ==    Equality: true == true gives true
  <>    Inequality: true <> false gives true
  <=>   Comparison: returns Integer for ordering

OTHER OPERATORS
  $     String conversion: $ready gives "true"
  $$    JSON conversion
  :=:   Copy assignment
  :^:   Replace assignment
  :~:   Merge assignment
  +=    Accumulate (logical or)
  +     Addition (logical or, pure)
  |     Pipeline operator

BOOLEAN FROM STRING
You can construct a Boolean from a String:
  fromText <- Boolean("true")
  invalid <- Boolean("maybe")
If the string is not 'true' or 'false', the Boolean becomes unset rather than throwing an exception. This follows the tri-state pattern: invalid input produces an unset value you can check with ?.

WHY TRI-STATE MATTERS
In Java: boolean defaults to false. Did the user decline, or did we forget to ask?
In Python: bool defaults to False. Is the flag off, or was it never configured?
In Go: bool defaults to false. Is the check complete with a negative result, or was it never performed?
In JavaScript: undefined, null, and false are all falsy but mean different things.

EK9 eliminates this ambiguity. Boolean() is unset. Boolean(true) is true. Boolean(false) is false. Three distinct states, three distinct meanings.

For locale-specific Boolean display, see Q44 (How does the Locale type work in EK9?).

See also Q29 (What is the default value of an unset variable?) for the full tri-state model. See Q47 for how the ? operator is used with Optional types. See Q244 for boolean and bitwise operators (and, or, xor, ~).

defines module qa.boolean.values

  defines program
    BooleanDemo()
      stdout <- Stdout()

      // Three states
      ready <- true
      failed <- false
      unknown <- Boolean()

      stdout.println(`ready: ${ready}, is set: ${ready?}`)
      stdout.println(`failed: ${failed}, is set: ${failed?}`)
      stdout.println(`unknown is set: ${unknown?}`)

      // Logical operators
      both <- ready and failed
      either <- ready or failed
      exclusive <- ready xor failed
      flipped <- ~ready

      stdout.println(`and: ${both}, or: ${either}`)
      stdout.println(`xor: ${exclusive}, complement: ${flipped}`)

      // Boolean from String
      fromText <- Boolean("true")
      invalid <- Boolean("maybe")
      stdout.println(`from text: ${fromText?}, invalid: ${invalid?}`)

      // Guarded assignment
      unknown :=? true
      stdout.println(`unknown after guard: ${unknown}`)

Java: boolean defaults to false, Boolean wrapper can be null (NPE risk). Python: bool defaults to False, truthy/falsy rules for other types. Go: bool defaults to false, no nullable bool. JavaScript: undefined/null/false all falsy with different semantics. Rust: bool is true/false, Option<bool> for tri-state. C#: bool defaults to false, Nullable<bool> for tri-state. EK9: Boolean has three states (true, false, unset), logical operators (and, or, xor, not, ~), unset propagates through operations.

and beginner bool boolean complement false first intro logical migrate not or start tri-state tristate true unset xor

Explanation: EK9 logical operators require Boolean operands. Using an Integer where a Boolean is expected triggers E07620 — type incompatibility. Use a comparison operator to produce a Boolean. See ek9 -h E07620 for details.


Question: How do I work with Date and Time?

What date and time types does EK9 have?
How do I use date literals in EK9?
How does date arithmetic work in EK9?

Answer:
EK9 has built-in Date and Time types with literal syntax, calendar-safe arithmetic, and rich operators. No imports or third-party libraries needed.

DATE: Calendar Date Without Time
Literal syntax: 2024-01-15, 1971-02-01
Constructor forms: Date(2020, 10, 03) or Date(daysFromEpoch) or Date(stringValue)
Unset constructor: Date() creates an unset date (not today). Get today: Date().today() returns the current date.
Accessors: year(), month(), day(), dayOfMonth(), dayOfWeek(), dayOfYear()
Arithmetic with Duration:
  nextWeek <- birthday + P7D
  anniversary <- wedding + P1Y1M4D
Subtract two Dates to get a Duration:
  age <- today - birthday
This is calendar-safe: adding P1M to January 31st gives February 28th (or 29th in a leap year), not an invalid date.

TIME: Time of Day Without Date
Literal syntax: 09:30:00, 14:15, 12:00:01
Constructor forms: Time(12, 00) or Time(12, 00, 01)
Unset constructor: Time() creates an unset time. Time().now() returns the current time.
Convenience: Time().startOfDay() gives 00:00:00, Time().endOfDay() gives 23:59:59
Accessors: hour(), minute(), second()
Arithmetic with Duration:
  later <- morning + PT3H
Time wraps at 24 hours: adding PT3H to 22:00 gives 01:00, not 25:00.
Subtract two Times to get a Duration:
  workHours <- 14:15 - 09:30:00

COMPARISON AND TRI-STATE
Both types support ==, <>, <, >, <=, >=, <=>. Like all EK9 types, Date and Time can be unset (see Q29). An unset date is different from any specific date.

DateTime combines date, time, and timezone into a single type (see Q92). Duration (P1Y1M4D) and Millisecond (500ms) are built-in temporal span types (see Q32, Q41). All temporal types support locale-aware formatting via the Locale type (see Q44).

Use 'ek9 -h Date' and 'ek9 -h Time' to see the full API for each type.

See Q32 for duration. See Q92 for datetime operations. See Q536-Q556 for deep-dive date, time, timezone, and duration coverage.

defines module qa.date.time

  defines program
    DateTimeDemo()
      stdout <- Stdout()

      // === DATE ===

      // Date literals (ISO 8601)
      birthday <- 1971-02-01
      wedding <- 2020-10-03
      stdout.println(`Birthday: ${birthday}`)
      stdout.println(`Wedding: ${wedding}`)

      // Constructor form
      sameWedding <- Date(2020, 10, 03)
      stdout.println(`Same wedding: ${sameWedding == wedding}`)

      // Date() is unset, Date().today() gets current date
      unsetDate <- Date()
      stdout.println(`Unset date isSet: ${unsetDate?}`)
      currentDate <- Date().today()
      stdout.println(`Today: ${currentDate}`)

      // Date accessors
      stdout.println(`Year: ${birthday.year()}, Month: ${birthday.month()}, Day: ${birthday.day()}`)
      stdout.println(`Day of week: ${birthday.dayOfWeek()}, Day of year: ${birthday.dayOfYear()}`)

      // Date + Duration arithmetic
      nextWeek <- birthday + P7D
      lastWeek <- birthday - P7D
      stdout.println(`Week after: ${nextWeek}, Week before: ${lastWeek}`)

      // Compound duration: one year, one month, four days
      anniversary <- wedding + P1Y1M4D
      stdout.println(`Anniversary + P1Y1M4D: ${anniversary}`)

      // Date - Date gives Duration
      gap <- anniversary - wedding
      stdout.println(`Gap: ${gap}`)

      // Compound assignment
      mutableDate <- 2024-01-15
      mutableDate += P2W
      stdout.println(`After += P2W: ${mutableDate}`)

      // === TIME ===

      // Time literals
      morning <- 09:30:00
      afternoon <- 14:15
      stdout.println(`Morning: ${morning}, Afternoon: ${afternoon}`)

      // Constructor forms
      noon <- Time(12, 00)
      precise <- Time(12, 00, 01)
      stdout.println(`Noon: ${noon}, Precise: ${precise}`)

      // Time() is unset
      unsetTime <- Time()
      stdout.println(`Unset time isSet: ${unsetTime?}`)

      // startOfDay and endOfDay
      dayStart <- Time().startOfDay()
      dayEnd <- Time().endOfDay()
      stdout.println(`Day start: ${dayStart}, Day end: ${dayEnd}`)

      // Time accessors
      stdout.println(`Hour: ${morning.hour()}, Minute: ${morning.minute()}, Second: ${morning.second()}`)

      // Time + Duration arithmetic
      later <- morning + PT3H
      earlier <- morning - PT1H
      stdout.println(`+3h: ${later}, -1h: ${earlier}`)

      // Time - Time gives Duration
      workHours <- afternoon - morning
      stdout.println(`Work hours: ${workHours}`)

      // Comparisons (use variables from function calls to avoid tautological conditions)
      if birthday < currentDate
        stdout.println("Birthday is in the past")
      currentTime <- Time().now()
      if morning < currentTime
        stdout.println("Morning is before current time")

Java: java.util.Date (broken), java.time.LocalDate/LocalTime (Java 8), no literals, verbose factory methods. Python: datetime.date/datetime.time, no literals, limited arithmetic. Rust: no built-in, chrono crate required. Go: time.Time only, no separate Date/Time, bizarre reference date format. JavaScript: Date object notoriously broken (months 0-indexed, mutable), no Time type. EK9: Date and Time built-in with literal syntax, calendar-safe arithmetic, Duration interop, no imports needed.

accessor arithmetic beginner calendar date day duration first hour intro literal minute month second start time today year

Explanation: EK9 built-in types use short method names without 'get' prefix. Date has year(), month(), day() not getYear(), getMonth(), getDay(). Triggers E50060 — method not resolved. See ek9 -h Date for the full API.


Question: How do I work with Duration?

What is the ISO 8601 duration format?
How do Duration literals work in EK9?
How do I represent time spans in EK9?
What does PT1H30M mean?

Answer:
Duration represents time spans using ISO 8601 literals. Starts with P, date components Y/M/W/D, T separates time H/M/S. M means months before T, minutes after T.

EXAMPLES
  P1Y6M15D one year six months fifteen days, PT1H30M one hour thirty minutes, P3W three weeks (=P21D).

Arithmetic: +, -, */by Integer or Float, compound assignment. Accessors: .years(), .months(), .days(), .hours(), .minutes(), .seconds(). Comparison: ==, <>, <, >, <=, >=, <=>.

Use 'ek9 -h Duration' for the full API.

See Q31 for Date/Time. See Q41 for Millisecond. See Q44 for locale formatting. See Q539 for date differences.

defines module qa.duration

  defines program
    DurationDemo()
      stdout <- Stdout()

      // === ISO 8601 DURATION FORMAT EXPLAINED ===

      // The 'P' prefix means "Period" - every duration starts with P
      // After P, date components: Y=years, M=months, W=weeks, D=days
      // The 'T' separator introduces time components: H=hours, M=minutes, S=seconds
      // Note: M means months BEFORE the T, and minutes AFTER the T

      // Date-only durations (no T needed)
      oneYear <- P1Y
      twoMonths <- P2M
      tenDays <- P10D
      threeWeeks <- P3W
      stdout.println(`Year: ${oneYear}, Months: ${twoMonths}, Days: ${tenDays}, Weeks: ${threeWeeks}`)

      // Weeks convert to days: P3W equals P21D
      require threeWeeks == P21D

      // Time-only durations (PT prefix - P then T then time components)
      oneHour <- PT1H
      thirtyMinutes <- PT30M
      fortyFiveSeconds <- PT45S
      stdout.println(`Hour: ${oneHour}, Minutes: ${thirtyMinutes}, Seconds: ${fortyFiveSeconds}`)

      // Combined time: PT1H30M15S = 1 hour, 30 minutes, 15 seconds
      combinedTime <- PT1H30M15S
      stdout.println(`Combined time: ${combinedTime}`)

      // Combined date: P1Y6M15D = 1 year, 6 months, 15 days
      combinedDate <- P1Y6M15D
      stdout.println(`Combined date: ${combinedDate}`)

      // Full combined: P2Y3M10DT4H30M = 2 years, 3 months, 10 days, 4 hours, 30 minutes
      // The T separates date parts from time parts
      fullCombined <- P2Y3M10DT4H30M
      stdout.println(`Full: ${fullCombined}`)

      // Complex with weeks: P2Y6W3DT8H5M8S
      // = 2 years, 6 weeks, 3 days (=45 days), 8 hours, 5 minutes, 8 seconds
      withWeeks <- P2Y6W3DT8H5M8S
      stdout.println(`With weeks: ${withWeeks}`)

      // === COMPONENT ACCESSORS ===

      d1 <- P2Y3M15DT4H30M45S
      stdout.println(`Years: ${d1.years()}, Months: ${d1.months()}, Days: ${d1.days()}`)
      stdout.println(`Hours: ${d1.hours()}, Minutes: ${d1.minutes()}, Seconds: ${d1.seconds()}`)

      // === DURATION ARITHMETIC ===

      // Addition and subtraction
      morning <- PT2H30M
      afternoon <- PT4H15M
      totalWork <- morning + afternoon
      difference <- afternoon - morning
      stdout.println(`Total: ${totalWork}, Difference: ${difference}`)

      // Multiply by Integer or Float
      doubled <- PT1H * 2
      tripled <- PT1H * 3
      scaled <- PT1H * 1.5
      stdout.println(`Doubled: ${doubled}, Tripled: ${tripled}, Scaled: ${scaled}`)

      // Divide by Integer or Float
      halved <- PT2H / 2
      thirded <- PT3H / 3
      stdout.println(`Halved: ${halved}, Thirded: ${thirded}`)

      // Compound assignment operators
      accumulator <- PT1H
      accumulator += PT30M
      require accumulator == PT1H30M
      accumulator -= PT15M
      require accumulator == PT1H15M
      accumulator *= 2
      require accumulator == PT2H30M
      accumulator /= 2
      require accumulator == PT1H15M
      stdout.println(`After compound ops: ${accumulator}`)

      // Negation
      positive <- PT5H
      negative <- -positive
      stdout.println(`Positive: ${positive}, Negative: ${negative}`)

      // === NEGATIVE DURATIONS ===

      // Subtracting a later time from an earlier time gives a negative duration
      negDur <- 06:15:12 - 12:04:09
      stdout.println(`Negative: ${negDur}`)
      require negDur == PT-5H-48M-57S

      // === DURATION FROM STRING ===

      // Construct from a string representation
      fromString <- Duration("P1Y6M")
      stdout.println(`From string: ${fromString}`)

      // Convert to string with $ operator
      asString <- $fullCombined
      stdout.println(`As string: ${asString}`)
      roundTrip <- Duration(asString)
      require roundTrip == fullCombined

      // === COMPARISON ===

      short <- PT30M
      long <- PT2H
      require short < long
      require long > short
      require short <> long
      require PT1H == PT1H
      require PT30M <= PT1H
      require PT2H >= PT1H
      stdout.println(`30min < 2h: ${short < long}`)

      // === UNSET DURATION ===

      unset <- Duration()
      require ~unset?
      stdout.println(`Unset duration isSet: ${unset?}`)

      // === USING DURATIONS WITH DATES AND TIMES ===

      // Date arithmetic with complex durations
      startDate <- 2020-01-15
      later <- startDate + P1Y6M10D
      stdout.println(`Date + P1Y6M10D: ${later}`)

      earlier <- startDate - P2M
      stdout.println(`Date - P2M: ${earlier}`)

      // Time arithmetic with wrapping
      startTime <- 22:00
      wrapped <- startTime + PT3H
      stdout.println(`22:00 + PT3H wraps to: ${wrapped}`)

      // DateTime arithmetic
      meeting <- 2024-06-15T10:30:00Z
      meetingEnd <- meeting + PT1H30M
      stdout.println(`Meeting ends: ${meetingEnd}`)

      // Subtracting temporal values produces a Duration
      dateGap <- 2024-12-25 - 2024-01-01
      stdout.println(`Days until Christmas: ${dateGap}`)

      timeGap <- 17:30 - 09:00
      stdout.println(`Work day: ${timeGap}`)

      // === MILLISECOND CONVERSION ===

      // Millisecond to Duration
      timeout <- 5400ms
      asDuration <- timeout.duration()
      stdout.println(`5400ms as duration: ${asDuration}`)

      // Duration to Millisecond
      fromDuration <- Millisecond(P2W)
      stdout.println(`P2W as millis: ${fromDuration}`)

Java: verbose Duration.ofHours()/Period split. Python: timedelta, no months/years. Go/Rust/JS: no duration literals. EK9: ISO 8601 PT1H30M literals, unified type years through seconds.

8601 arithmetic beginner days duration first format hours interval intro iso literal millisecond minutes months period seconds start timespan timing weeks years

Explanation: EK9 Duration uses short method names: hours(), minutes(), seconds() not getHours(), getMinutes(), getSeconds(). Triggers E50060 — method not resolved. See ek9 -h Duration for the full API.


Question: How do I work with regular expressions?

How do I split strings with a regex in EK9?

How do I extract groups from a regex match?

Answer:
EK9 has a built-in RegEx type with /pattern/ literal syntax (same as JavaScript), avoiding double-escaping.

REGEX LITERALS
  digitPattern <- /\d+/
  emailPattern <- /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
No string escaping needed: /\d+/ is what you mean. Java requires Pattern.compile("\\d+").

MATCHING
Bidirectional 'matches' operator:
  require 'hello123' matches /[a-z]+\d+/
  require /[a-z]+\d+/ matches 'hello123'
Negate with 'not matches':
  require 'hello' not matches /\d+/

SPLITTING STRINGS
Split with regex delimiter, works both directions:
  parts <- 'one:two:three'.split(/:/)
  alsoParts <- /:/.split('one:two:three')
Both produce List of String.

GROUP EXTRACTION
Capture groups extract matched portions via split():
  groups <- text.split(/\{(.*?)(\d+)(.*)/)
If matched, groups is List of String per capture group. If not matched, groups is unset (see Q29).
Access: groups.getOrDefault(0, "") or stream: cat groups | skip 1 | head 1 | collect as String

ESCAPING IN LITERALS
/\\/ matches literal backslash. /\// matches literal forward slash.
Shortcuts: \d \w \s . Quantifiers: ? * + {n} {n,m}. Anchors: ^ $ \b. Classes: [a-z] [^0-9].

REGEX COMPOSITION
Build patterns with + operator:
  combined <- /^Hello/ + / / + /World$/
Extend with +=:
  pattern <- /^Start/
  pattern += /.*End$/

COMPARISON AND IDENTITY
== and <> compare patterns. ? checks if set. $ converts to string. length returns pattern length.

Use 'ek9 -h RegEx' and 'ek9 -h String' for the full API.

See Q37 for string operations. See Q29 for tri-state and unset group extraction.

defines module qa.regex

  defines program
    RegExDemo()
      stdout <- Stdout()

      // === REGEX LITERALS ===

      // Regex literals use /pattern/ syntax - like JavaScript
      // No double-escaping needed (unlike Java's Pattern.compile("\\d+"))
      digitPattern <- /\d+/
      emailPattern <- /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
      stdout.println(`Digit pattern: ${digitPattern}`)
      require "user@example.com" matches emailPattern
      require "not-an-email" not matches emailPattern

      // === MATCHING ===

      // The 'matches' operator works in both directions
      require "hello123" matches /[a-z]+\d+/
      require /[a-z]+\d+/ matches "hello123"

      // 'not matches' for negation
      require "hello" not matches /\d+/

      // Match name variants with character classes and alternation
      namePattern <- /[S|s]te(?:ven?|phen)/
      require "Steve" matches namePattern
      require "steve" matches namePattern
      require "Stephen" matches namePattern
      require "Steven" matches namePattern
      require "Stephene" not matches namePattern

      // Character class with quantifier
      sixChars <- /[a-zA-Z0-9]{6}/
      require sixChars matches "arun32"
      require sixChars not matches "kkvarun32"
      require sixChars not matches "arun$2"

      // === SPLITTING STRINGS ===

      // Split works both directions: string.split(regex) or regex.split(string)
      colonRegEx <- /:/
      colonDelimited <- "one:two:three:four:five"

      fromString <- colonDelimited.split(colonRegEx)
      fromRegEx <- colonRegEx.split(colonDelimited)
      require fromString == fromRegEx
      stdout.println(`Split: ${fromString}`)
      require $fromString == "one,two,three,four,five"

      // === GROUP EXTRACTION ===

      // Capture groups in the pattern extract matched portions
      text <- "This is a sample Text 1234 with numbers in between."
      groupPattern <- /\{(.*?)(\d+)(.*)/
      groups <- text.split(groupPattern)

      stdout.println(`Groups: ${groups}`)
      require length groups == 3
      require groups.getOrDefault(0, "") == "This is a sample Text "
      require groups.getOrDefault(2, "") == " with numbers in between."

      // Extract specific group using stream pipeline
      justNumber <- cat groups | skip 1 | head 1 | collect as String
      require $justNumber == "1234"
      stdout.println(`Extracted number: ${justNumber}`)

      // Convert extracted text to Integer
      asInteger <- Integer(justNumber)
      require asInteger == 1234
      stdout.println(`As integer: ${asInteger}`)

      // Get last two groups
      lastTwo <- cat groups | tail 2 | collect as List of String
      require length lastTwo == 2

      // === NO MATCH RETURNS UNSET ===

      noNumbers <- "No digits here at all."
      noMatch <- noNumbers.split(groupPattern)
      require ~noMatch?
      stdout.println(`No match isSet: ${noMatch?}`)

      // === ESCAPING IN LITERALS ===

      // Backslash in regex literal: /\\/ matches a literal backslash
      backslashPattern <- /^Some\\Thing$/
      require backslashPattern matches "Some\Thing"

      // Forward slash in regex literal: /\// matches a literal forward slash
      slashPattern <- /^Some\/Thing$/
      require slashPattern matches "Some/Thing"

      // Fraction matching with escaped slash
      fractionPattern <- /.*\/.*/
      require fractionPattern matches "3/4"

      // === REGEX COMPOSITION ===

      // Build patterns with + operator
      prefix <- /^Hello/
      combined <- prefix + / / + /World$/
      require combined matches "Hello World"
      stdout.println(`Combined: ${combined}`)

      // += to extend a pattern
      growing <- /^Start/
      growing += /.*End$/
      require growing matches "Start and End"

      // === COMMON PATTERNS ===

      // Phone number
      require "555-1234" matches /^\d{3}-\d{4}$/

      // UK postcode (simplified)
      require "SW1A 1AA" matches /^[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}$/

      // ISO date format
      require "2024-06-15" matches /^\d{4}-\d{2}-\d{2}$/

      stdout.println("All regex tests passed")

Java: Pattern.compile() double-escaping, Pattern/Matcher ceremony. Python: re.compile(r'pattern'). JavaScript: /pattern/ (EK9 follows this). Rust/Go: no literal syntax. EK9: /pattern/ literal, bidirectional matches, split both directions, no double-escaping.

anchor beginner capture character class escape expression first group intro literal match matches migrate pattern quantifier regex regular split start

regex
regular expression
pattern matching string

Explanation: EK9 rejects variable names that shadow operator keywords. 'matches' is a reserved operator keyword. Use descriptive names like 'digitPattern' or 'emailPattern'. See ek9 -h E50001 for details.


Question: How do I work with colours?

How does HSL colour manipulation work in EK9?

Answer:
Colour is EK9's built-in type for RGB colour values with hex literal syntax. No imports needed. Uses British spelling 'Colour' consistently.

LITERALS
  #FF0000         Pure red (6-digit RGB)
  #FF186276       With alpha (8-digit ARGB)

OUTPUT FORMATS
  .RGB() returns '#RRGGBB', .RGBA() returns '#RRGGBBAA', .ARGB() returns '#AARRGGBB'.

HSL MANIPULATION
Immutable methods return NEW colours: withLightness(80), withSaturation(90), withHue(200). Access HSL values with .hue(), .saturation(), .lightness(). Control transparency with withOpaque(percentage).

COLOUR ARITHMETIC
Add/subtract to blend/remove RGB components (auto-clamped to 0-255):
  purple <- #FF0000 + #0000FF

BITS INTEROP
  .bits() extracts 32-bit ARGB value. Colour(bitsValue) constructs from Bits.

Comparison: ==, <>, <, >, <=, >=, <=>.

Use 'ek9 -h Colour' to see the full API.

See Q37 for string interpolation. See Q40 for Bits interop.

defines module qa.colour

  defines program
    ColourDemo()
      stdout <- Stdout()

      // === COLOUR LITERALS ===

      // Hex literal with alpha channel: #AARRGGBB (ARGB format)
      opaqueBlue <- #FF186276
      stdout.println(`Opaque blue: ${opaqueBlue}`)

      // Hex literal without alpha: #RRGGBB (RGB format)
      pureRed <- #FF0000
      pureGreen <- #00FF00
      pureBlue <- #0000FF
      stdout.println(`Red: ${pureRed}, Green: ${pureGreen}, Blue: ${pureBlue}`)

      // Unset colour
      unsetColour <- Colour()
      require ~unsetColour?

      // === OUTPUT FORMATS ===

      // Three different hex string formats
      stdout.println(`RGB: ${opaqueBlue.RGB()}`)
      stdout.println(`RGBA: ${opaqueBlue.RGBA()}`)
      stdout.println(`ARGB: ${opaqueBlue.ARGB()}`)

      // === HSL ACCESSORS ===

      // Hue (0-360), Saturation (0-100%), Lightness (0-100%)
      stdout.println(`Hue: ${opaqueBlue.hue()}`)
      stdout.println(`Saturation: ${opaqueBlue.saturation()}`)
      stdout.println(`Lightness: ${opaqueBlue.lightness()}`)

      // === HSL MANIPULATION ===

      // Each with* method returns a NEW colour (immutable)
      lighterBlue <- opaqueBlue.withLightness(80)
      stdout.println(`Lighter: ${lighterBlue}`)

      moreSaturated <- lighterBlue.withSaturation(90)
      stdout.println(`More saturated: ${moreSaturated}`)

      shiftedHue <- opaqueBlue.withHue(200)
      stdout.println(`Shifted hue: ${shiftedHue}`)

      // === ALPHA CHANNEL (TRANSPARENCY) ===

      // withOpaque(percentage) sets opacity: 100=fully opaque, 0=fully transparent
      semiTransparent <- opaqueBlue.withOpaque(50)
      stdout.println(`50% opaque: ${semiTransparent}`)

      mostlyOpaque <- opaqueBlue.withOpaque(80)
      stdout.println(`80% opaque: ${mostlyOpaque}`)

      // === COLOUR ARITHMETIC ===

      // Add colours: blends RGB components (clamped to 0-255)
      purple <- pureRed + pureBlue
      stdout.println(`Red + Blue = Purple: ${purple}`)

      yellow <- pureRed + pureGreen
      stdout.println(`Red + Green = Yellow: ${yellow}`)

      cyan <- pureGreen + pureBlue
      stdout.println(`Green + Blue = Cyan: ${cyan}`)

      // Subtract colours: removes RGB components (clamped to 0)
      backToRed <- purple - pureBlue
      stdout.println(`Purple - Blue = Red: ${backToRed}`)
      require backToRed == pureRed

      backToGreen <- yellow - pureRed
      stdout.println(`Yellow - Red = Green: ${backToGreen}`)
      require backToGreen == pureGreen

      // Subtract specific amounts of a channel
      lessRed <- moreSaturated - #9A0000
      stdout.println(`Less red: ${lessRed}`)

      moreBlue <- lessRed + #00001D
      stdout.println(`More blue: ${moreBlue}`)

      // === BITS INTEROP ===

      // Convert Colour to Bits and back
      colourBits <- opaqueBlue.bits()
      stdout.println(`As bits: ${colourBits}`)

      fromBits <- Colour(colourBits)
      require fromBits == opaqueBlue

      // Build colour from raw bit patterns
      redBits <- 0b11111111111111110000000000000000
      redFromBits <- Colour(redBits)
      stdout.println(`Red from bits: ${redFromBits}`)

      // === PROGRAMMATIC LIGHTENING ===

      // Calculate a percentage lighter
      currentLightness <- opaqueBlue.lightness()
      proposedLightness <- currentLightness * 1.9
      clampedLightness <- proposedLightness <? 100
      percentLighter <- opaqueBlue.withLightness(clampedLightness)
      stdout.println(`90% lighter: ${percentLighter}`)

      // === COMPARISON ===

      require pureRed <> pureBlue
      require #FF0000 == #FF0000
      stdout.println(`Red == Red: ${pureRed == #FF0000}`)

Java: java.awt.Color tied to AWT, no literals, no HSL. CSS: #hex but declarative only. Python/Rust/Go/JS: no built-in colour type. EK9: built-in #RRGGBB literals, HSL methods, colour arithmetic with clamping, Bits interop.

alpha argb beginner bits blend color colour first hex hsl hue intro lightness migrate opacity rgb rgba saturation start transparent

Explanation: EK9 Colour uses 'withLightness' not 'setLightness'. Methods return new immutable values rather than mutating. Check available methods with 'ek9 -h Colour'. See ek9 -h E50060 for details.


Question: How do I work with money and currency in EK9?

How does EK9 handle currency arithmetic and rounding?

What arithmetic operations can I do with Money in EK9?

How does rounding work for Money in EK9?

Answer:
EK9 has a built-in Money type with literal syntax, automatic rounding, and per-currency precision. No financial library needed.

MONEY LITERALS
Format: amount#CURRENCY using ISO 4217 three-letter currency codes:
  10#GBP            British pounds (auto-padded to 10.00#GBP)
  30.20#USD         US dollars
  6798.9288#CLF     Chilean Unidad de Fomento (4 decimal places)
  1500#JPY          Japanese yen (0 decimal places)
The compiler knows every ISO 4217 currency and its correct decimal precision. GBP and USD use 2 decimals, JPY uses 0, CLF uses 4. Amounts are automatically padded or rounded to the correct precision for the currency. Money() creates an unset money value.

AUTOMATIC ROUNDING
EK9 uses HALF_UP rounding — the rounding everyone learns in school. When the digit being dropped is 5 or above, round away from zero:
  99.51#GBP / 2 = 49.755 rounds to 49.76#GBP
Rounding is automatic and uses each currency's correct precision. You never need to specify a rounding mode or scale.

ARITHMETIC OPERATORS
Addition and subtraction work between money values of the SAME currency:
  money + money      Addition (10#GBP + 89.51#GBP = 99.51#GBP)
  money - money      Subtraction (500#GBP - 100#GBP = 400#GBP)
Multiplication and division scale by a Float or Integer:
  money * float      49.76#GBP * -8.754 = -435.60#GBP
  money / integer    99.51#GBP / 2 = 49.76#GBP (HALF_UP)
Dividing money by money gives the ratio as a Float:
  935.60#GBP / 155.93#GBP = 6.0001 (a Float, not Money)

COMPOUND ASSIGNMENT
  working += 4.07#GBP     Add to existing
  working -= 0.56#GBP     Subtract from existing
  working *= 0.666        Multiply by factor
  working /= 4            Divide by number

UNARY OPERATORS
  -amount          Negation
  abs amount       Absolute value
  sqrt amount      Square root (rounded to currency precision)
  amount ^ 6       Power (exponentiation)

COMPARISON
Money supports ==, <>, <, >, <=, >=, <=> for same-currency comparison.

See Q149 for currency safety (mixed currency, division by zero, and why Money is built-in). See Q150 for currency conversion and locale formatting. Use 'ek9 -h Money' to see the full API.

See Q36 for dimension. See Q149 for money safety.

defines module qa.money

  defines program
    MoneyDemo()
      stdout <- Stdout()

      // === MONEY LITERALS ===

      // Format: amount#CURRENCY using ISO 4217 codes
      tenPounds <- 10#GBP
      thirtyDollars <- 30.20#USD
      chilean <- 6798.9288#CLF
      stdout.println(`GBP: ${tenPounds}`)
      stdout.println(`USD: ${thirtyDollars}`)
      stdout.println(`CLF: ${chilean}`)

      // Auto-padding: 10#GBP equals 10.00#GBP
      require tenPounds == 10.00#GBP

      // Unset money
      unsetMoney <- Money()
      require ~unsetMoney?

      // === ARITHMETIC WITH AUTO-ROUNDING ===

      // Addition (same currency only)
      total <- tenPounds + 89.51#GBP
      require total == 99.51#GBP
      stdout.println(`Total: ${total}`)

      // Division rounds using HALF_UP: 99.51 / 2 = 49.755 rounds to 49.76
      halved <- total / 2
      require halved == 49.76#GBP
      stdout.println(`Halved (HALF_UP): ${halved}`)

      // Multiply by Float
      negativeAmount <- halved * -8.754
      require negativeAmount == -435.60#GBP
      stdout.println(`Negative: ${negativeAmount}`)

      // Subtraction
      recovery <- 500#GBP - negativeAmount
      require recovery == 935.60#GBP

      // Complex expression
      calculated <- (recovery * 3) / 18
      require calculated == 155.93#GBP
      stdout.println(`Calculated: ${calculated}`)

      // Money / Money gives Float (the ratio)
      ratio <- recovery / calculated
      stdout.println(`Ratio: ${ratio}`)

      // === COMPOUND ASSIGNMENT ===

      working <- 155.93#GBP
      working += 4.07#GBP
      require working == 160.00#GBP

      working *= 0.666
      require working == 106.56#GBP

      working -= 0.56#GBP
      require working == 106.00#GBP

      working /= 4
      require working == 26.50#GBP

      // === UNARY OPERATORS ===

      // Negation
      working := -working
      require working == -26.50#GBP

      // Absolute value
      working := abs working
      require working == 26.50#GBP

      // Square root
      working := sqrt working
      require working == 5.15#GBP

      // Power
      working := working ^ 6
      require working == 18657.07#GBP
      stdout.println(`After power: ${working}`)

      // === COMPARISON ===

      require tenPounds < total
      require total > tenPounds
      require tenPounds <> total
      require tenPounds <= 10.00#GBP
      require tenPounds >= 10.00#GBP

Java: BigDecimal with explicit RoundingMode at every operation, verbose new BigDecimal("10.50").multiply(rate).setScale(2, RoundingMode.HALF_UP), Currency class separate from amount, no literal syntax, mixed currency not detected. Python: decimal.Decimal requires context for rounding, no currency awareness, money libraries (py-moneyed) needed. JavaScript: IEEE 754 floating-point causes 0.1+0.2!=0.3 bugs, Dinero.js or similar needed. Ruby: no built-in, money gem needed. Go: no built-in, shopspring/decimal or similar. Rust: no built-in, rust_decimal crate. C#: decimal type has precision but no currency, no literal syntax for currency. EK9: built-in amount#CURRENCY literals, automatic HALF_UP rounding, per-currency precision, mixed currency safety, stream collection, currency conversion, no imports needed.

arithmetic beginner convert currency decimal exchange financial first gbp half_up intro iso4217 money precision rounding start usd

Explanation: EK9 Money uses operators for arithmetic, not method calls. Use + for addition, - for subtraction, not .add() or .subtract(). Triggers E50060 — method not resolved. See ek9 -h Money for the full API.


Question: How do I work with measurements and units in EK9?

Does EK9 have a built-in unit type for measurements?

How does EK9 prevent unit mismatch errors?

How do I convert between units like miles and kilometres in EK9?

Answer:
Dimension is EK9's built-in type for unit-aware measurements. Any number followed by a unit suffix becomes a literal: 1cm, 10px, 4.5em, 8mile. The unit suffix is freeform.

Same-unit arithmetic works normally. Different-unit operations return unset (safety against unit mismatch). Scaling by number preserves the unit. Dimension/Dimension of same unit gives a Float ratio. Convert units with convert(): eightMiles.convert(1.609344km).

Operators: +, -, *, /, ^, abs, sqrt, comparison, <?. Use #< to extract numeric value.

Use 'ek9 -h Dimension' to see the full API.

See Q35 for Money. See Q39 for Integer/Float. See Q44 for locale formatting.

defines module qa.dimension

  defines program
    DimensionDemo()
      stdout <- Stdout()

      // === DIMENSION LITERALS ===

      // Format: number followed by unit suffix (any string)
      oneCentimetre <- 1cm
      tenPixels <- 10px
      fourAndHalfEm <- 4.5em
      oneAndHalfEm <- 1.5em
      eightMiles <- 8mile
      stdout.println(`cm: ${oneCentimetre}, px: ${tenPixels}, em: ${fourAndHalfEm}`)

      // Unset dimension
      unsetDim <- Dimension()
      require ~unsetDim?

      // === ARITHMETIC: SCALING BY NUMBER ===

      // Multiply dimension by number — scales the amount, keeps the unit
      doubled <- oneCentimetre * 2
      require doubled == 2cm
      stdout.println(`1cm * 2 = ${doubled}`)

      // Divide dimension by number — scales down
      divided <- tenPixels / 5
      require divided == 2px
      stdout.println(`10px / 5 = ${divided}`)

      // Add a plain number to a dimension
      wider <- fourAndHalfEm + 0.6
      require wider == 5.1em
      stdout.println(`4.5em + 0.6 = ${wider}`)

      // === ARITHMETIC: SAME-UNIT ADDITION ===

      // Add dimensions with the SAME unit
      combined <- fourAndHalfEm + oneAndHalfEm
      require combined == 6em
      stdout.println(`4.5em + 1.5em = ${combined}`)

      // === ARITHMETIC: DIMENSION / DIMENSION GIVES A NUMBER ===

      // Dividing dimension by dimension of same unit gives a unitless Float
      ratio <- fourAndHalfEm / oneAndHalfEm
      require ratio == 3
      stdout.println(`4.5em / 1.5em = ${ratio} (unitless)`)

      // === SAFETY: DIFFERENT UNITS RETURN UNSET ===

      // Cannot add centimetres to pixels — different units
      invalidAdd <- oneCentimetre + tenPixels
      require ~invalidAdd?
      stdout.println(`cm + px isSet: ${invalidAdd?}`)

      // Comparison across different units also returns unset
      invalidCompare <- oneCentimetre <> tenPixels
      require ~invalidCompare?

      // === UNIT CONVERSION ===

      // Define a conversion factor: 1 mile = 1.609344 km
      numberOfKmInMiles <- 1.609344km
      inKM <- eightMiles.convert(numberOfKmInMiles)
      require inKM == 12.874752km
      stdout.println(`${eightMiles} in km is ${inKM}`)

      // But after conversion, original and converted have DIFFERENT units
      // So adding them returns unset — must pick one unit system
      stillInvalid <- eightMiles + inKM
      require ~stillInvalid?

      // === EXTRACT NUMERIC VALUE ===

      // The #< operator extracts the number without the unit
      numericValue <- #< inKM
      require numericValue == 12.874752
      stdout.println(`Numeric value without unit: ${numericValue}`)

      // === UNARY OPERATORS ===

      // Negation
      returnJourney <- -eightMiles
      stdout.println(`Return journey: ${returnJourney}`)

      // Absolute value
      require abs returnJourney == eightMiles

      // Square root
      sqrtEm <- sqrt combined
      stdout.println(`sqrt 6em = ${sqrtEm}`)

      // Power
      squared <- inKM ^ 2
      stdout.println(`Squared: ${squared}`)

      // === COMPOUND ASSIGNMENT AND INCREMENT ===

      working <- 5.1em
      working += 0.9
      require working == 6em
      stdout.println(`After +=: ${working}`)

      working++
      require working == 7em
      stdout.println(`After ++: ${working}`)

      // === COMPARISON ===

      require fourAndHalfEm < combined
      require combined > fourAndHalfEm
      require fourAndHalfEm <> combined
      require fourAndHalfEm <= 4.5em
      require fourAndHalfEm >= 4.5em

      // Min operator
      lesser <- fourAndHalfEm <? combined
      require lesser == fourAndHalfEm
      stdout.println(`Min of 4.5em and 6em: ${lesser}`)

Java: plain double, manual unit tracking. F#: compile-time units. CSS: declarative only. EK9: freeform unit literals, unit-safe arithmetic returns unset for mismatches, convert() for boundaries.

beginner convert css dimension first intro kilometre mars measurement metre mile orbiter pixel scaling start unit

Explanation: EK9 Dimension does not have a getValue() method. Use operators for arithmetic and the $ operator for string representation. Triggers E50060 — method not resolved. See ek9 -h Dimension for the full API.


Question: How do I work with strings in EK9?

How does string interpolation work in EK9?

Can I use strings as streams of characters in EK9?

Answer:
EK9 strings use double quotes for plain text and backticks for interpolation. Strings are streamable as character sequences, so substring extraction and text analysis use the same stream pipeline syntax as collections.

LITERALS AND INTERPOLATION
  greeting <- "Hello World"
  stdout.println(`Welcome to ${lang} version ${ver}`)
Escape sequences: \t
 \\ \" \uXXXX.

KEY METHODS
  .upperCase(), .lowerCase(), .trim(), .length(), .count(char)
  .rightPadded(n), .leftPadded(n)
Concatenation with +. Use 'ek9 -h String' for the full API.

STREAMABLE STRINGS
Strings stream as characters using skip/head/map/filter/collect:
  brownFox <- cat sentence | skip 10 | head 9 | collect as String
This replaces substring() with unambiguous skip-and-take semantics. Works identically on strings and collections.

Comparison: ==, <>, <, >, <=, >=, <=>. Hashcode: #?.

See Q33 for regex matching. See Q38 for the Character type. See Q43 for backtick escaping. See Q44 for locale formatting. See Q113 for text construct. See Q170 for string length. See Q171 for string contains. See Q242 for conversion operators.

defines module qa.strings

  defines function

    charToLowerCaseString()
      -> ch as Character
      <- result as String: ch? <- $ch.lowerCase() else String()

    stringComparator()
      ->
        s1 as String
        s2 as String
      <-
        comparison as Integer: s1 <=> s2

    justStringValue()
      -> s1 as String
      <- rtn as String: s1

    isO()
      -> ch as Character
      <- found as Integer: ch == 'o' or ch == 'O' <- 1 else Integer()

  defines program
    StringDemo()
      stdout <- Stdout()

      // === STRING LITERALS ===

      // Double-quoted strings
      greeting <- "Hello World"
      stdout.println(greeting)

      // String interpolation with backticks
      languageName <- "EK9"
      versionNumber <- 1
      stdout.println(`Welcome to ${languageName} version ${versionNumber}`)

      // Escape sequences: \t tab, \n newline, \\ backslash, \" quote
      withTab <- "Column1\tColumn2"
      stdout.println(withTab)

      withNewline <- "Line1\nLine2"
      stdout.println(withNewline)

      // Unicode escapes for non-ASCII characters
      danish <- "\u00C6\u00D8\u00C5"
      stdout.println(`Danish letters: ${danish}`)

      // Unset string
      unsetString <- String()
      require ~unsetString?

      // === STRING METHODS ===

      sentence <- "The Quick Brown Fox"

      // Case conversion
      stdout.println(`Upper: ${sentence.upperCase()}`)
      stdout.println(`Lower: ${sentence.lowerCase()}`)

      // Padding
      stdout.println(`Right padded: [${sentence.rightPadded(30)}]`)
      stdout.println(`Left padded: [${sentence.leftPadded(30)}]`)

      // Trimming
      paddedSentence <- sentence.rightPadded(30)
      stdout.println(`Trimmed: [${paddedSentence.trim()}]`)

      // Length — two equivalent syntaxes
      stdout.println(`Length (method): ${sentence.length()}`)
      stdout.println(`Length (operator): ${length sentence}`)

      // Count character occurrences
      stdout.println(`Count of 'o': ${sentence.count('o')}`)

      // === CONCATENATION ===

      part1 <- "The Quick Brown Fox"
      part2 <- "Jumps Over The Lazy Dog"
      fullSentence <- `${part1} ${part2}`
      stdout.println(fullSentence)

      // === HASHCODE ===

      hashValue <- #? sentence
      stdout.println(`Hashcode: ${hashValue}`)

      // === STRINGS ARE STREAMABLE ===

      // Strings stream as character sequences
      // Extract a substring using skip and head
      brownFox <- cat fullSentence | skip 10 | head 9 | collect as String
      stdout.println(`Extracted: [${brownFox}]`)

      // Going beyond string length is safe — returns what is available
      endPart <- cat fullSentence | skip 35 | head 20 | collect as String
      stdout.println(`End part: [${endPart}]`)

      // Join a list of strings via stream collect
      spacer <- " "
      wordParts <- [part1, spacer, part2]
      rejoined <- cat wordParts | collect as String
      stdout.println(`Rejoined: ${rejoined}`)

      // Count occurrences of o/O using stream map
      oCount <- cat fullSentence | map with isO | collect as Integer
      stdout.println(`Count of o/O: ${oCount}`)

      // Find unique characters used (sorted, lowercase)
      charsUsed <- cat fullSentence | map with charToLowerCaseString | sort with stringComparator | uniq by justStringValue | collect as String
      stdout.println(`Unique chars: [${charsUsed}]`)

      // === COMPARISON ===

      alpha <- "abc"
      beta <- "def"
      require alpha < beta
      require beta > alpha
      require alpha <> beta
      require alpha == "abc"
      require alpha <= "abc"
      require alpha >= "abc"

      // Min operator
      fruit1 <- "apple"
      fruit2 <- "banana"
      lesser <- fruit1 <? fruit2
      require lesser == "apple"
      stdout.println(`Min: ${lesser}`)

Java: no native interpolation, substring() methods, no streamable characters. Python: f-strings, slicing [start:end]. Kotlin: similar $var templates. EK9: backtick interpolation, strings natively streamable as characters, skip/head replaces substring().

backtick beginner concatenation count escape first interpolation intro length lowercase migrate pad start stream string substring trim unicode uppercase

Explanation: The + operator on String requires another String operand. Adding an Integer to a String triggers E07620 — type incompatibility. Use $ to convert to string first: "Hello" + $number. See ek9 -h E07620 for details.


Question: How do I work with characters in EK9?

Does EK9 have a character type separate from strings?

How do I convert a character to a string in EK9?

How does the promote operator work with characters?

How do I iterate through characters in EK9?

Answer:
EK9 has a dedicated Character type for single Unicode characters, separate from String. Single quotes for characters, double quotes for strings. No implicit conversion — use #^ promote operator.

CHARACTER LITERALS
  letter <- 'a'
  accent as Character := '\u00E9'
Character() creates unset, Character(stringValue) creates from single-char string.

CASE CONVERSION
Instance methods:
  upper <- letter.upperCase()
  lower <- upper.lowerCase()
  roundTrip <- letter.upperCase().lowerCase()

PROMOTE TO STRING: THE #^ OPERATOR
  asString <- #^ letter               Prefix syntax
  alsoString <- letter.#^()            Method call syntax
  typed as String := #^letter          With explicit type
All conversions explicit and visible. $ also converts to string for display, but #^ is the semantic type promotion.

LENGTH
Set character has length 1, unset has length 0:
  length letter          returns 1
  letter.length()        method call syntax

HASHCODE
  hashValue <- #? letter               Prefix syntax
  hashValue <- letter.#?()             Method call syntax

COPY OPERATOR
  copied <- Character()
  copied :=: 'z'

INCREMENT AND DECREMENT
  working <- 'a'
  working++              now 'b'
  working--              back to 'a'

COMPARISON
By Unicode code point: ==, <>, <, >, <=, >=, <=>.

TRI-STATE
Comparing with unset returns unset Boolean:
  result <- Character() == 'a'     result is unset

CHARACTERS AND STRINGS TOGETHER
Strings are streamable as character sequences (see Q37). #^ converts Characters back to Strings for stream operations.

WHY SEPARATE?
Python/JavaScript have no character type. EK9 keeps them distinct: Character has case conversion, code-point comparison, ++/--. String has trim(), padded(), count(). The #^ operator makes the boundary explicit.

Use 'ek9 -h Character' for the full API.

See Q37 for strings. See Q43 for escape and interpolation.

defines module qa.character

  defines program
    CharacterDemo()
      stdout <- Stdout()

      // === CHARACTER LITERALS ===

      // Single-quoted character literals
      letter <- 'a'
      digit <- '9'
      stdout.println(`Letter: ${letter}, Digit: ${digit}`)

      // Unicode escape for non-ASCII
      accent as Character := '\u00E9'
      stdout.println(`Accented: ${accent}`)

      // Unset character
      unsetChar <- Character()
      require ~unsetChar?
      stdout.println(`Unset isSet: ${unsetChar?}`)

      // === CASE CONVERSION ===

      // Instance methods — not static utility calls
      upper <- letter.upperCase()
      stdout.println(`Uppercase of ${letter}: ${upper}`)

      lower <- upper.lowerCase()
      stdout.println(`Lowercase of ${upper}: ${lower}`)

      // Method chaining
      roundTrip <- letter.upperCase().lowerCase()
      require roundTrip == letter
      stdout.println(`Round trip: ${roundTrip}`)

      // === PROMOTE TO STRING ===

      // The #^ operator explicitly converts Character to String
      asString <- #^ letter
      stdout.println(`Promoted: ${asString}`)

      // Also available as method call syntax
      alsoString <- letter.#^()
      stdout.println(`Method promote: ${alsoString}`)

      // Explicit typing shows the conversion
      typed as String := #^letter
      require typed?
      stdout.println(`Typed promote: ${typed}`)

      // === LENGTH ===

      // A set character always has length 1
      stdout.println(`Length of '${letter}': ${length letter}`)
      stdout.println(`Length method: ${letter.length()}`)

      // An unset character has length 0
      stdout.println(`Unset length: ${length unsetChar}`)

      // === HASHCODE ===

      // Two syntax forms for hashcode
      hashPrefix <- #? letter
      hashMethod <- letter.#?()
      stdout.println(`Hashcode prefix: ${hashPrefix}`)
      stdout.println(`Hashcode method: ${hashMethod}`)

      // String interpolation with hashcode
      stdout.println(`Hashcode of ${accent} is ${#?accent}`)

      // === COPY OPERATOR ===

      // :=: deep copies one character to another
      copied <- Character()
      copied :=: 'z'
      require copied == 'z'
      stdout.println(`Copied: ${copied}`)

      // === INCREMENT AND DECREMENT ===

      // ++ moves to next character, -- to previous
      working <- 'a'
      working++
      require working == 'b'
      stdout.println(`After ++: ${working}`)

      working--
      require working == 'a'
      stdout.println(`After --: ${working}`)

      // === COMPARISON ===

      // Characters compare by Unicode value
      require 'a' < 'z'
      require 'z' > 'a'
      require 'a' <> 'z'
      require 'a' == 'a'
      require 'a' <= 'a'
      require 'a' >= 'a'

      // Comparisons with unset return unset (not true, not false)
      unsetCompare <- unsetChar == letter
      require ~unsetCompare?
      stdout.println(`Unset compare isSet: ${unsetCompare?}`)

      // Spaceship operator for sorting
      ordering <- letter <=> 'z'
      stdout.println(`letter <=> 'z': ${ordering}`)

Java: primitive char (16-bit UTF-16), static Character.toUpperCase(). Python/JS: no character type. Rust: char is full Unicode scalar. EK9: single-quoted literals, full Unicode, instance methods .upperCase()/.lowerCase(), #^ promote to String, ++/-- operators.

beginner char character convert decrement first increment intro literal lowercase migrate promote quote single start string unicode uppercase

Explanation: EK9 Character uses upperCase() and lowerCase(), not toUpper() or toUpperCase(). Triggers E50060 — method not resolved. See ek9 -h Character for the full API.


Question: How do Integer and Float work in EK9?

What numeric types does EK9 have and how do hex and binary literals work?

Why does division by zero return unset instead of throwing an exception?

Why does EK9 only have two numeric types instead of byte, short, long, unsigned?

Answer:
EK9 has two numeric types: Integer (64-bit signed whole numbers) and Float (64-bit IEEE 754 floating-point). Both are full objects, not primitives.

INTEGER LITERALS
Decimal: 42, -17, 0
Hex: 0xFF, 0x1F (prefix 0x)
Binary: 0b1010, 0b11001100 (prefix 0b)
Underscores for readability: 1_000_000, 0xFF_FF
Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

FLOAT LITERALS
Decimal: 3.14, -2.5, 0.0
Scientific notation: 4.9E-324, 1.7976931348623157E308

ARITHMETIC OPERATORS
Both types support: + - * / ^ (power), abs, sqrt
  17 + 5 = 22,  17 / 5 = 3 (integer division)
  3.14 * 2.0 = 6.28,  2.0 ^ 0.5 = square root of 2

DIVISION BY ZERO RETURNS UNSET
This is one of EK9's most important safety decisions. Java throws ArithmeticException for integer division by zero and produces NaN/Infinity for float. JavaScript silently produces Infinity or NaN that propagates through calculations. EK9 returns unset for ANY division by zero:
  90 / 0 is unset,  0 / 0 is unset,  90.0 / 0.0 is unset
But 0 / 90 = 0 (zero divided by non-zero is valid).
IEEE 754 NaN was designed to let calculations continue, hoping programmers would check later. 50 years shows they don't. NaN propagates silently producing garbage, and NaN != NaN breaks equality. EK9's unset achieves the same 'continue calculating' benefit but the ? operator makes checking explicit.

PROMOTE INTEGER TO FLOAT: #^
Use the #^ operator to convert Integer to Float:
  intVal <- 42
  floatVal <- #^ intVal
Float has no promote because it is already the widest numeric type. This explicit promotion means you always know where Integer becomes Float.

WHY ONLY TWO NUMERIC TYPES?
Java has 6 numeric primitives plus boxed wrappers. Rust has 12 numeric types. Go has 15+. EK9 has Integer and Float. Period. No byte, short, long, unsigned, int8, float32.
This eliminates: silent narrowing conversions (Java long to int), signed/unsigned confusion (C unsigned overflow), float-to-double precision surprises, integer overflow wrapping. One whole number type, one decimal type. If you need arbitrary precision decimals, use Money. If you need bit manipulation, use Bits.

See Q91 for advanced features (mod vs rem, factorial, bitwise, locale formatting, streams). See Q40 for the Bits type. See Q25 for more on the #^ promote operator. See Q44 for Locale formatting. See Q240 for arithmetic and math operators (mod, rem, abs, sqrt, ^). Use 'ek9 -h Integer' and 'ek9 -h Float' for the full API.

defines module qa.integer.and.float

  defines program
    IntegerAndFloatDemo()
      stdout <- Stdout()

      // === INTEGER LITERALS ===

      decimal <- 42
      hex <- 0xFF
      binary <- 0b10101010
      big <- 1_000_000

      stdout.println(`Decimal: ${decimal}`)
      stdout.println(`Hex 0xFF: ${hex}`)
      stdout.println(`Binary 0b10101010: ${binary}`)
      stdout.println(`With underscores: ${big}`)

      // === BASIC ARITHMETIC ===

      a <- 17
      b <- 5
      stdout.println(`${a} + ${b} = ${a + b}`)
      stdout.println(`${a} - ${b} = ${a - b}`)
      stdout.println(`${a} * ${b} = ${a * b}`)
      stdout.println(`${a} / ${b} = ${a / b}`)

      // Power, absolute value, square root
      stdout.println(`2 ^ 10 = ${2 ^ 10}`)
      stdout.println(`abs -49 = ${abs -49}`)
      stdout.println(`sqrt 49 = ${sqrt 49}`)

      // === FLOAT BASICS ===

      pi <- 3.14159
      scientific <- 2.5E10
      stdout.println(`Pi: ${pi}`)
      stdout.println(`Scientific: ${scientific}`)
      stdout.println(`2.0 ^ 0.5 = ${2.0 ^ 0.5}`)
      stdout.println(`abs -16.0 = ${abs -16.0}`)

      // === DIVISION BY ZERO RETURNS UNSET ===

      zero <- 0
      divResult <- 90 / zero
      stdout.println(`90 / 0 isSet: ${divResult?}`)

      fzero <- 0.0
      fResult <- 90.0 / fzero
      stdout.println(`90.0 / 0.0 isSet: ${fResult?}`)

      // Zero divided by non-zero is valid
      normalDiv <- 0 / 90
      stdout.println(`0 / 90 = ${normalDiv}`)

      // === PROMOTE INTEGER TO FLOAT ===

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

      // === COMPARISON ===

      five <- 5
      anotherFive <- 5
      ten <- 10
      stdout.println(`5 < 10: ${five < ten}`)
      stdout.println(`5 == 5: ${five == anotherFive}`)
      ordering <- five <=> ten
      stdout.println(`5 <=> 10: ${ordering}`)

Java: 6 numeric primitives (byte, short, int, long, float, double) plus boxed wrappers, ArithmeticException on integer div-by-zero, Double.NaN for float div-by-zero. Python: arbitrary precision integers, float backed by C double, ZeroDivisionError. JavaScript: single Number type (IEEE 754 double), Infinity/NaN, no integer type. Rust: 12 numeric types (i8-i128, u8-u128, f32, f64), panic on overflow in debug. Go: 15+ numeric types, panic on integer div-by-zero, math.NaN for float. EK9: Integer (64-bit) and Float (64-bit) only, div-by-zero returns unset, explicit #^ promote.

arithmetic basics beginner binary division first float hex integer intro literal migrate nan number numeric promote start underscore unset zero

Explanation: A String value cannot be assigned to an Integer variable. EK9 is strongly typed and does not implicitly convert between unrelated types. Use Integer("42") for explicit parsing. See ek9 -h E50030 for details.


Question: How does the Bits type work in EK9?

What is the difference between Integer and Bits in EK9?

How do I manipulate individual bits in EK9?

Why does EK9 have a separate Bits type instead of using integers for bit operations?

Answer:
Bits is EK9's dedicated type for bit-level operations — a variable-length ordered collection of bits, NOT an integer. Binary literals: 0b010011. The + operator CONCATENATES bits.

Bitwise: and, or, xor, ~ (not). Shift: << grows (adds zeros), >> shrinks. Integer does NOT have shift operators. Streamable as Booleans (LSB first). Integrates with Colour via .bits() and Colour(bitsValue).

Use 'ek9 -h Bits' to see the full API.

See Q39 for Integer. See Q34 for Colour-to-Bits integration. See Q244 for bitwise operators.

defines module qa.bits

  defines function

    booleanToBits()
      -> b as Boolean
      <- rtn as Bits: Bits(b)

  defines program
    BitsDemo()
      stdout <- Stdout()

      // === BITS LITERALS ===

      // Binary literals with 0b prefix
      a <- 0b010011
      b <- 0b101010
      stdout.println(`a: ${a}`)
      stdout.println(`b: ${b}`)

      // Unset bits
      unsetBits <- Bits()
      require ~unsetBits?
      stdout.println(`Unset isSet: ${unsetBits?}`)

      // === BITWISE OPERATORS ===

      // and, or, xor
      anded <- a and b
      ored <- a or b
      xored <- a xor b
      stdout.println(`a and b: ${anded}`)
      stdout.println(`a or b: ${ored}`)
      stdout.println(`a xor b: ${xored}`)

      // not operator (two syntaxes)
      notted <- ~a
      alsoNotted <- not a
      stdout.println(`not a (~): ${notted}`)
      stdout.println(`not a: ${alsoNotted}`)

      // === SHIFT OPERATORS ===

      c <- 0b01010011

      // Shift left adds zeros on right, grows the bit sequence
      shiftedLeft <- c << 1
      stdout.println(`c << 1: ${shiftedLeft}`)

      shiftedLeft2 <- c << 2
      stdout.println(`c << 2: ${shiftedLeft2}`)

      // Shift right removes bits from right
      shiftedRight <- c >> 2
      stdout.println(`c >> 2: ${shiftedRight}`)

      // === CONCATENATION WITH + (NOT ADDITION) ===

      // + joins bits together — this is NOT numeric addition
      set6 <- 0b010011
      set7 <- 0b101010
      joined <- set6 + set7
      stdout.println(`set6 + set7: ${joined}`)

      // Append a Boolean (true=1, false=0)
      withTrue <- set6 + true
      stdout.println(`set6 + true: ${withTrue}`)

      withFalse <- set6 + false
      stdout.println(`set6 + false: ${withFalse}`)

      // Mutating append
      growing <- 0b11
      growing += true
      growing += false
      stdout.println(`Growing: ${growing}`)

      // === COMPARISON ===

      require a == 0b010011
      require a <> b
      require a < b
      require b > a

      // Spaceship
      ordering <- a <=> b
      stdout.println(`a <=> b: ${ordering}`)

      // === LENGTH ===

      stdout.println(`Length of a: ${length a}`)
      stdout.println(`Length of joined: ${length joined}`)

      // === STREAMING BITS AS BOOLEANS ===

      // Bits are streamable — each element is a Boolean
      // Least significant bit first (right to left)
      set6false <- 0b01001101

      partial <- cat set6false | skip 3 | map with booleanToBits | collect as Bits
      stdout.println(`Skip 3 bits: ${partial}`)

      // === HASHCODE ===

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

      // === COPY ===

      copied <- Bits()
      copied :=: a
      require copied == a
      stdout.println(`Copied: ${copied}`)

C/Java/Python/JS: bit ops on integers, signed/unsigned confusion, fixed-width. EK9: dedicated variable-length Bits type, + concatenates, << grows, >> shrinks, streamable as Booleans.

and beginner binary bits bitwise boolean colour concatenate first flag hardware intro mask migrate not or pixel register shift start stream xor

Explanation: EK9 Bits does not have setBit() or getBit() methods. Use bitwise operators: and, or, xor, ~, >>, <<. Triggers E50060 — method not resolved. See ek9 -h Bits for the full API.


Question: How does the Millisecond type work in EK9 and how does it relate to Duration?

How do I convert between Millisecond and Duration in EK9?

Answer:
Millisecond is EK9's dedicated type for precise timing and performance measurement using the ms suffix: 100ms, 500ms. Type-safe — cannot mix with incompatible types.

Arithmetic: +, -, multiply/divide by Integer or Float, Millisecond/Millisecond gives Float ratio. Conversion: .duration() converts to Duration (#^ also promotes). Construct from Duration: Millisecond(P2W).

Performance timing: SystemClock().millisecond() for start/end measurement.

Use Millisecond for precise timing/timeouts. Use Duration for calendar-scale spans.

Use 'ek9 -h Millisecond' to see the full API.

See Q32 for Duration. See Q31 for Date/Time. See Q44 for locale formatting.

defines module qa.millisecond

  defines program
    MillisecondDemo()
      stdout <- Stdout()

      // === MILLISECOND LITERALS ===

      // Simple ms suffix on integers
      shortTimeout <- 100ms
      halfSecond <- 500ms
      oneSecond <- 1000ms
      stdout.println(`Short: ${shortTimeout}`)
      stdout.println(`Half second: ${halfSecond}`)
      stdout.println(`One second: ${oneSecond}`)

      // Negative milliseconds
      negMs <- -250ms
      stdout.println(`Negative: ${negMs}`)

      // Unset
      unsetMs <- Millisecond()
      require ~unsetMs?
      stdout.println(`Unset isSet: ${unsetMs?}`)

      // === ARITHMETIC ===

      // Add milliseconds together
      combined <- 100ms + 50ms
      stdout.println(`100ms + 50ms = ${combined}`)

      // Multiply by Float or Integer (scaling)
      scaled <- 100ms * 3.5
      stdout.println(`100ms * 3.5 = ${scaled}`)

      scaledInt <- 100ms * 5
      stdout.println(`100ms * 5 = ${scaledInt}`)

      // Divide by Integer or Float
      halved <- 1000ms / 2
      stdout.println(`1000ms / 2 = ${halved}`)

      // Divide Millisecond by Millisecond gives Float ratio
      ratio <- 750ms / 250ms
      stdout.println(`750ms / 250ms = ${ratio}`)

      // Increment and decrement
      ticker <- 100ms
      ticker++
      stdout.println(`After ++: ${ticker}`)
      ticker--
      stdout.println(`After --: ${ticker}`)

      // Negation
      pos <- 500ms
      neg <- -pos
      stdout.println(`Negated: ${neg}`)

      // Absolute value
      absVal <- abs neg
      require absVal == pos
      stdout.println(`abs: ${absVal}`)

      // === CONVERSION TO DURATION ===

      // duration() method converts Millisecond to Duration
      // Values >= 500ms round up to the nearest second
      fiveSeconds <- 5000ms
      asDuration <- fiveSeconds.duration()
      stdout.println(`5000ms as Duration: ${asDuration}`)

      // 501ms rounds up to 1 second
      nearlyASecond <- 501ms
      stdout.println(`501ms as Duration: ${nearlyASecond.duration()}`)

      // Negative milliseconds
      negDuration <- -501ms
      stdout.println(`-501ms as Duration: ${negDuration.duration()}`)

      // #^ promote operator also converts to Duration
      promoted <- #^ 3000ms
      stdout.println(`Promoted to Duration: ${promoted}`)

      // === CONSTRUCT FROM DURATION ===

      // Create Millisecond from a Duration literal
      twoWeeks <- Millisecond(P2W)
      stdout.println(`P2W as Millisecond: ${twoWeeks}`)

      fiveMinutes <- Millisecond(PT5M)
      stdout.println(`PT5M as Millisecond: ${fiveMinutes}`)

      // === ADD DURATION TO MILLISECOND ===

      base <- 5400ms
      withDuration <- base + PT1S
      stdout.println(`5400ms + PT1S = ${withDuration}`)

      // === PERFORMANCE TIMING ===

      starting <- SystemClock().millisecond()
      stdout.println("Do some work")
      ending <- SystemClock().millisecond()
      elapsed <- ending - starting
      stdout.println(`Elapsed: ${elapsed}`)

      // === COMPARISON ===

      require 100ms < 500ms
      require 500ms > 100ms
      require 100ms <> 500ms
      require 100ms == 100ms
      require 100ms <= 100ms
      require 100ms >= 100ms

      shortDelay <- 100ms
      ordering <- shortDelay <=> 500ms
      stdout.println(`100ms <=> 500ms: ${ordering}`)

      // === UNSET PROPAGATION ===

      unsetResult <- unsetMs + 100ms
      require ~unsetResult?
      stdout.println(`Unset + 100ms isSet: ${unsetResult?}`)

      // === HASHCODE ===

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

Java: System.currentTimeMillis() returns raw long. Python: time.time() returns float. Rust/Go: no ms literal. EK9: native ms literal, dedicated type, promotes to Duration via #^.

beginner clock convert delay duration elapsed first interval intro migrate millisecond ms performance promote start stopwatch timeout timing

Explanation: EK9 Millisecond does not have toSeconds() or similar conversion methods. Use .duration() to convert to Duration, or use arithmetic operators. Triggers E50060 — method not resolved. See ek9 -h Millisecond for the full API.


Question: How does the Path type work in EK9 and what is it for?

How do I navigate JSON data structures in EK9?

What is the difference between Path and FileSystemPath in EK9?

How do the $, $$, and $? operators differ in EK9?

Answer:
IMPORTANT: Path in EK9 is NOT a file system path. It is a data navigation path for traversing object graphs like JSON structures. If you need file system paths, use FileSystemPath instead. This is a critical distinction that developers from other languages often miss.

PATH IS FOR DATA NAVIGATION
Path uses the $? prefix to create compile-time checked navigation expressions for object graphs (JSON, nested records, data structures):
  simplePath <- $?.aKey
  nested <- $?.some.path.to.value
  fromArray <- $?[0].a-field
  multiDim <- $?.data[2][1].multi-dimensional.array
  complex <- $?.some.path.inc[0].array

FILESYSTEMPATH IS FOR FILES
FileSystemPath is the separate type for file and directory operations:
  filePath <- FileSystemPath("/home/user/file.txt")
It has methods like parent(), fileName(), extension(), exists(). Path has none of these because it navigates data, not file systems.

THE $? PREFIX
The path literal starts with $? followed by property names and array indices:
  Properties: $?.propertyName (dot-separated)
  Arrays: $?[index] (bracket notation)
  Mixed: $?.store.book[0].title
  Multi-dimensional: $?[2][1].field
  Hyphenated keys: $?[0].a-field

The compiler validates the syntax at compile time. Unlike Java's JsonPath strings or Python's dict key chains, typos and structural errors are caught before your code runs.

THE $ OPERATOR FAMILY
EK9 has three related operators that all use $ as a visual anchor but serve completely different purposes:

  $value      to-string conversion (any type to String)
  $$record    to-JSON serialization (record/class to JSON)
  $?.path     data path literal (compile-time checked navigation)

One $ is simple conversion, two $$ is structured output, $? is structured input navigation. This visual pattern makes the intent immediately recognizable in code.

ESCAPING $ IN INTERPOLATED STRINGS
Since $ triggers interpolation in backtick strings, use \$ to include a literal dollar sign:
  stdout.println(`\$ to-string: ${stringRep}`)
  stdout.println(`\$\$ to-JSON: ${jsonOfMe}`)
  stdout.println(`\$? path: ${navPath}`)
In normal double-quoted strings, $ does not need escaping. In backtick interpolation, $ and backtick must be escaped with backslash.

PATH CONCATENATION
The + operator joins paths:
  basePath <- $?.api
  extended <- basePath + $?.users
You can also append strings:
  withVersion <- basePath + "/v2"

PATH OPERATORS
  contains: check if path contains a substring
    fullPath contains "store"     true/false
  matches: match against a regular expression
    path matches /user.*/         true/false
  length: character count of the path
  ==, <>, <, >, <=, >=, <=>: comparison operators

WHY A BUILT-IN PATH TYPE?
Modern applications constantly navigate data structures: JSON API responses, configuration trees, nested records. In Java, you use string-based JsonPath ("$.store.book[0].title") with no compile-time checking. In JavaScript, optional chaining (obj?.store?.book?.[0]?.title) is concise but still unchecked. In Python, nested dict access (data['store']['book'][0]) throws KeyError at runtime.

EK9 makes data navigation a first-class type. Paths can be stored in variables, passed as parameters, composed programmatically, and validated at compile time. This is the same philosophy as RegEx being built-in rather than a library: if something is used everywhere, it should be part of the language with compile-time safety.

WHY SEPARATE FROM FILESYSTEMPATH?
Because they solve completely different problems:
  Path: navigates IN-MEMORY data structures (JSON, object graphs)
  FileSystemPath: navigates ON-DISK file system hierarchies

Mixing these concepts (as most languages do with a single 'path' type or string) leads to confusion. A JSON path $?.users[0].name has nothing to do with /home/users/name.txt. Different types, different operations, different safety requirements.

See Q43 (How do I escape characters in EK9 string interpolation?) for the complete rules on escaping $ in backtick strings. See Q37 (How do I work with strings in EK9?) for string interpolation basics.

Use 'ek9 -h Path' and 'ek9 -h FileSystemPath' to see the full APIs.

defines module qa.path

  defines record
    CustomerDetail
      firstName <- String()
      lastName <- String()

      default CustomerDetail()

      CustomerDetail()
        ->
          firstName as String
          lastName as String
        this.firstName :=: firstName
        this.lastName :=: lastName

      default operator $$

      default operator ?

  defines program
    PathDemo()
      stdout <- Stdout()

      // === PATH LITERALS ===

      // $? prefix creates a compile-time checked path
      simplePath <- $?.aKey
      stdout.println(`Simple: ${simplePath}`)

      // Nested property access
      nested <- $?.some.path.to.value
      stdout.println(`Nested: ${nested}`)

      // Array indexing
      firstElement <- $?[0]
      stdout.println(`First element: ${firstElement}`)

      // Property from array element
      fromArray <- $?[0].a-field
      stdout.println(`From array: ${fromArray}`)

      // Multi-dimensional array access
      multiDim <- $?.data[2][1].multi-dimensional.array
      stdout.println(`Multi-dim: ${multiDim}`)

      // Complex path
      complex <- $?.some.path.inc[0].array
      stdout.println(`Complex: ${complex}`)

      // === DISTINGUISH FROM $ AND $$ ===

      // $value is the to-string operator
      anInt <- 42
      stringRep <- $anInt
      stdout.println(`\$ to-string: ${stringRep}`)

      // $$record is the to-JSON operator
      me <- CustomerDetail(firstName: "Steve", lastName: "Limb")
      jsonOfMe <- $$me
      stdout.println(`\$\$ to-JSON: ${jsonOfMe}`)

      // $?.path is the path navigation literal
      navPath <- $?.store.book[0].title
      stdout.println(`\$? path: ${navPath}`)

      // === PATH CONCATENATION ===

      basePath <- $?.api
      extended <- basePath + $?.users
      stdout.println(`Concatenated: ${extended}`)

      // Append string
      withString <- basePath + "/v2"
      stdout.println(`With string: ${withString}`)

      // === PATH OPERATORS ===

      // Length
      stdout.println(`Length: ${length nested}`)

      // Comparison
      path1 <- $?.alpha
      path2 <- $?.beta
      require path1 <> path2
      require path1 < path2
      stdout.println(`alpha <=> beta: ${path1 <=> path2}`)

      // Contains
      fullPath <- $?.store.book[0].title
      containsResult <- fullPath contains "store"
      stdout.println(`Contains 'store': ${containsResult}`)

      // Matches with RegEx
      matchPath <- $?.users
      matchResult <- matchPath matches /user.*/
      stdout.println(`Matches /user.*/: ${matchResult}`)

      // === HASHCODE ===

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

      // === UNSET PATH ===

      unsetPath <- Path()
      require ~unsetPath?
      stdout.println(`Unset isSet: ${unsetPath?}`)

      // === COPY ===

      copied <- Path()
      copied :=: nested
      require copied == nested
      stdout.println(`Copied: ${copied}`)

Java: JsonPath library with string expressions $.store.book[0].title, no compile-time checking, runtime exceptions on invalid paths, java.nio.file.Path for file system. JavaScript: native optional chaining obj?.store?.book?.[0], jq for CLI JSON queries, no compile-time validation, fs.path for files. Python: nested dict access data['store']['book'][0] with KeyError, jsonpath-ng library, pathlib.Path for files. Rust: serde_json Value indexing value["store"]["book"][0], jsonpath-rust crate, std::path::Path for files. Go: encoding/json with map[string]interface{} casting, gjson library for JSON paths, filepath package for files. C#: System.Text.Json JsonElement navigation, JsonPath.Net library, System.IO.Path for files. EK9: native $?.path literal with compile-time validation, Path type for data navigation, FileSystemPath type for files, clear separation of concerns.

array beginner compile data dollar escape file filesystempath first graph interpolation intro json literal migrate navigation object path property start

firstName as String
      lastName as String

Explanation: Record fields must be initialised at declaration. Declaring fields without initialisation triggers E08180. Use inline initialisation like 'String()' to create an unset-but-initialised field. See ek9 -h E08180 for details.


Question: How do I escape characters in EK9 string interpolation?

How do I include a literal dollar sign in a backtick string in EK9?

What is the difference between escaping in double-quoted and backtick strings in EK9?

How do I display the $ and $$ operators as text in interpolated strings in EK9?

Why does my backtick string fail when I use $ without interpolation in EK9?

Answer:
EK9 has two string types with different escaping rules. Double-quoted strings are plain text where $ is just a regular character. Backtick strings support interpolation where ${...} evaluates expressions. This means backtick strings need special escaping for $ and backtick characters that double-quoted strings do not.

BACKTICK STRING ESCAPING (interpolation strings)
Backtick strings evaluate ${...} expressions. To include literal special characters:
  \$    Literal dollar sign (prevents interpolation trigger)
  \`    Literal backtick character
  \t    Tab

    Newline
  \\    Literal backslash
  \uXXXX  Unicode code point

Examples:
  salary <- 42000
  stdout.println(`Your salary is \$${salary}`)
  //Output: Your salary is $42000

  stdout.println(`Use \`backticks\` for interpolation`)
  //Output: Use `backticks` for interpolation

DOUBLE-QUOTED STRING ESCAPING (plain text)
Double-quoted strings have NO interpolation, so $ is just a regular character:
  plainDollar <- "Price: $42"
  //Output: Price: $42

  withQuote <- "She said \"hello\""
  //Output: She said "hello"

  withBackslash <- "C:\\Users\\file.txt"
  //Output: C:\Users\file.txt

WHY THIS MATTERS FOR EK9 OPERATORS
EK9 uses $ in three operator contexts:
  $value     to-string conversion
  $$record   to-JSON serialization
  $?.path    data navigation path literal

When you want to MENTION these operators as text in an interpolated string, you must escape the dollar sign:
  name <- "EK9"
  stringRep <- $name
  stdout.println(`\$ converts to string: ${stringRep}`)

  me <- PersonDetail(name: "Steve", age: 30)
  jsonRep <- $$me
  stdout.println(`\$\$ converts to JSON: ${jsonRep}`)

  navPath <- $?.store.book[0].title
  stdout.println(`\$? creates a path: ${navPath}`)

Without the backslash, the lexer would try to parse $$ or $? as operators inside the string, causing parse errors.

USING OPERATORS DIRECTLY INSIDE INTERPOLATION
You can use $ and $$ operators directly inside ${...} without an intermediate variable:
  stdout.println(`Direct JSON: ${$$me}`)
  stdout.println(`Direct to-string: ${$salary}`)
The ${$$me} pattern evaluates $$me (to-JSON) and interpolates the result. The ${$salary} pattern evaluates $salary (to-string) and interpolates. This is concise and avoids creating temporary variables just for display.

COMBINING ESCAPED AND INTERPOLATED CONTENT
You can mix escaped dollar signs and interpolation in the same string:
  price <- 9.99
  stdout.println(`Item costs \$${price} (tax not included)`)
  //Output: Item costs $9.99 (tax not included)

The \$ produces a literal dollar sign, then ${price} is the interpolation expression. The lexer processes them left to right: backslash-dollar is a literal, dollar-brace is interpolation.

BACKSLASH-SPACE SHORTCUT
If a backslash is followed by a space, it is treated as just a backslash. This is a convenient alternative to \\ when the backslash is not immediately before a special character:
  withBs <- "path\ here"
  //Output: path\ here

QUICK REFERENCE TABLE
  Escape    Double-quoted    Backtick
  \$        not needed       REQUIRED (prevents interpolation)
  \`        not needed       REQUIRED (prevents string end)
  \"        REQUIRED         not needed
  \t        tab              tab

        newline           newline
  \\        backslash        backslash
  \uXXXX   unicode          unicode

The key insight: double-quoted strings need \" for quotes, backtick strings need \$ for dollars and \` for backticks. Each string type only needs escaping for its own delimiter and trigger characters.

See Q37 (How do I work with strings in EK9?) for string methods, streaming, and interpolation basics. See Q42 (How does the Path type work?) for the $? operator that often needs escaping in interpolated output. See Q113 for the text construct for internationalization.

Use 'ek9 -h String' to see the full String API. See Q188 for JSON as first-class type and the $$ operator. See Q242 for conversion operators ($, $$, #?) used in interpolation.

defines module qa.escape.interpolation

  defines record
    PersonDetail
      name <- String()
      age <- Integer()

      default PersonDetail()

      PersonDetail()
        ->
          name as String
          age as Integer
        this.name :=: name
        this.age :=: age

      default operator $$

      default operator ?

  defines program
    EscapingDemo()
      stdout <- Stdout()

      // === BACKTICK-SPECIFIC ESCAPING ===

      // \$ produces a literal dollar sign in backtick strings
      salary <- 42000
      stdout.println(`Your salary is \$${salary}`)

      // Show the $ operator label without triggering interpolation
      name <- "EK9"
      stringRep <- $name
      stdout.println(`\$ converts to string: ${stringRep}`)

      // Show the $$ operator label
      me <- PersonDetail(name: "Steve", age: 30)
      jsonRep <- $$me
      stdout.println(`\$\$ converts to JSON: ${jsonRep}`)

      // Show the $? path literal label
      navPath <- $?.store.book[0].title
      stdout.println(`\$? creates a path: ${navPath}`)

      // \` produces a literal backtick inside a backtick string
      stdout.println(`Use \`backticks\` for interpolation`)

      // === DOUBLE-QUOTED STRING ESCAPING ===

      // In double-quoted strings, $ is just a regular character
      plainDollar <- "Price: $42"
      stdout.println(plainDollar)

      // \" for literal quote in double-quoted strings
      withQuote <- "She said \"hello\""
      stdout.println(withQuote)

      // \\ for literal backslash
      withBackslash <- "C:\\Users\\file.txt"
      stdout.println(withBackslash)

      // === COMMON ESCAPE SEQUENCES (both string types) ===

      // \t tab, \n newline
      withTab <- `Column1\tColumn2`
      stdout.println(withTab)

      withNewline <- "Line1\nLine2"
      stdout.println(withNewline)

      // \uXXXX for Unicode code points
      copyright <- "\u00A9 2024"
      stdout.println(copyright)

      // Backslash followed by space is just a backslash
      withBs <- "path\ here"
      stdout.println(withBs)

      // === OPERATORS DIRECTLY IN INTERPOLATION ===

      // Use $$ directly inside ${...} — no intermediate variable needed
      stdout.println(`Direct JSON: ${$$me}`)

      // Use $ directly inside ${...}
      stdout.println(`Direct to-string: ${$salary}`)

      // === COMBINING ESCAPING WITH INTERPOLATION ===

      // Mix escaped dollar signs and interpolation in the same string
      price <- 9.99
      stdout.println(`Item costs \$${price} (tax not included)`)

      // Multiple interpolations with escaped operators
      x <- 42
      stdout.println(`Integer ${x}, use \$ for to-string, \$\$ for to-JSON`)

Java: no string interpolation until Java 21 string templates (preview), $ is a regular character in all strings, escape sequences same as EK9 double-quoted strings. Python: f-strings use {expr} for interpolation, literal brace needs {{ doubling not backslash escaping, $ is a regular character. JavaScript: template literals use ${expr} in backticks, literal backtick needs backslash escaping, literal dollar-brace needs \${ or ${'$'}, very similar rules to EK9 backtick strings. Kotlin: string templates use $var and ${expr}, literal dollar needs ${'$'} trick (no backslash escape), a common pain point. Ruby: string interpolation uses #{expr} in double-quoted strings, $ is a regular character but #{ needs escaping. C#: interpolated strings use $"...{expr}..." and literal brace needs {{ doubling. EK9: backtick strings use ${expr}, \$ for literal dollar, \` for literal backtick, double-quoted strings are plain text where $ needs no escaping. The backslash-dollar approach is more intuitive than Python's brace doubling or Kotlin's ${'$'} workaround.

backslash backtick beginner dollar escape first interpolation intro literal operator quote start string unicode

string interpolation
escape character
template string


Question: How does the Locale type work in EK9 and how do I format values for different regions?

How do I format numbers, dates, and currency for different locales in EK9?

What is the difference between shortFormat, mediumFormat, longFormat, and fullFormat in EK9?

How do I display money amounts with locale-specific formatting in EK9?

How does EK9 handle internationalization and localization?

Answer:
Locale is EK9's unified formatting type for presenting values according to regional conventions. One type handles formatting for Integer, Float, Date, Time, DateTime, Money, Boolean, and Dimension.

CONSTRUCTION
  enGB <- Locale("en_GB")        single string
  skSK <- Locale("sk", "SK")    language, country

FORMATTING
Unified API: locale.format(value) for all types. Four named levels for Date/Time/Money: shortFormat(), mediumFormat(), longFormat(), fullFormat(). Day of week: enGB.dayOfWeek(date).

Use 'ek9 -h Locale' to see the full API.

See Q31 for Date/Time/DateTime. See Q35 for Money. See Q36 for Dimension. See Q37 for string interpolation. See Q39 for Integer/Float formatting.

defines module qa.locale

  defines program
    LocaleDemo()
      stdout <- Stdout()

      // === LOCALE CONSTRUCTION ===

      // Single string with underscore separator
      enGB <- Locale("en_GB")

      // Dash separator also works
      enUS <- Locale("en-US")

      // Two-argument form: language, country
      deutsch <- Locale("de_DE")
      skSK <- Locale("sk", "SK")

      // Access language and country parts
      stdout.println(`Language: ${enGB.language()}, Country: ${enGB.country()}`)

      // === INTEGER FORMATTING ===

      bigNumber <- 675807
      negative <- -92208

      // Thousand separators differ by locale
      stdout.println(`GB: ${enGB.format(bigNumber)}`)
      stdout.println(`DE: ${deutsch.format(bigNumber)}`)
      stdout.println(`SK: ${skSK.format(negative)}`)

      // === FLOAT FORMATTING ===

      pi <- 3.14159265358979
      bigFloat <- -1.797693134862395E12

      // Decimal separators differ by locale
      stdout.println(`GB pi: ${enGB.format(pi)}`)
      stdout.println(`DE pi: ${deutsch.format(pi)}`)

      // Control decimal places with second argument
      stdout.println(`GB pi (2dp): ${enGB.format(pi, 2)}`)
      stdout.println(`DE big (1dp): ${deutsch.format(bigFloat, 1)}`)

      // === DATE FORMATTING — four named levels ===

      wedding <- 2020-10-03

      stdout.println(`GB short: ${enGB.shortFormat(wedding)}`)
      stdout.println(`US medium: ${enUS.mediumFormat(wedding)}`)
      stdout.println(`SK long: ${skSK.longFormat(wedding)}`)
      stdout.println(`DE full: ${deutsch.fullFormat(wedding)}`)

      // === TIME FORMATTING ===

      lunchTime <- 12:00:01

      stdout.println(`GB short: ${enGB.shortFormat(lunchTime)}`)
      stdout.println(`DE medium: ${deutsch.mediumFormat(lunchTime)}`)

      // === DATETIME FORMATTING ===

      event <- 2020-10-03T12:00:00Z

      stdout.println(`US short: ${enUS.shortFormat(event)}`)
      stdout.println(`GB medium: ${enGB.mediumFormat(event)}`)
      stdout.println(`DE long: ${deutsch.longFormat(event)}`)
      stdout.println(`SK full: ${skSK.fullFormat(event)}`)

      // === MONEY FORMATTING ===

      tenPounds <- 10#GBP
      thirtyDollars <- 30.89#USD

      // Default format — symbol + full amount
      stdout.println(`GB: ${enGB.format(tenPounds)}`)
      stdout.println(`DE: ${deutsch.format(tenPounds)}`)
      stdout.println(`SK: ${skSK.format(thirtyDollars)}`)

      // Named levels for money
      stdout.println(`Medium (no fraction): ${enGB.mediumFormat(tenPounds)}`)
      stdout.println(`Short (no symbol/fraction): ${enGB.shortFormat(thirtyDollars)}`)
      stdout.println(`Long (no symbol): ${deutsch.longFormat(tenPounds)}`)

      // Custom control: format(money, withSymbol, withFraction)
      stdout.println(`Symbol, no fraction: ${enGB.format(arg0: thirtyDollars, showSymbol: true, showFractionalPart: false)}`)
      stdout.println(`No symbol, with fraction: ${enGB.format(arg0: thirtyDollars, showSymbol: false, showFractionalPart: true)}`)

      // === BOOLEAN FORMATTING ===

      stdout.println(`True in GB: ${enGB.format(true)}`)
      stdout.println(`True in DE: ${deutsch.format(true)}`)

      // === DIMENSION FORMATTING ===

      dist <- 42.5km
      stdout.println(`Dimension GB: ${enGB.format(dist)}`)
      stdout.println(`Dimension GB (1dp): ${enGB.format(dist, 1)}`)

      // === DAY OF WEEK ===

      stdout.println(`Day: ${enGB.dayOfWeek(wedding)}`)
      stdout.println(`Tag: ${deutsch.dayOfWeek(wedding)}`)

      // === LOCALE OPERATORS ===

      // Comparison
      require enGB <> deutsch
      require enGB < enUS
      stdout.println(`GB <=> US: ${enGB <=> enUS}`)

      // Hashcode
      stdout.println(`Hash: ${#? enGB}`)

      // To-string and to-JSON
      stdout.println(`String: ${enGB}`)
      stdout.println(`JSON: ${$enGB}`)

      // Copy
      copied <- Locale()
      copied :=: enGB
      require copied == enGB

      // Unset
      unsetLocale <- Locale()
      require ~unsetLocale?
      stdout.println(`Unset isSet: ${unsetLocale?}`)

      // Matches
      matchResult <- enGB matches /en.*/
      stdout.println(`Matches /en.*/: ${matchResult}`)

      // === USING LOCALE WITH INTERPOLATION ===

      // Locale formatting combines naturally with string interpolation
      stdout.println(`The wedding was on ${enGB.longFormat(wedding)} at ${enGB.shortFormat(lunchTime)}`)
      stdout.println(`Cost: ${enGB.format(tenPounds)} (${deutsch.format(tenPounds)} in German format)`)

Java: separate NumberFormat/DateTimeFormatter/Currency classes. Python: locale module with global state. Rust/Go: no built-in locale. EK9: unified locale.format(value) for all types, named format levels.

beginner country currency date first format fullFormat internationalization intro language locale localization longFormat mediumFormat migrate money number region shortFormat start time

Explanation: EK9 Locale uses short method names: language(), country() not getLanguage(), getCountry(). Triggers E50060 — method not resolved. See ek9 -h Locale for the full API.