Skip to content

Instantly share code, notes, and snippets.

@evancz
Last active May 21, 2017 05:01
Show Gist options
  • Save evancz/44c90ac34f46c63a27ae to your computer and use it in GitHub Desktop.
Save evancz/44c90ac34f46c63a27ae to your computer and use it in GitHub Desktop.

Components with Effects

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.

Overall Picture

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.

services

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.

Basic Version of an Effectful Architecture

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:

  1. 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 like batch subeffectsin my update function.
  2. 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.
@rtfeldman
Copy link

Oh wait, I think I just figured it out...that implementation is prone to race conditions. Like if you start with model1 and kick off an HTTP task, then somebody else comes along with model2 and kicks off another HTTP task, but the latter finishes first, then the former will finish and happily extend model1 to determine the new model, overriding whatever impact on model the latter wanted to have.

Making sure all the Tasks do is generate more Actions not only avoids this problem, but makes sure it can't happen.

tl;dr I'm dumb and this is better.

@mgold
Copy link

mgold commented Jul 14, 2015

@rgrempel I really like that example and I agree with your intuition. I think reaction is a great name. However, I think that sometimes you will need the model. For example, I have a text field and a submit button. Because of how Grahics.Input.Form works, the field content is stored in the model. To send it to the server when the submit happens, I need access to the model. I think that a well-placed sampleOn could ensure that the model is updated (and saved in the typical foldp way), and then passed with the same action to reaction to see if anything effectful needs to happen.

@mgold
Copy link

mgold commented Jul 14, 2015

This just came up on the list, where the model informs the reaction, and the original action is not (directly) needed. So I think any approach is going to need to create a reaction based on both. Cruddy ASCII signal flow diagram:

old model ----new model---\
action-------/-------------reaction

@aphorisme
Copy link

After some days off, I had another look into my question which Max pointed out to be related to this gist.

After all, I've came up with a general idea I put into a library; maybe it can give you some inspiration on your thoughts: (https://github.com/aphorisme/elm-oprocesso)
It's very fresh, but I will use it on my next apps, so it might evolve into something helpful.

@aphorisme
Copy link

Just in case you already had a look: I've decided to change the main type, such that one can glue actions together. I'm on the run for now, but I will finish it tomorrow (or later).

@rgrempel
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment