How do I work with characters in EK9?
← Getting Started · Ref: Q38
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.
Example
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}`)
Common mistakes
E50060 — EK9 Character uses upperCase() and lowerCase(), not toUpper() or toUpperCase(). Triggers E50060 — method not resolved. See ek9 -h Character for the full API.
Incorrect:
upper <- letter.toUpper()
Correct:
upper <- letter.upperCase()
Other ways to ask this
- 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?
Coming from another language?
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.
Keywords: character, lowercase, quote, migrate, char, increment, uppercase, unicode, promote, intro, string, decrement, literal, first, beginner, single, convert, start