Packages
lustre
4.6.4
5.7.1
5.7.0
5.6.0
5.5.2
5.5.1
5.5.0
5.4.0
5.3.5
5.3.4
5.3.3
5.3.2
5.3.1
5.3.0
5.2.1
5.2.0
5.1.1
5.1.0
5.0.3
5.0.2
5.0.1
5.0.0
4.6.4
4.6.3
4.6.2
4.6.1
4.6.0
4.5.1
4.5.0
4.4.4
4.4.3
4.4.1
4.4.0
4.3.6
4.3.5
4.3.4
4.3.3
4.3.2
4.3.1
4.3.0
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.0
4.0.0-rc1
4.0.0-rc.2
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.2
3.0.0-rc.1
2.0.1
2.0.0
1.3.0
1.2.0
1.1.0
1.0.0
Create HTML templates, single page applications, Web Components, and real-time server components in Gleam!
Current section
Files
Jump to
Current section
Files
src/lustre@effect.erl
-module(lustre@effect).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([custom/1, from/1, event/2, none/0, batch/1, map/2, perform/5]).
-export_type([effect/1, actions/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(
" In other frameworks it's common for components to perform side effects\n"
" whenever the need them. An event handler might make an HTTP request, or a\n"
" component might reach into the DOM to focus an input.\n"
"\n"
" In Lustre we try to keep side effects separate from our main program loop.\n"
" This comes with a whole bunch of benefits like making it easier to test and\n"
" reason about our code, making it possible to implement time-travel debugging,\n"
" or even to run our app on the server using Lustre's server components. This\n"
" is great but we still need to perform side effects at some point, so how do\n"
" we do that?\n"
"\n"
" The answer is through the `Effect` type that treats side effects as *data*.\n"
" This approach is known as having **managed effects**: you pass data that\n"
" describes a side effect to Lustre's runtime and it takes care of performing\n"
" that effect and potentially sending messages back to your program for you.\n"
" By going through this abstraction we discourage side effects from being\n"
" performed in the middle of our program.\n"
"\n"
" ## Examples\n"
"\n"
" For folks coming from other languages (or other Gleam code!) where side\n"
" effects are often performed in-place, this can feel a bit strange. A couple\n"
" of the examples in the repo tackle effects:\n"
"\n"
" - [`05-http-requests`](https://github.com/lustre-labs/lustre/tree/main/examples/05-http-requests)\n"
" - [`06-custom-effects`](https://github.com/lustre-labs/lustre/tree/main/examples/06-custom-effects)\n"
"\n"
" This list of examples is likely to grow over time, so be sure to check back\n"
" every now and then to see what's new!\n"
"\n"
" ## Getting help\n"
"\n"
" If you're having trouble with Lustre or not sure what the right way to do\n"
" something is, the best place to get help is the [Gleam Discord server](https://discord.gg/Fm8Pwmy).\n"
" You could also open an issue on the [Lustre GitHub repository](https://github.com/lustre-labs/lustre/issues).\n"
"\n"
" While our docs are still a work in progress, the official [Elm guide](https://guide.elm-lang.org)\n"
" is also a great resource for learning about the Model-View-Update architecture\n"
" and the kinds of patterns that Lustre is built around.\n"
"\n"
).
-opaque effect(NXX) :: {effect, list(fun((actions(NXX)) -> nil))}.
-type actions(NXY) :: {actions,
fun((NXY) -> nil),
fun((binary(), gleam@json:json()) -> nil),
fun((gleam@erlang@process:selector(NXY)) -> nil),
gleam@dynamic:dynamic_()}.
-file("src/lustre/effect.gleam", 126).
?DOC(false).
-spec custom(
fun((fun((NYD) -> nil), fun((binary(), gleam@json:json()) -> nil), fun((gleam@erlang@process:selector(NYD)) -> nil), gleam@dynamic:dynamic_()) -> nil)
) -> effect(NYD).
custom(Run) ->
{effect,
[fun(Actions) ->
Run(
erlang:element(2, Actions),
erlang:element(3, Actions),
erlang:element(4, Actions),
erlang:element(5, Actions)
)
end]}.
-file("src/lustre/effect.gleam", 105).
?DOC(
" Construct your own reusable effect from a custom callback. This callback is\n"
" called with a `dispatch` function you can use to send messages back to your\n"
" application's `update` function.\n"
"\n"
" Example using the `window` module from the `plinth` library to dispatch a\n"
" message on the browser window object's `\"visibilitychange\"` event.\n"
"\n"
" ```gleam\n"
" import lustre/effect.{type Effect}\n"
" import plinth/browser/window\n"
"\n"
" type Model {\n"
" Model(Int)\n"
" }\n"
"\n"
" type Msg {\n"
" FetchState\n"
" }\n"
"\n"
" fn init(_flags) -> #(Model, Effect(Msg)) {\n"
" #(\n"
" Model(0),\n"
" effect.from(fn(dispatch) {\n"
" window.add_event_listener(\"visibilitychange\", fn(_event) {\n"
" dispatch(FetchState)\n"
" })\n"
" }),\n"
" )\n"
" }\n"
" ```\n"
).
-spec from(fun((fun((NXZ) -> nil)) -> nil)) -> effect(NXZ).
from(Effect) ->
custom(fun(Dispatch, _, _, _) -> Effect(Dispatch) end).
-file("src/lustre/effect.gleam", 117).
?DOC(false).
-spec event(binary(), gleam@json:json()) -> effect(any()).
event(Name, Data) ->
custom(fun(_, Emit, _, _) -> Emit(Name, Data) end).
-file("src/lustre/effect.gleam", 146).
?DOC(
" Most Lustre applications need to return a tuple of `#(model, Effect(msg))`\n"
" from their `init` and `update` functions. If you don't want to perform any\n"
" side effects, you can use `none` to tell the runtime there's no work to do.\n"
).
-spec none() -> effect(any()).
none() ->
{effect, []}.
-file("src/lustre/effect.gleam", 164).
?DOC(
" Batch multiple effects to be performed at the same time.\n"
"\n"
" **Note**: The runtime makes no guarantees about the order on which effects\n"
" are performed! If you need to chain or sequence effects together, you have\n"
" two broad options:\n"
"\n"
" 1. Create variants of your `msg` type to represent each step in the sequence\n"
" and fire off the next effect in response to the previous one.\n"
"\n"
" 2. If you're defining effects yourself, consider whether or not you can handle\n"
" the sequencing inside the effect itself.\n"
).
-spec batch(list(effect(NYI))) -> effect(NYI).
batch(Effects) ->
{effect,
begin
gleam@list:fold(
Effects,
[],
fun(B, _use1) ->
{effect, A} = _use1,
lists:append(B, A)
end
)
end}.
-file("src/lustre/effect.gleam", 178).
?DOC(
" Transform the result of an effect. This is useful for mapping over effects\n"
" produced by other libraries or modules.\n"
"\n"
" **Note**: Remember that effects are not _required_ to dispatch any messages.\n"
" Your mapping function may never be called!\n"
).
-spec map(effect(NYM), fun((NYM) -> NYO)) -> effect(NYO).
map(Effect, F) ->
{effect,
begin
gleam@list:map(
erlang:element(2, Effect),
fun(Eff) ->
fun(Actions) ->
Eff(
{actions,
fun(Msg) ->
(erlang:element(2, Actions))(F(Msg))
end,
erlang:element(3, Actions),
fun(Selector) ->
(erlang:element(4, Actions))(
gleam_erlang_ffi:map_selector(
Selector,
F
)
)
end,
erlang:element(5, Actions)}
)
end
end
)
end}.
-file("src/lustre/effect.gleam", 230).
?DOC(false).
-spec perform(
effect(NYQ),
fun((NYQ) -> nil),
fun((binary(), gleam@json:json()) -> nil),
fun((gleam@erlang@process:selector(NYQ)) -> nil),
gleam@dynamic:dynamic_()
) -> nil.
perform(Effect, Dispatch, Emit, Select, Root) ->
Actions = {actions, Dispatch, Emit, Select, Root},
gleam@list:each(erlang:element(2, Effect), fun(Eff) -> Eff(Actions) end).