A tour of Nymph
This tour builds a small immutable program. Every executable sample is tagged nym and checked by the compiler; the reference supplies the complete rules.
Functions, blocks, and immutable bindings
A function body is one expression. A block evaluates steps in order and returns its final value; let introduces an immutable binding.
func scaled_average(a: int, b: int, scale: int): float = {
let sum = a + b
let scaled = sum * scale
scaled / 2
}Structs return replacements
Struct fields do not change in place. A function can preserve the old value and return a new one.
struct Task(title: string, done: boolean)
func complete(task: Task): Task = Task(title = task.title, done = true)
func before_and_after(): #(boolean, boolean) = {
let before = Task(title = "Dishes", done = false)
let after = complete(before)
#(before.done, after.done)
}Enums, static views, and matching
Enums may embed variant sets. Widening changes the nominal static view, not the underlying variant. Embedding is not an implicit Into implementation.
enum Priority { Low, Medium, High }
enum Scheduling { ...Priority, Deferred }
func schedule(priority: Priority): Scheduling = priority
func weight(priority: Priority): int = match (priority) {
Priority.Low -> 1,
Priority.Medium -> 2,
Priority.High -> 3,
}See Structs and enums for fixed-point sets, selected variants, pattern refinement, and static method dispatch.
Absence and failure are values
Nymph has no null and no exceptions. Option<T> represents presence or absence; Result<T, E> represents expected failure.
enum Priority { Low, Medium, High }
func priority(code: int): Result<Priority, string> = match (code) {
1 -> Ok(value = Priority.Low),
2 -> Ok(value = Priority.Medium),
3 -> Ok(value = Priority.High),
_ -> Error(error = "unknown priority: ${code}"),
}Persistent iteration
An iterator's next() returns nominal Iteration<Item, self> successor state. for is a dedicated compiler operation over that protocol; it is not a mutable iterator desugaring.
struct Counter(next: int, end: int)
impl Iterator<int> for Counter {
func next(): Iteration<int, self> = if (this.next > this.end) {
Done
} else {
Yield(item = this.next, next = Counter(next = this.next + 1, end = this.end))
}
}
func find_three(): Option<int> = for@values (value in Counter(next = 1, end = 4)) {
if (value == 3) { break@values value }
}Immutable state loops
There is no source while. A state loop gives every iteration fresh immutable bindings and replaces named values simultaneously on continue.
func sum_to(limit: int): int = loop@sum (
let next = 1
let total = 0
) {
if (next > limit) { break@sum total }
continue(next = next + 1, total = total + next)
}See Iteration and Immutability.
Observation and program output
echo value returns the identical value and adds no effect. In development it renders complete structure to stderr regardless of field visibility; release emission erases the observer but keeps operand evaluation. Intentional output uses effectful I/O.
struct Task(public title: string, private note: string)
func inspect(task: Task): Task = echo taskThe Node launcher never prints a successful root value. main may return void, Option<void>, or Result<void, E> (or the corresponding Task), with exact error and exit handling documented in Projects and the Node launcher.
import std/io
func main(): void = io.println("Hello from immutable Nymph")Next steps
- Expressions covers closures, pipes, operators, and control flow.
- Error handling covers
Option,Result, and?. - Projects covers manifests, semantic roots, profiles, and execution.