-module(smut). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/smut.gleam"). -export([new/1, set/2, get/1, update/2, get_and_update/2, update_and_get/2]). -export_type([msg/1, state/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Functionally manage shared \"mutable\" state.\n" "\n" " Obviously, this isn't really shared, mutable state in the classic\n" " sense. On the Erlang target, the state lives in its own process,\n" " with updates and retrievals happening sequentially through that\n" " process's mailbox.\n" "\n" " ```gleam\n" " import smut\n" "\n" " let counter = smut.new(0)\n" " get(counter) |> echo\n" " // 0\n" " set(12, counter)\n" " get(counter) |> echo\n" " // 12\n" " ```\n" ). -type msg(EAQ) :: {assign, EAQ} | {update, fun((EAQ) -> EAQ)} | {retrieve_then_update, gleam@erlang@process:subject(EAQ), fun((EAQ) -> EAQ)} | {update_then_retrieve, gleam@erlang@process:subject(EAQ), fun((EAQ) -> EAQ)} | {retrieve, gleam@erlang@process:subject(EAQ)}. -opaque state(EAR) :: {handle, gleam@erlang@process:subject(msg(EAR))}. -file("src/smut.gleam", 36). -spec run(EAS, gleam@erlang@process:subject(msg(EAS))) -> nil. run(State, Pipe) -> case gleam_erlang_ffi:'receive'(Pipe) of {assign, New_state} -> run(New_state, Pipe); {update, With} -> _pipe = With(State), run(_pipe, Pipe); {retrieve_then_update, Return_pipe, With@1} -> gleam@erlang@process:send(Return_pipe, State), _pipe@1 = With@1(State), run(_pipe@1, Pipe); {update_then_retrieve, Return_pipe@1, With@2} -> New_state@1 = With@2(State), gleam@erlang@process:send(Return_pipe@1, New_state@1), run(New_state@1, Pipe); {retrieve, Return_pipe@2} -> gleam@erlang@process:send(Return_pipe@2, State), run(State, Pipe) end. -file("src/smut.gleam", 75). ?DOC( " Initialize a new `State`, returning a handle to it.\n" "\n" " ```gleam\n" " import gleam/option.{type Option, None, Some}\n" " import smut.{type State}\n" "\n" " let global_frog_type: State(Option(String))= smut.new(Some(\"blue\"))\n" " smut.get(global_frog_type) |> echo\n" " // Some(\"blue\")\n" " ```\n" "\n" " ### Note\n" " \n" " On the Erlang target at least, this launches a new process\n" " that won't necessarily get cleaned up just because the handle goes\n" " out of scope. Normally you'd get a piece of advice here like, \"don't call\n" " this function in a loop\", but Gleam doesn't have loops! But seriously,\n" " don't call this in a situation where, in a more imperative language,\n" " that call site would be in a loop.\n" ). -spec new(EAV) -> state(EAV). new(Initial_state) -> Back_channel = gleam@erlang@process:new_subject(), proc_lib:spawn_link( fun() -> Pipe = gleam@erlang@process:new_subject(), gleam@erlang@process:send(Back_channel, Pipe), run(Initial_state, Pipe) end ), Pipe@1 = gleam_erlang_ffi:'receive'(Back_channel), {handle, Pipe@1}. -file("src/smut.gleam", 121). ?DOC( " Set the value of the `State`.\n" "\n" " ```gleam\n" " import smut.{type State}\n" "\n" " let flavors = [\"grape\", \"banana\"] |> smut.new()\n" " smut.get(flavors) |> echo\n" " // [\"grape\", \"banana\"]\n" " [\"floor stripper\", \"bog water\"] |> smut.set(flavors)\n" " smut.get(flavors) |> echo\n" " // [\"floor stripper\", \"bog water\"]\n" " ```\n" "\n" " ## Note\n" "\n" " In a concurrent context, this is probably not the function you want,\n" " at least not most of the time. If there is more than one process\n" " interacting with your `State`, an update operation like\n" "\n" " ```gleam\n" " smut.get(flavors)\n" " |> list.prepend(\"salted caramel\")\n" " |> smut.set(flavors)\n" " ```\n" "\n" " is not guaranteed to be atomic. You almost assuredly want one of\n" " \n" " * [`update()`](#update)\n" " * [`get_and_update()`](#get_and_update)\n" " * [`update_and_get()`](#update_and_get)\n" " \n" " instead. Updates through these functions happen entirely in the\n" " state handling process, and will be atomic.\n" ). -spec set(EAX, state(EAX)) -> nil. set(New, Handle) -> {handle, Send} = Handle, gleam@erlang@process:send(Send, {assign, New}). -file("src/smut.gleam", 135). ?DOC( " Return the current value of the state.\n" "\n" " ```gleam\n" " import smut\n" "\n" " let cat = smut.new(\"black\")\n" " smut.get(cat) |> echo\n" " // \"black\"\n" " ```\n" ). -spec get(state(EBA)) -> EBA. get(Handle) -> Return = gleam@erlang@process:new_subject(), {handle, Send} = Handle, gleam@erlang@process:send(Send, {retrieve, Return}), gleam_erlang_ffi:'receive'(Return). -file("src/smut.gleam", 153). ?DOC( " Update the value of the state atomically, `with` the supplied function.\n" "\n" " ```gleam\n" " import smut\n" "\n" " let count = smut.new(0)\n" " smut.update(fn(n) { n + 1 }, count)\n" " smut.get(count) |> echo\n" " // 1, if no other processes have also changed the value between\n" " // smut.new() and smut.get()\n" " ```\n" ). -spec update(fun((EBC) -> EBC), state(EBC)) -> nil. update(With, Handle) -> {handle, Send} = Handle, gleam@erlang@process:send(Send, {update, With}), nil. -file("src/smut.gleam", 173). ?DOC( " Return the value of the state, then update it atomically `with`\n" " the supplied function.\n" "\n" " ```gleam\n" " import smut\n" "\n" " let count = smut.new(0)\n" " smut.get_and_update(fn(n) { n + 1 }, count) |> echo\n" " // 0, if no other processes changed the value between smut.new()\n" " // and smut.update()\n" " smut.get(count) |> echo\n" " // 1, if no other processes changed the value between\n" " // smut.get_and_update() and smut.get()\n" " ```\n" ). -spec get_and_update(fun((EBE) -> EBE), state(EBE)) -> EBE. get_and_update(With, Handle) -> Return = gleam@erlang@process:new_subject(), {handle, Send} = Handle, gleam@erlang@process:send(Send, {retrieve_then_update, Return, With}), gleam_erlang_ffi:'receive'(Return). -file("src/smut.gleam", 194). ?DOC( " Update the value of the state `with` the supplied function, _then_\n" " return the new updated value.\n" "\n" " ```gleam\n" " import smut\n" "\n" " let count = smut.new(0)\n" " smut.update_and_get(fn(n), { n + 1 }, count) |> echo\n" " // 1, if no other processes changed the value between\n" " // smut.new() and smut.update_and_get()\n" " smut.get(count) |> echo\n" " // 1 again, if no other process changed the value between\n" " // smut.update_and_get() and smut.get()\n" " ```\n" ). -spec update_and_get(fun((EBG) -> EBG), state(EBG)) -> EBG. update_and_get(With, Handle) -> Return = gleam@erlang@process:new_subject(), {handle, Send} = Handle, gleam@erlang@process:send(Send, {update_then_retrieve, Return, With}), gleam_erlang_ffi:'receive'(Return).