The Return Monad

Harness the power of (model, Cmd msg)

Writing 100% pure code is very nice, and Elm facilitates that, but in the end we need to do IO and have effects on the world. In Elm the biggest side effects are abstracted away into the runtime, like applying the virtual dom patch, or ticking the Cmd queue. However this is unlikely to represent all of the side effects needed for an application.

The closest thing we have to the IO Monad is Task. While Task is a proper monad and all, Elm is not monadic programming. The common paradigm is not composing effects monadically using Task, but to compose effects monoidally with Cmd.

Specifically the Elm language dictates to us a select number of locations in the code where we can pass Cmd's to the runtime for execution.

Where can we perform arbitrary IO?

Lets take a look at Navigation.programWithFlags and see what it gives us. We are going to use this version of program because its more like what you might use in the real world. Its also a more complex version, and shows more of what production Elm looks like.

programWithFlags
  :  Parser data
  -> { init : flags -> data -> (model, Cmd msg)
     , update : msg -> model -> (model, Cmd msg)
     , urlUpdate : data -> model -> (model, Cmd msg)
     , view : model -> Html msg
     , subscriptions : model -> Sub msg }
  -> Program flags

This gives use three places to hand Cmds to the runtime, init, update, and urlUpdate. And thats it, those are the only places that IO is possible. And Each time we don't hand the Cmd alone, but as a part of a (model, Cmd msg) tuple.

So whether we are using the elm-return library to treat (model, Cmd msg) as a Monad or not, Program dictates that we will be write code in the context of this type.

For each of these functions we can think in terms of what information is available to us. We can have an effect dependent on the shape of the available incoming values, or ignore those values.

init

init : flags -> data -> (model, Cmd msg)

We can have an effect, once at the start of the app, based on the url data and any flags passed from JavaScript.

update

update : msg -> model -> (model, Cmd msg)

We can have an effect each time our application receives a message from the runtime, based on the previous model and the current msg.

urlUpdate

urlUpdate : data -> model -> (model, Cmd msg)

We can have an effect when the url changes, based on the url data, and the previous model.

Return

In Elm we will be writing a great deal of our code in the context of the Return Monad.

type alias Return msg model =
    (model, Cmd msg)

The Return Monad is just a Monad Writer, with Cmd msg as the monoid, and model as the monadic value. As such the action of andThen/>>= zips together Cmds automatically. And we get singleton/pure/return from Cmd.none

andThen : Return x a -> (a -> Return x b) -> Return x b
andThen (model, cmd) f =
  let
    (model', cmd') =
      f model

  in
    model' ! [cmd, cmd']

singleton : a -> Return x a
signleton model = (model, Cmd.none)

If you've been working with Elm for any period of time, you should be noticing something. The above code is extremely common boilerplate that occurs frequently when embedding update functions from sub components.

A side benefit of writing code in this style is that dropping Cmds is no longer a risk. In practice code that drops a Cmd by substituting in a Cmd from a sub update function, happens, is subtle and can be hard to identify.

Leveraging the context

Lets say we have a redirect in our SPA. If the model shows the user has not logged in, we want to redirect to the /login page. This needs to be checked each time regardless. It needs to be checked when the application starts, when the application updates (the user may have logged out), and when the urlChanges.

We don't want to define our redirect function more than once, and we want to define it with an expressive type.

redirect : { authorized : Bool } -> Route -> Route

So how to we add this functionality to our app?

init : Flags -> Route -> (Model, Cmd Msg)
init flags route =
  case route of
    Index ->
      ({ initialModel | indexModel = Ind (initialIndex flags) }
      , Cmd.none
      )

    Dashboard ->
      ({ initialModel | pageModel = Dash initialDashboard}
      , getData
      )

Its actually not clear at all. We could do something like:

init : Flags -> Route -> (Model, Cmd Msg)
init flags route =
  let
    (model, cmd) =
      case route of
        Index ->
          ({ initialModel | indexModel = Ind (initialIndex flags) }
          , Cmd.none
          )

        Dashboard ->
          ({ initialModel | pageModel = Dash initialDashboard}
          , getData
          )
  in
    model !
      [ cmd
      , redirect flags route
      ]

But that gets unwieldy, particularly if we have more than one Cmd we need to layer in. Worse still if we need to guarantee that parts of the Model are synchronized. Or we need to ensure that a chain of effects is applied in more than one of the three effectful locations dictated by Program.

For example:


init : Flags -> Route -> (Model, Cmd Msg)
init flags route =
  let
    (model, cmd) =
      case route of
        Index ->
          ({ initialModel | indexModel = Ind (initialIndex flags) }
          , Cmd.none
          )

        Dashboard ->
          ({ initialModel | pageModel = Dash initialDashboard}
          , getData
          )

    (model', cmd') =
      syncPartsOfTheModel model !
        [ cmd
        , redirect flags route
        , thenDoMeAlso
        ]
  in
    model' !
      [ cmd'
      , basedOnModelHaveEffect model' ]

There is real potential for human error in the above code, as effects are interleaved throughout, and the developer may drop, or duplicate Cmds by accident, and the order of operations is obfuscated by pattern matches and literals.

Now with Return

We can easily layer in a command of this nature. This is equivalent to tell since we are writing to the Cmd monoid.

command : Cmd msg -> Return msg model -> Return msg model

And now that we have functions executing on the Return context, we can pipeline in more effects on the monad. The result is code that is easier to read and comprehend.

init : Flags -> Route -> Return Msg Model
init flags route =
  (case route of
      Index ->
        ({ initialModel | indexModel = Ind (initialIndex flags) }
        , Cmd.none
        )

      Dashboard ->
        ({ initialModel | pageModel = Dash initialDashboard}
        , getData
        )
  )
  |> command (redirect flags route)
  |> map (syncPartsOfTheModel : Model -> Model)
  |> command thenDoMeAlso
  |> effect basedOnModelHaveEffect

And if we need to reuse part of this pipeline, staying dry is now easy:

regardless : ReturnF Msg Model
regardless =
  >> map syncPartsOfTheModel
  >> command thenDoMeAlso
  >> effect basedOnModelHaveEffect

This means its now easier to compose effects!

It also facilitates writing our functions with more semantic types.

f : Model -> Cmd Msg

The type clearly expresses the usage of this function, we are not altering the model, and we don't need a Msg, url data, or flags. And we can interact with these as well (see the Respond module).

Embedding Update

The update function demanded by Program is the place where we can have stateful code in Elm. So any sub component of the application that has state will need to be wired into this function somehow.

A sub update function may have one of the following relations to the top level update function:

Uses the same Msg Uses a sub Msg
Uses the same Model BB BS
Uses a sub Model SB SS

where

Lets see how coding in the context of Return can clean up some of this familiar boilerplate.

SubMsg

mapCmd : (a -> b) -> Return a model -> Return b model

update : Msg -> Model -> Return Msg Model
update msg model =
  case msg of
    Foo foo ->
      Foo.update foo model
        |> mapCmd Foo

SubModel

map : (a -> b) -> Return msg a -> Return msg b

update : Msg -> Model -> Return Msg Model
update msg model =
  case msg of
    Foo ->
      Foo.update msg model.foo
        |> map Foo

SubMsg SubModel

mapBoth : (a -> b) -> (c -> d) -> Return a c -> Return b d

update : Msg -> Model -> Return Msg Model
update msg model =
  case msg of
    Foo foo ->
      Foo.update foo model.foo
        |> mapBoth Foo FooModel

Room to explore

So long as (model, Cmd msg) is our one and only means of interacting with state, as well as side effects, it worth it to explore the type and understand its properties. There is more to consider that is outside the scope of this post.

If you want to try out some of these techniques, you can find my package for the Return Monad below:

Fresheyeball/elm-return

Feedback appreciated. Thanks to toastal and splodingsocks for proofing this content.