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.