Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

A tour of nevla

Each example links to the playground, where the URL carries the program itself; edit and rerun as you go.

Hello

fn main() {
    print("hello, nevla")
}

▸ run it in the playground

fn main() is the entry point. print renders anything. Four-space indents and nevla fmt settles every other style question.

Errors are values

fn half(n int) (int, error?) {
    if n % 2 != 0 {
        return 0, error.new("odd number")
    }
    return n / 2, none
}

fn main() {
    v, err := half(42)
    if err != none {
        print("error: " + err.msg)
    } else {
        print(v)
    }
}

▸ run it in the playground

A fallible function ends its result list with error?. Dropping an error is a compile error; you either bind it (v, err :=) and decide, or propagate it with check, which requires your own function to return error?:

fn quarter(n int) (int, error?) {
    h := check half(n)      // on error: return it, zero values elsewhere
    return check half(h), none
}

A multi-value never travels as a unit, so Go’s return half(n) is a compile error here. Propagate early with return check half(n), none, or bind and decide: v, err := half(n); return v, err. The error slot stays visible at every hop.

Handle errors at the layer that can do something about them; propagate only when the caller owns the decision.

Options, no nil

fn main() {
    m := map[str]int{"a": 1}
    v := m["a"]             // a map read is int?: present or none
    if v != none {
        print(v + 1)        // narrowed to int inside the branch
    }
}

▸ run it in the playground

There is no nil. Absence is an option type (int?), and the checker makes you look before you touch: using v unnarrowed is a compile error.

The copy model

struct User {
    Name str
    Age int
}

fn main() {
    u := User{Name: "nevla", Age: 1}
    v := u
    v.Age = 99
    print(u.Age)    // 1: structs copy

    xs := [1, 2, 3]
    ys := xs
    ys[0] = 99
    print(xs[0])    // 99: lists are references
}

▸ run it in the playground

Go’s split: scalars, strings, and structs copy on assignment; lists, maps, functions, and py values are references. Closures capture by reference.

Modules and visibility

import "util.nv" binds a sibling file as module util. Capitalized top-level names are exported; lowercase is private to its file, fields included. The Go rule, no keywords.

The py bridge

import py "torch"

fn main() (error?) {
    w := check torch.randn([784, 10], requires_grad: true)
    x := check torch.randn([32, 784])
    logits := check (x @ w)
    print(check str(logits.shape))
    return none
}

▸ run it in the playground

A chain of Python operations is one fallible unit: any exception anywhere in model(x).loss.item() becomes one nevla error at the point of consumption. Keyword arguments pass through, @ is matrix multiplication, and for range iterates any Python iterable. Inside a project, every import py must be declared (nevla py add torch), so a missing dependency is a compile error rather than a crash twenty minutes into a run.

with: Python context managers

import py "torch"

fn main() (error?) {
    x := check torch.randn([4, 4])
    with torch.no_grad() {
        y := check (x * 2)
        print(check str(y.shape))
    }
    return none
}

▸ run it in the playground

with expr { } runs the block under a Python context manager: __enter__ before, __exit__ on every exit from the block. A return that carries an error reaches __exit__ as an exception, so a manager that branches on exception state (a transaction’s commit/rollback) sees the error path exactly as Python would. The statement itself has no error slot — fallible acquisition belongs before it (db := check connect(...), then with db.transaction() { }), and an exception raised by the manager itself is a fault. One manager per statement; nest for more.