# effects



effects [#effects]

effects are the backbone of ferrule. every form of non-local control flow (error propagation, async suspension, resource cleanup, generators) is an effect. this unifies control flow under a single model.

no effects clause means pure. you can tell from the signature what a function does.

syntax [#syntax]

types go in `<>`, effects go in `[]`:

```ferrule
function process<T>(input: T, cap io: Io) -> T effects [alloc, io, fail<ProcessError>] {
    // ...
}
```

a function with no effects clause is pure:

```ferrule
function add(x: i32, y: i32) -> i32 {
    return x + y;
}
```

effect taxonomy [#effect-taxonomy]

control effects (built-in) [#control-effects-built-in]

| effect    | meaning                               |
| --------- | ------------------------------------- |
| `fail<E>` | function may fail with error type E   |
| `suspend` | function may pause and resume (async) |
| `diverge` | function may not return (never)       |

resource effects (need capabilities) [#resource-effects-need-capabilities]

| effect | capability | what it does           |
| ------ | ---------- | ---------------------- |
| `fs`   | `Fs`       | file system operations |
| `net`  | `Net`      | network operations     |
| `io`   | `Io`       | stdin/stdout/stderr    |
| `time` | `Clock`    | time access, sleep     |
| `rng`  | `Rng`      | randomness             |

computation effects (no capabilities needed) [#computation-effects-no-capabilities-needed]

| effect    | what it does                |
| --------- | --------------------------- |
| `alloc`   | memory allocation           |
| `atomics` | atomic memory operations    |
| `cpu`     | privileged cpu instructions |
| `simd`    | simd intrinsics             |
| `ffi`     | foreign function calls      |

fail<E> replaces error E [#faile-replaces-error-e]

errors are an effect now, composable with everything else.

old:

```ferrule
function readFile(path: String, cap fs: Fs) -> Bytes error IoError effects [fs] {
    // ...
}
```

new:

```ferrule
function readFile(path: String, cap fs: Fs) -> Bytes effects [fs, fail<IoError>] {
    // ...
}
```

`check`, `ok`, `err`, `ensure` all still work. the difference is conceptual: failure is just another effect in the unified model.

subset rule [#subset-rule]

for any call `g()` inside `f`, the effects of g must be a subset of f's effects:

```ferrule
function caller() effects [fs] {
    netCall();  // error: requires [net], not in [fs]
}
```

this is the core enforcement. you can't sneak effects past the caller.

effects vs capabilities [#effects-vs-capabilities]

they're related but different.

**effects** are markers. they say "this function might do io" or "this function might allocate". they're compile-time information.

**capabilities** are values. they're the authority to actually do the io. you pass them around.

the relationship:

| effect    | capability | explanation                      |
| --------- | ---------- | -------------------------------- |
| `fs`      | `Fs`       | need Fs cap to do fs effect      |
| `net`     | `Net`      | need Net cap to do net effect    |
| `io`      | `Io`       | need Io cap to do io effect      |
| `time`    | `Clock`    | need Clock cap to do time effect |
| `rng`     | `Rng`      | need Rng cap to do rng effect    |
| `alloc`   | none       | just marks allocation            |
| `atomics` | none       | just marks atomic ops            |

if a function has `effects [fs]`, it must have a `cap fs: Fs` somewhere in the call chain. this is checked by the capability flow lint.

purity [#purity]

a pure function has no effects clause at all. functions with `fail<E>` are not pure. error propagation is a form of control flow.

```ferrule
function add(x: i32, y: i32) -> i32 {
    return x + y;  // pure
}
```

effect inference [#effect-inference]

within a module, the compiler can infer effects from the function body. public symbols must spell them out:

```ferrule
// private, inference ok
function helper(cap io: Io) {
    io.println("hello");  // inferred: effects [io]
}

// public, must be explicit
pub function api(cap io: Io) -> Unit effects [io] {
    helper(io);
}
```

exports without explicit effects are rejected.

higher-order functions and effect polymorphism [#higher-order-functions-and-effect-polymorphism]

when you take a function as parameter, you need to handle its effects. `...E` spreads effects from the parameter into the caller:

```ferrule
function map<T, U, E>(items: View<T>, f: (T) -> U effects E) -> Vec<U>
    effects [alloc, ...E]
{
    // ...
}
```

whatever effects f has, map also has, plus alloc.

scoped effect handlers [#scoped-effect-handlers]

`scope` blocks create effect boundaries. these are not full algebraic effects; no continuations. they're scoped effect handlers, limited to lexical scope, compiled to structured control flow (gotos + cleanup). simple enough for a systems language.

retry [#retry]

```ferrule
scope retry(max: 3) {
    const conn = check net.connect(host, port)
    return check conn.read()
} on fail(e) {
    if attempts < max {
        clock.sleep(backoff(attempts))
        retry
    } else {
        propagate e
    }
}
```

timeout [#timeout]

```ferrule
scope timeout(Duration.seconds(5), clock) {
    const data = check fetch(url, net)
    return process(data)
} on timeout {
    return fallback_data()
}
```

transaction [#transaction]

```ferrule
scope transaction(db) {
    check db.insert(record1)
    check db.insert(record2)
} on fail(e) {
    db.rollback()
    propagate e
}
```

async via effects [#async-via-effects]

`suspend` is an effect. no async/await keywords. no function coloring.

```ferrule
function fetch(url: Url, cap net: Net) -> Response
    effects [net, suspend, fail<NetError>]
{
    const conn = check net.connect(url.host, url.port)
    return check conn.readAll()
}
```

scope handlers determine how suspend is resolved:

* `scope parallel { ... }`: runs children concurrently
* `scope race { ... }`: returns first to complete
* `scope blocking { ... }`: runs event loop synchronously

generators via effects [#generators-via-effects]

`yield` is an effect. `for` is the handler:

```ferrule
function fibonacci() -> i32 effects [yield<i32>] {
    var a = 0; var b = 1
    loop { yield a; const next = a + b; a = b; b = next }
}

for n in fibonacci() {
    // ...
}
```

what's planned [#whats-planned]

| feature                                 | status |
| --------------------------------------- | ------ |
| effect syntax and subset rule           | α1     |
| `fail<E>` effect                        | α1     |
| standard resource/computation effects   | α1     |
| effect inference (private functions)    | α1     |
| effect polymorphism (`...E` spread)     | α2     |
| scoped effect handlers (`scope ... on`) | α2     |
| `suspend` effect (async)                | β      |
| `yield` effect (generators)             | β      |
