Current section
Files
Jump to
Current section
Files
src/bright.erl
-module(bright).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([init/2, update/2, compute/2, schedule/2, unwrap/1, data/1, computed/1, step/2, start/2, lazy_compute/3, lazy_schedule/3]).
-export_type([bright/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-opaque bright(MWK, MWL) :: {bright,
MWK,
MWL,
list(gleam@dynamic:dynamic_()),
list(gleam@dynamic:dynamic_())}.
-file("src/bright.gleam", 21).
?DOC(
" Creates the initial `Bright`. `data` & `computed` should be initialised with\n"
" their correct empty initial state.\n"
).
-spec init(MWM, MWN) -> bright(MWM, MWN).
init(Data, Computed) ->
{bright, Data, Computed, [], []}.
-file("src/bright.gleam", 60).
?DOC(
" Update data & effects during update cycle. Use it a way to update your data\n"
" stored in `Bright`, and chain them with other `bright` calls.\n"
"\n"
" ```gleam\n"
" pub fn update(model: Bright(data, computed), msg: Msg) {\n"
" use model <- bright.start(model)\n"
" // Run an update, and returns #(Bright(data, computed), Effect(msg)).\n"
" bright.update(model, update_data(_, msg))\n"
" }\n"
" ```\n"
).
-spec update(
{bright(MXD, MXE), lustre@effect:effect(MXH)},
fun((MXD) -> {MXD, lustre@effect:effect(MXH)})
) -> {bright(MXD, MXE), lustre@effect:effect(MXH)}.
update(Bright, Update_) ->
{Bright@1, Effects} = Bright,
{Data, Effect} = Update_(erlang:element(2, Bright@1)),
{begin
_record = Bright@1,
{bright,
Data,
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record)}
end,
lustre@effect:batch([Effects, Effect])}.
-file("src/bright.gleam", 83).
?DOC(
" Derives data from the `data` state, and potentially the current `computed`\n"
" state. `compute` will run **at every render**, so be careful with computations\n"
" as they can block paint or actors.\n"
"\n"
" ```gleam\n"
" pub fn update(model: Bright(data, computed), msg: Msg) {\n"
" use model <- bright.start(model)\n"
" model\n"
" |> bright.update(update_data(_, msg))\n"
" |> bright.compute(fn (d, c) { Computed(..c, field1: computation1(d)) })\n"
" |> bright.compute(fn (d, c) { Computed(..c, field2: computation2(d)) })\n"
" |> bright.compute(fn (d, c) { Computed(..c, field3: computation3(d)) })\n"
" }\n"
" ```\n"
).
-spec compute(
{bright(MXN, MXO), lustre@effect:effect(MXR)},
fun((MXN, MXO) -> MXO)
) -> {bright(MXN, MXO), lustre@effect:effect(MXR)}.
compute(Bright, Compute_) ->
gleam@pair:map_first(
Bright,
fun(Bright@1) ->
_pipe = Compute_(
erlang:element(2, Bright@1),
erlang:element(3, Bright@1)
),
(fun(Computed) -> _record = Bright@1,
{bright,
erlang:element(2, _record),
Computed,
erlang:element(4, _record),
erlang:element(5, _record)} end)(_pipe)
end
).
-file("src/bright.gleam", 109).
?DOC(
" Plugs in existing `data` and `computed` state, to issue some side-effects,\n"
" when your application needs to run side-effects depending on the current state.\n"
"\n"
" ```gleam\n"
" pub fn update(model: Bright(data, computed), msg: Msg) {\n"
" use model <- bright.start(model)\n"
" model\n"
" |> bright.update(update_data(_, msg))\n"
" |> bright.schedule(model, fn (data, computed) {\n"
" use dispatch <- effect.from\n"
" case data.field == 10 {\n"
" True -> dispatch(my_msg)\n"
" False -> Nil\n"
" }\n"
" })\n"
" }\n"
" ```\n"
).
-spec schedule(
{bright(MXW, MXX), lustre@effect:effect(MYA)},
fun((MXW, MXX) -> lustre@effect:effect(MYA))
) -> {bright(MXW, MXX), lustre@effect:effect(MYA)}.
schedule(Bright, Schedule_) ->
{Bright@1, Effects} = Bright,
Effect = Schedule_(erlang:element(2, Bright@1), erlang:element(3, Bright@1)),
{Bright@1, lustre@effect:batch([Effects, Effect])}.
-file("src/bright.gleam", 196).
?DOC(
" Extracts `data` & `computed` states from `Bright`.\n"
"\n"
" ```gleam\n"
" pub fn view(model: Bright(data, computed)) {\n"
" let #(data, computed) = bright.unwrap(model)\n"
" html.div([], [\n"
" // Use data or computed here.\n"
" ])\n"
" }\n"
" ```\n"
).
-spec unwrap(bright(MZB, MZC)) -> {MZB, MZC}.
unwrap(Bright) ->
{erlang:element(2, Bright), erlang:element(3, Bright)}.
-file("src/bright.gleam", 210).
?DOC(
" Extracts `data` state from `Bright`.\n"
"\n"
" ```gleam\n"
" pub fn view(model: Bright(data, computed)) {\n"
" let data = bright.data(model)\n"
" html.div([], [\n"
" // Use data here.\n"
" ])\n"
" }\n"
" ```\n"
).
-spec data(bright(MZF, any())) -> MZF.
data(Bright) ->
erlang:element(2, Bright).
-file("src/bright.gleam", 224).
?DOC(
" Extracts `computed` state from `Bright`.\n"
"\n"
" ```gleam\n"
" pub fn view(model: Bright(data, computed)) {\n"
" let computed = bright.computed(model)\n"
" html.div([], [\n"
" // Use computed here.\n"
" ])\n"
" }\n"
" ```\n"
).
-spec computed(bright(any(), MZK)) -> MZK.
computed(Bright) ->
erlang:element(3, Bright).
-file("src/bright.gleam", 246).
?DOC(
" Allows to run multiple `update` on multiple `Bright` in the same update cycle.\n"
" Every call to step with compute a new `Bright`, and will let you chain the\n"
" steps.\n"
"\n"
" ```gleam\n"
" pub type Model {\n"
" Model(\n"
" fst_bright: Bright(data, computed),\n"
" snd_bright: Bright(data, computed),\n"
" )\n"
" }\n"
"\n"
" fn update(model: Model, msg: Msg) {\n"
" use fst_bright <- bright.step(update_fst(model.fst_bright, msg))\n"
" use snd_bright <- bright.step(update_snd(model.snd_bright, msg))\n"
" #(Model(fst_bright:, snd_bright:), effect.none())\n"
" }\n"
" ```\n"
).
-spec step(
{bright(MZN, MZO), lustre@effect:effect(MZR)},
fun((bright(MZN, MZO)) -> {MZV, lustre@effect:effect(MZR)})
) -> {MZV, lustre@effect:effect(MZR)}.
step(Bright, Next) ->
{Bright@1, Effs} = Bright,
{Model, Effs_} = Next(Bright@1),
{Model, lustre@effect:batch([Effs, Effs_])}.
-file("src/bright.gleam", 281).
-spec panic_if_different_computations_count(list(any()), list(any())) -> nil.
panic_if_different_computations_count(Old_computations, Computations) ->
Count = erlang:length(Old_computations),
gleam@bool:guard(
Count =:= 0,
nil,
fun() ->
Is_same_count = Count =:= erlang:length(Computations),
gleam@bool:guard(
Is_same_count,
nil,
fun() -> erlang:error(#{gleam_error => panic,
message => <<"Memoized computed should be consistent over time, otherwise memo can not work."/utf8>>,
module => <<"bright"/utf8>>,
function => <<"panic_if_different_computations_count"/utf8>>,
line => 289}) end
)
end
).
-file("src/bright.gleam", 38).
?DOC(
" Start the Bright update cycle. Use it as a way to trigger the start of `Bright`\n"
" computations, and chain them with other `bright` calls. `start` handles all\n"
" of the hard work, of turning a `Bright(data, computed)` into a\n"
" `#(Bright(data, computed), Effect(msg))`, and will take care that your\n"
" `Bright(data, computed)` is always consistent over multiple update cycles.\n"
"\n"
" ```gleam\n"
" pub fn update(model: Bright(data, computed), msg: Msg) {\n"
" // Starts the update cycle, and returns #(Bright(data, computed), Effect(msg)).\n"
" use model <- bright.start(model)\n"
" bright.update(model, update_data(_, msg))\n"
" }\n"
" ```\n"
).
-spec start(
bright(MWP, MWQ),
fun(({bright(MWP, MWQ), lustre@effect:effect(MWV)}) -> {bright(MWP, MWQ),
lustre@effect:effect(MWV)})
) -> {bright(MWP, MWQ), lustre@effect:effect(MWV)}.
start(Bright, Next) ->
Old_computations = erlang:element(5, Bright),
gleam@pair:map_first(
Next({Bright, lustre@effect:none()}),
fun(New_data) ->
panic_if_different_computations_count(
Old_computations,
erlang:element(4, New_data)
),
Past_selections = lists:reverse(erlang:element(4, New_data)),
_record = New_data,
{bright,
erlang:element(2, _record),
erlang:element(3, _record),
[],
Past_selections}
end
).
-file("src/bright.gleam", 296).
?DOC(
" Optimization on JS, to ensure that two data sharing the referential equality\n"
" will shortcut the comparison. Useful when performance are a thing in client\n"
" browser. Otherwise, rely on Erlang equality.\n"
).
-spec are_dependencies_equal(any(), any()) -> boolean().
are_dependencies_equal(A, B) ->
gleam_stdlib:identity(A) =:= gleam_stdlib:identity(B).
-file("src/bright.gleam", 255).
-spec lazy_wrap(
{bright(MZY, MZZ), lustre@effect:effect(NAC)},
fun((MZY) -> NAE),
fun(({bright(MZY, MZZ), lustre@effect:effect(NAC)}, fun((MZY, MZZ) -> NAI)) -> {bright(MZY, MZZ),
lustre@effect:effect(NAC)}),
fun((MZY, MZZ, NAE) -> NAI)
) -> {bright(MZY, MZZ), lustre@effect:effect(NAC)}.
lazy_wrap(Bright, Selector, Setter, Compute_) ->
Selected_data = Selector(erlang:element(2, (erlang:element(1, Bright)))),
Selections = [gleam_stdlib:identity(Selected_data) |
erlang:element(4, (erlang:element(1, Bright)))],
Compute_@1 = fun(Data, Computed) ->
Compute_(Data, Computed, Selected_data)
end,
Bright@1 = {begin
_record = erlang:element(1, Bright),
{bright,
erlang:element(2, _record),
erlang:element(3, _record),
Selections,
erlang:element(5, _record)}
end,
erlang:element(2, Bright)},
case erlang:element(5, (erlang:element(1, Bright@1))) of
[] ->
Setter(Bright@1, Compute_@1);
[Value | Past_selections] ->
_pipe = {begin
_record@1 = erlang:element(1, Bright@1),
{bright,
erlang:element(2, _record@1),
erlang:element(3, _record@1),
erlang:element(4, _record@1),
Past_selections}
end,
erlang:element(2, Bright@1)},
case are_dependencies_equal(Value, Selected_data) of
true ->
fun gleam@function:identity/1;
false ->
fun(_capture) -> Setter(_capture, Compute_@1) end
end(_pipe)
end.
-file("src/bright.gleam", 142).
?DOC(
" Derives data like [`compute`](#compute) lazily. `lazy_compute` accepts a\n"
" selector as second argument. Each time the selector returns a different data\n"
" than previous run, the computation will run. Otherwise, nothing happens.\n"
" The computation function will receive `data`, `computed` and the selected\n"
" data (i.e. the result from your selector function), in case accessing the\n"
" selected data is needed.\n"
"\n"
" ```gleam\n"
" pub fn update(model: Bright(data, computed), msg: Msg) {\n"
" use model <- bright.start(model)\n"
" model\n"
" |> bright.update(update_data(_, msg))\n"
" // Here, e is always the result data.field / 10 (the result from selector).\n"
" |> bright.lazy_compute(selector, fn (d, c, e) { Computed(..c, field1: computation1(d, e)) })\n"
" |> bright.lazy_compute(selector, fn (d, c, e) { Computed(..c, field2: computation2(d, e)) })\n"
" |> bright.lazy_compute(selector, fn (d, c, e) { Computed(..c, field3: computation3(d, e)) })\n"
" }\n"
"\n"
" /// Use it with lazy_compute to recompute only when the field when\n"
" /// { old_data.field / 10 } != { data.field / 10 }\n"
" fn selector(d, _) {\n"
" d.field / 10\n"
" }\n"
" ```\n"
).
-spec lazy_compute(
{bright(MYG, MYH), lustre@effect:effect(MYK)},
fun((MYG) -> MYM),
fun((MYG, MYH, MYM) -> MYH)
) -> {bright(MYG, MYH), lustre@effect:effect(MYK)}.
lazy_compute(Bright, Selector, Compute_) ->
lazy_wrap(Bright, Selector, fun compute/2, Compute_).
-file("src/bright.gleam", 178).
?DOC(
" Plugs in existing `data` like [`schedule`](#schedule) lazily. `lazy_schedule` accepts\n"
" a selector as second argument. Each time the selector returns a different data\n"
" than previous run, the computation will run. Otherwise, nothing happens.\n"
" The scheduling function will receive `data`, `computed` and the selected\n"
" data (i.e. the result from your selector function), in case accessing the\n"
" selected data is needed.\n"
"\n"
" ```gleam\n"
" pub fn update(model: Bright(data, computed), msg: Msg) {\n"
" use model <- bright.start(model)\n"
" model\n"
" |> bright.update(update_data(_, msg))\n"
" // e is equal to d.field / 10 (the result from selector).\n"
" |> bright.lazy_schedule(selector, fn (data, computed, selected) {\n"
" use dispatch <- effect.from\n"
" case d.field == 10 {\n"
" True -> dispatch(my_msg)\n"
" False -> Nil\n"
" }\n"
" })\n"
" }\n"
"\n"
" /// Use it with lazy_schedule to recompute only when the field when\n"
" /// { old_data.field / 10 } != { data.field / 10 }\n"
" fn selector(d, _) {\n"
" d.field / 10\n"
" }\n"
" ```\n"
).
-spec lazy_schedule(
{bright(MYQ, MYR), lustre@effect:effect(MYU)},
fun((MYQ) -> MYW),
fun((MYQ, MYR, MYW) -> lustre@effect:effect(MYU))
) -> {bright(MYQ, MYR), lustre@effect:effect(MYU)}.
lazy_schedule(Bright, Selector, Schedule_) ->
lazy_wrap(Bright, Selector, fun schedule/2, Schedule_).