Current section

Files

Jump to
smut src smut.erl
Raw

src/smut.erl

-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).