smut
Functionally manage shared “mutable” state.
Obviously, this isn’t really shared, mutable state in the classic sense. On the Erlang target, the state lives in its own process, with updates and retrievals happening sequentially through that process’s mailbox.
import smut
let counter = smut.new(0)
get(counter) |> echo
// 0
set(12, counter)
get(counter) |> echo
// 12
Types
Values
pub fn get(handle: State(state)) -> state
Return the current value of the state.
import smut
let cat = smut.new("black")
smut.get(cat) |> echo
// "black"
pub fn get_and_update(
with: fn(state) -> state,
handle: State(state),
) -> state
Return the value of the state, then update it atomically with
the supplied function.
import smut
let count = smut.new(0)
smut.get_and_update(fn(n) { n + 1 }, count) |> echo
// 0, if no other processes changed the value between smut.new()
// and smut.update()
smut.get(count) |> echo
// 1, if no other processes changed the value between
// smut.get_and_update() and smut.get()
pub fn new(initial_state: state) -> State(state)
Initialize a new State, returning a handle to it.
import gleam/option.{type Option, None, Some}
import smut.{type State}
let global_frog_type: State(Option(String))= smut.new(Some("blue"))
smut.get(global_frog_type) |> echo
// Some("blue")
Note
On the Erlang target at least, this launches a new process that won’t necessarily get cleaned up just because the handle goes out of scope. Normally you’d get a piece of advice here like, “don’t call this function in a loop”, but Gleam doesn’t have loops! But seriously, don’t call this in a situation where, in a more imperative language, that call site would be in a loop.
pub fn set(new: state, handle: State(state)) -> Nil
Set the value of the State.
import smut.{type State}
let flavors = ["grape", "banana"] |> smut.new()
smut.get(flavors) |> echo
// ["grape", "banana"]
["floor stripper", "bog water"] |> smut.set(flavors)
smut.get(flavors) |> echo
// ["floor stripper", "bog water"]
Note
In a concurrent context, this is probably not the function you want,
at least not most of the time. If there is more than one process
interacting with your State, an update operation like
smut.get(flavors)
|> list.prepend("salted caramel")
|> smut.set(flavors)
is not guaranteed to be atomic. You almost assuredly want one of
instead. Updates through these functions happen entirely in the state handling process, and will be atomic.
pub fn update(
with: fn(state) -> state,
handle: State(state),
) -> Nil
Update the value of the state atomically, with the supplied function.
import smut
let count = smut.new(0)
smut.update(fn(n) { n + 1 }, count)
smut.get(count) |> echo
// 1, if no other processes have also changed the value between
// smut.new() and smut.get()
pub fn update_and_get(
with: fn(state) -> state,
handle: State(state),
) -> state
Update the value of the state with the supplied function, then
return the new updated value.
import smut
let count = smut.new(0)
smut.update_and_get(fn(n), { n + 1 }, count) |> echo
// 1, if no other processes changed the value between
// smut.new() and smut.update_and_get()
smut.get(count) |> echo
// 1 again, if no other process changed the value between
// smut.update_and_get() and smut.get()