effectful 0.2.2

Effect<A, E, R> (sync + async), context/layers, pipe — interpreter-style, no bundled executor
Documentation
# Stm and commit — Building Transactions

`Stm<A, E>` describes a transactional computation. It is lazy and pure with respect to the outside world: it can read/write transactional cells, fail with `E`, or retry.

## commit: Lift Stm into Effect

```rust,ignore
use effectful::{Effect, Stm, TRef, commit, run_blocking};

let ref_a: TRef<i32> = run_blocking(commit(TRef::make(1)), ())?;
let ref_b: TRef<i32> = run_blocking(commit(TRef::make(2)), ())?;

let transaction: Stm<i32, ()> = ref_a.read_stm().flat_map(move |a| {
    ref_b.read_stm().map(move |b| a + b)
});

let effect: Effect<i32, (), ()> = commit(transaction);
let result = run_blocking(effect, ())?;
```

`commit(stm)` returns `Effect<A, E, R>`. The transaction is executed when the effect runs. On commit conflicts or `Stm::retry()`, it retries.

## atomically

`atomically(stm)` is an alias for `commit(stm)`. It still returns an `Effect`; run it with `run_blocking`, `run_async`, or compose it with other effects.

```rust,ignore
use effectful::{atomically, run_blocking};

let effect = atomically(counter.modify_stm(|n| (n + 1, n + 1)));
let value = run_blocking(effect, ())?;
```

## Stm::fail

Transactions can fail with typed errors:

```rust,ignore
use effectful::{Stm, TRef};

fn withdraw(account: TRef<u64>, amount: u64) -> Stm<u64, InsufficientFunds> {
    account.read_stm().flat_map(move |balance| {
        if balance < amount {
            Stm::fail(InsufficientFunds)
        } else {
            account
                .write_stm(balance - amount)
                .map(move |_| balance - amount)
        }
    })
}
```

`Stm::fail(e)` aborts the current transaction immediately. `commit` propagates that `E` through the returned effect.

## Stm::retry

Sometimes a transaction should wait until a condition is true rather than fail:

```rust,ignore
use effectful::{Stm, TRef};

fn dequeue(queue: TRef<Vec<Item>>) -> Stm<Item, ()> {
    queue.read_stm().flat_map(move |items| {
        if items.is_empty() {
            Stm::retry()
        } else {
            let item = items[0].clone();
            queue.write_stm(items[1..].to_vec()).map(move |_| item)
        }
    })
}
```

`Stm::retry()` asks the commit loop to restart later. `TQueue::take` uses this behavior to wait while empty.

## Composing Transactions

Transactions compose with `flat_map` and `map`.

```rust,ignore
let big_transaction: Stm<(), AppError> = transfer_funds(from, to, amount)
    .flat_map(move |_| record_audit_log(from, to, amount));

let effect = commit(big_transaction);
```

The composed transaction commits as a unit. If any part fails, retries, or observes a conflict, the whole transaction does not partially commit.