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

A handle to the mutable state.

pub opaque type State(state)

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()
Search Document