Scalars & Strings
Nomi has three numeric scalar types — Int,
Float, Decimal — plus
Bool, String,
Byte, and Bytes.
Numbers, booleans, operators
Section titled “Numbers, booleans, operators”Arithmetic, comparisons, and the logical operators and / or / ! all read
as you’d expect:
fn main(): Bool {
dbg 42 + 8
dbg 10 - 3
dbg 2 * 21
dbg 20 / 4
dbg -(3 + 4)
dbg 42 > 10
dbg True and !False
}
Number literal forms
Section titled “Number literal forms”Numbers can use digit separators, alternate radices (hex / binary / octal), or scientific notation — different spellings of the same values:
fn main(): Float {
dbg 1_000_000
dbg 0xFF
dbg 0b1010
dbg 1.0e10
}
Decimal — exact arithmetic
Section titled “Decimal — exact arithmetic”Float is fast but approximate: small rounding errors accumulate, and
familiar identities like the one below quietly fail. Decimal is the exact
alternative — use it whenever rounding error is a bug (money, billing,
anywhere correctness matters more than speed). Decimal literals carry a d
suffix:
fn main(): Decimal {
// Float carries base-2 imprecision
dbg 0.1 + 0.2 == 0.3
// Decimal is exact
dbg 0.1d + 0.2d == 0.3d
// Sum four prices to the cent — exactly
dbg 19.99d + 5.00d + 2.50d + 0.99d
}
Strings — interpolation and concatenation
Section titled “Strings — interpolation and concatenation”Strings interpolate ${expr} and concatenate with +:
import std/io
fn main() {
name = "Nomi"
io.print("Hello, ${name}!")
io.print("1 + 2 = ${1 + 2}")
io.print("Hello" + ", " + "Nomi")
}
io.print accepts any value that can be displayed, so
you rarely need to stringify before printing — interpolation calls each value’s
Display.to_string for you.
+, -, *, and / are backed by standard operator interfaces:
Add, Subtract,
Multiply, and Divide. The
built-in scalar impls cover numeric arithmetic; + also covers string
concatenation. Custom types can implement the same interfaces when the operator
is the clearest domain operation.
Text model
Section titled “Text model”Nomi’s text model has three pieces:
Stringis text. Nomi has no separateChartype, so even one-character text is aString.- A grapheme is one user-visible character.
éand many emoji count as one grapheme even when they are built from multiple Unicode values. Nomi represents graphemes asStringvalues. Codepointis an integer-backed Unicode scalar value: a valid number in Unicode’s scalar-value range. Use it when you need to inspect the lower-level values that make up text.Bytesis immutable binary data, andByteis one byte in that buffer. Use it for UTF-8 boundaries, file/network payloads, and Go[]byteFFI.
Most string operations use the grapheme view: String.length,
String.graphemes,
String.slice, and
String.reverse work in user-visible
characters rather than raw bytes.
Use String.to_codepoints when you need the
lower-level scalar values:
import {
std/codepoints.Codepoint
}
fn main(): List<Int> {
dbg String.length("café")
dbg String.graphemes("café")
"café"
|> String.to_codepoints()
|> Iter.map(Codepoint.to_int)
|> Iter.to_list()
|> dbg
}
String equality is byte-based, so visually identical text can compare unequal
when it uses different Unicode forms. Normalize with
String.normalize before comparing text
from mixed sources.
Bytes — binary data
Section titled “Bytes — binary data”Use String.to_bytes to encode text as
UTF-8 bytes. Bytes is iterable, so the generic Iter.* functions work on it,
and Byte.to_int exposes each Byte as an
integer when you need to inspect it. Bytes + Bytes concatenates buffers; the
named form is Bytes.concat.
import {
std/io
}
fn main() {
raw = String.to_bytes("café")
dbg Bytes.length(raw)
dbg raw |> Iter.map(Byte.to_int) |> Iter.to_list()
prefix = String.to_bytes("go:")
case Bytes.to_string(prefix + raw) {
Ok(text) -> io.print(text)
Err(reason) -> io.print(reason)
}
}
Byte itself is not numeric. Convert through
Byte.from_int and Byte.to_int when a
boundary really needs integer values.
Triple-quoted strings
Section titled “Triple-quoted strings”A """…""" literal spans multiple lines. Interpolation works the same way,
and leading indentation common to every line is stripped:
import std/io
fn main() {
name = "Alice"
query = """
SELECT *
FROM users
WHERE name = '${name}'
"""
io.print(query)
}
Raw strings
Section titled “Raw strings”A backtick string is raw: escapes, ${...} interpolation, and $$ dollar
doubling are not processed. Use it for embedded text where those characters
should pass through literally:
import std/io
fn main() {
pattern = `\d{3,4}`
template = `price: ${PRICE}`
query = `
SELECT *
FROM invoices
WHERE total > ${MIN_TOTAL}
`
io.print(pattern)
io.print(template)
io.print(query)
}
Multi-line raw strings use the same indentation rules as triple-quoted strings.
For compiled regular expressions, use the Regex typed literal form shown in
Typed Literals.
The next chapter — Collections — uses these scalar values inside lists, maps, and sets.