Grasm v1.0.0 Get started

Learn

Effects and capabilities

The row after a function's return type says what it is allowed to do. It is inferred from the body, so it cannot lie, and it survives across a package boundary — which is what makes a dependency's reach something you can check rather than something you have to trust.

The row

fn pure_sum(xs: [i64]) -> i64 {              // no row: it does nothing but compute
    xs.sum()
}

fn report(xs: [i64]) / {io} {                // it prints, so it says io
    print("{xs.sum()}")
}

fn fetch(url: str) -> str ! IoError / {net} {   // fails, and reaches the network
    tcp_get(url)
}

You do not have to write the row — it is inferred. Writing it is a claim the compiler checks: declare / {} on something that prints and the program does not build.

Why inference matters

A hand-written effect annotation is a comment that happens to be checked at one point. An inferred one is a fact about the body. When somebody adds a print three calls down, the row widens on its own and every caller that promised not to print stops compiling — including callers in other packages.

Your own effects

An effect is a set of operations. A handler supplies them for a block, and the operation resolves where it is performed, not where it is written — so a function five calls deep uses whichever handler is installed.

effect Cfg {
    fn value() -> i64
}

fn deep() -> i64 / {Cfg} {
    Cfg.value()
}

fn main() {
    with Cfg handled_by CfgHandler{ value: || 42 } {
        print("{deep()}")             // 42
    }
}

A handler is a value. It can be bound, passed to a function, stored in a list — the function installing it need never have seen the literal. That is how a test substitutes a clock, a random source, or a network for the real one without the code under test knowing.

Capability ceilings

A package declares, in its manifest, the effects it may perform:

[package]
name = "ecommerce_cart"
version = "1.0.0"
entry = "main.gr"
capabilities = ["io", "net"]

Because effects are inferred from source, a dependency that quietly starts touching the filesystem exceeds its declared ceiling and fails to build in every consumer. Not a runtime surprise. Not something a reviewer has to notice in a diff. A compile error, at the boundary, before the code runs.

This is the reason Grasm has a package system rather than a directory of files.