One of the big questions we see these days is “how do I use the Elm Architecture with tasks?”
So the goal here is to outline how to have components nested deep in your application that can trigger effects.
So the overall picture is that we have a pure and immutable chunk of code called “the Elm Architecture” where we describe our whole application. We then have tiny services on the borders where all of our effects are run and then routed back into our application.
A number of nice benefits come from this pattern. They show up at least in the case of HTTP communitation and database communication.
-
HTTP: Let's say you are loading a bunch of information, possibly redundant in various ways. In the normal world, every component sends a bunch of requests independent of everything else, so if 10 things need your profile picture, they all send that request. By routing all the HTTP effects to a centlized place, we can ensure that these are batched, perhaps making things much faster.
-
Databases: Let's say a bunch of components generate changes they'd like to send to the database. It is concievable that you want to turn a bunch of haphazard changes into a single transaction that will succeed or fail in a way that is easy to understand and work with. By batching all the database effects in one place, there can be some logic about how to batch things into transactions, or perhaps avoid sending the requests altogether under certain circumstances.
While it is nice that you actually can get some nice things from this setup, it really is not a choice for us. Ultimately, we have to do things this way if we want to continue to manage effects. So the question is effectively, how do make this happen in a regular and easy to understand way.
This essentially extends the basic Elm Architecture with some way of threading effects around.
-- BASIC STUFF
model : Model
view : Address Action -> Model -> Html
update : Address Action -> Action -> Model -> (Model, Effects)
-- We get an address in so we know how to route any effects
-- EFFECTS
type Effects
-- An opaque type, conceptually a list of tasks.
-- It can be constructed in very specific ways.
doNothing : Effects
arbitraryTask : Address action -> Task Never action -> Effects
-- When the `Task` is done, the result is routed to the given `Address`.
-- `Never` is an uninhabited type, no value exists that has that type.
-- See http://package.elm-lang.org/packages/imeckler/empty/latest
-- The point is that we can specify "this task must not fail" which is
-- another way of saying "you must explicitly handle any potential errors"
http : Address action -> Http Never action -> Effects
-- `Http` is some custom thing like `Task`, but with extra restrictions
-- By making it its own thing, we can analyze it to automatically do any
-- necessary batching and caching.
database : Address action -> Database Never action -> Effects
-- `Database` is the same idea as `Http` where we can have some custom
-- chainable thing that can be turned into atomic transactions in a nice way
batch : List Effects -> Effects
-- Sometimes you want to do a batch of things all at once.
-- START
start : App model action -> Output
type alias App model action =
{
model : model,
view : Address action -> model -> Html,
update : Address action -> action -> model -> (model, Effects)
}
type alias Output =
{
frames : Signal Html,
-- hand this to main
effects : Signal (Task () ())
-- hand this directly to a port, it will run effects and route the results
}
Now I think the two main concerns here are:
- Will it be a pain to manually route
Effects
? If I am updating the subcomponents, I need to be sure to gather all their effects together and say something likebatch subeffects
in myupdate
function. - It seems like the ideal version of this will allow people to write custom effect managers. If you want to integrate with IndexDB or MySQL or whatever, maybe you want to write a custom thing just for that. I think this is doable, but I don't have enough experience yet to be able to write down what that'll look like.
@mgold I did indeed eventually have to supply the model to the
reaction
function -- as you predicted, I came across a use case that needed it.