Do you want to encapsulate a bit of state, but find updating components daunting and the boilerplate cringe-worthy? Well, you probably don’t want or need this since you likely just want to keep all or as much of the state at the top-level, but in case you are building a true, standalone component, then this might be of use.

After watching much of the Elm Conf talks—particularly the Q&A panel—there was a decent amount of discussion about components. Most responses were along the lines of “try not to use them” and “use them where appropriate—where you need some local state”. This is very true, but what happens when you do need components? The first thing you’ll notice is that it’s cumbersome and the general reaction from the community is that by making it cumbersome you will be influenced to not use components as often. …But you will probably have to do it eventually and so it still should to be addressed.

…Think about functions as the key part of reuse … when you want to break out some logic, always reach for a function. So if your update [function] is getting too crazy, make a helper function.

So let’s talk about what this update helper function would look like!


A basic understanding Fresheyeball’s Return monad (or Haskell’s Writer monad) will be required. I highly suggest reading the author’s blog post for context, “The Return Monad: Harness the power of ( model, Cmd msg )”.

By no means is this the right way to do Elm as that is subjective. However, this is how I’ve started writing my update function in a nearly 8000-line production-ready code base.

Return.Optics, package here, is a utility library extending Return with Monocle making a clean, concise API for doing Elm component updates in the context of other updates. Initially it includes helper functions around refraction—the bending of light. Like viewing a straw being inserted into a glass of water, we’ll use a Lens to bend our top-level update function into our component update, and when we pull it out, well be left with an unbent ( model, Cmd msg ) of the Elm architecture, slicing down the size and simplifying our code.


If that doesn’t make sense, you’re in luck because we’re about to go over an example.

Suppose we have this trivial, toy component and model…

Models

module Model exposing (Model)

import Checkbox.Model as Checkbox


type alias Model =
    { pageTitle : String
    , checkbox : Checkbox.Model
    }
module Checkbox.Model exposing (Model)

type alias Model =
    { checked : Bool
    }

Msgs

module Msg exposing (Msg(..))

import Checkbox.Msg as Checkbox


type Msg
    = TitleChange String
    | CheckboxMsg Checkbox.Msg
module Checkbox.Msg exposing (Msg(..))

type Msg
    = CheckMe Bool

Assuming we have built up some cmdWeAlwaysDo, with the standard library we’d write updates like this:

Stardard Updates

module Update exposing (update)

import Checkbox.Update as Checkbox
import Model
import Msg exposing (Msg(TitleChange, CheckboxMsg))


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    let
        cmdWeAlwaysDo : Cmd Msg
        cmdWeAlwaysDo =
            -- insert a real command in a non-toy app
            Cmd.none
    in
        case msg of
            TitleChange title ->
                ( { model | pageTitle = title }, cmdWeAlwaysDo )

            CheckboxMsg cbMsg ->
                let
                    ( cbModel, cbCmd ) =
                        Checkbox.Update cbMsg model.checkbox
                in
                    { model | checkbox = cbModel }
                        ! [ Cmd.map CheckboxMsg cbCmd
                          , cmdWeAlwaysDo
                          ]
module Checkbox.Update exposing (update)

import Checkbox.Model as Model
import Checkbox.Msg as Msg exposing (Msg(CheckMe))


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        CheckMe bool ->
            ( { model | checked = bool }, Cmd.none )

We can start to clean this up with Return

Return Update

module Update exposing (update)

import Return exposing (Return)
import Checkbox.Update as Checkbox
import Model
import Msg exposing (Msg(TitleChange, CheckboxMsg))


update : Msg -> Model -> Return Msg Cmd
update msg =
    let
        cmdWeAlwaysDo : Cmd Msg
        cmdWeAlwaysDo =
            -- insert a real command in a non-toy app
            Cmd.none
    in
        Return.singleton
            >> Return.command cmdWeAlwaysDo
            >> case msg of
                TitleChange title ->
                    Return.map <| \m -> { m | pageTitle = title }

                CheckboxMsg cbMsg ->
                    (\(model, cmd) ->
                        let
                            ( cbModel, cbCmd ) =
                                checkboxUpdate cbMsg model.checkbox
                        in
                            { model | checkbox = cbModel }
                                ! [ Cmd.map CheckboxMsg cbCmd
                                  , cmd
                                  ]
                    )
module Checkbox.Update exposing (update)

import Return exposing (Return)
import Checkbox.Model as Model
import Checkbox.Msg as Msg exposing (Msg(CheckMe))


update : Checkbox -> CheckboxModel -> Return Msg Model
update msg =
    Return.singleton
        >> case msg of
            CheckMe bool ->
                Return.map <| \m -> { m | checked = bool }

It’s a little hard to see in a toy example, but when you start building a large update function the ReturnAPI and it’s monadic properties give you a way to update little parts of your model/cmd tuple without dropping commands (because the Platform.Cmds are monoidal with Cmd.batch and Cmd.none)… If you look at the TitleChange, it’s super clean, and we forget about commands since we’re only updating a part of the model and we get to cut to the meat of our intentions. Also because we’re piping in the singleton return, we can use applicative style programming.

However, there’s a problem when we look at the toy component’s update. Things actually managed to get uglier because we need to run the component’s update to get our sub model/cmd tuple, update the model, and then make sure we don’t drop that cmdWeAlwaysDo along with mapping the command to the top-level message. So can we clean that up?


Lenses

Using the defined APIs of Monocle’s Lens and Optional, we can get to something (subjectively) cleaner.

But doesn’t the Elm community consider lenses an anti-pattern? Well, sort of. In the case where I was talking in Slack about lenses I got a lot of flack with comments like needing lenses is a code smell, but let’s take a second to talk about lenses. In the case of Lens we have a type signature that looks like this:

type alias Lens a b =
    { get : a -> b
    , set : b -> a -> a
    }

Lenses don’t have to be scary. What we have here is a definedAPI for getter and setter function—nothing more. So rather than creating a function to set a complicated field in your record floating in the abyss of your codebase, lenses give you a defined way to write and bundle up these functions—backed by math and laws—and they compose which provides you a lot of power. The biggest downside is that without derivation and macros you have to write your own lenses. (But it’s not hard!)

But I digress; fears aside, let’s get back to the update function by creating some lenses for our model using Monocle.Lens.Lens as a constructor:

Lensed Models

module Model exposing (..)

import Monocle.Lens exposing (Lens)
import Checkbox.Model as Checkbox


type alias Model =
    { pageTitle : String
    , checkbox : Checkbox.Model
    }


pageTitlel : Lens Model String
pageTitlel =
    Lens .pageTitle <| \p m -> { m | pageTitle = p }


checkboxl : Lens Model Checkbox.Model
checkboxl =
    Lens .checkbox <| \c m -> { m | checkbox = c }
module Checkbox.Model exposing (..)

import Monocle.Lens exposing (Lens)


type alias Model =
    { checked : Bool
    }


checkedl : Lens Model Bool
checkedl =
    Lens .checked <| \c m -> { m | checked = c }

This doesn’t look so bad. Lens as a constructor means we first pass in our get function, and then follow it up with our set function. What’s neat is as the app grows, we’ll have these getters and setters in place to reuse.

Let’s take a peek at Return.Optics.refractl’s source:

refractl : Lens pmod cmod -> (cmsg -> pmsg) -> (cmod -> Return cmsg cmod) -> ReturnF pmsg pmod
refractl lens mergeBack fx ( model, cmd ) =
    lens.get model
        |> fx
        |> Return.mapBoth mergeBack (flip lens.set model)
        |> Return.command cmd

Breaking down this type signature in relation to our toy:

  1. A Lens of top-level model and the component model (Model and Checkbox.Model.Model).
  2. A function to get our component’s Cmd to our top-level Cmd (Checkbox.Msg.Msg -> Msg).
  3. A component update function (Checkbox.Update.update).
  4. A top-level return (aka model/cmd tuple).
  5. Returning a top-level return. (Note: Return.ReturnF is an endomorphism which just allows us to combine 4 & 5 inputing and returning the same type).

And breaking down the function itself:

  1. Using our Lens, get our component from the model.
  2. Run our component’s update on it.
  3. Map on our component update’s Cmd with that function that that gets to our top-level Msg along with using the Lens to set component with the update’s changes to the component’s model.
  4. Make sure we append in all other commands we’ve built up.

This might seem a bit heavy, so let’s see it in practice with the CheckboxMsg (along with using our other lenses):

Refract Update

module Update exposing (update)

import Return exposing (Return)
import Return.Optics exposing (refractl)
import Checkbox.Update as Checkbox
import Model
import Msg exposing (Msg(TitleChange, CheckboxMsg))


update : Msg -> Model -> Return Msg Cmd
update msg =
    let
        cmdWeAlwaysDo : Cmd Msg
        cmdWeAlwaysDo =
            -- insert a real command in a non-toy app
            Cmd.none
    in
        Return.singleton
            >> Return.command cmdWeAlwaysDo
            >> case msg of
                TitleChange title ->
                    Return.map <| .set Model.pageTitlel title

                CheckboxMsg cbMsg ->
                    refractl Model.checkboxl CheckboxMsg <|
                        Checkbox.update cbMsg
module Checkbox.Update exposing (update)

import Checkbox.Model as Model
import Checkbox.Msg as Msg exposing (Msg(..))


update : Msg -> Model -> Return Msg Model
update msg =
    Return.singleton
        >> case msg of
            CheckMe bool ->
                Return.map <| .set Model.checkedl bool

I don’t know about you, but that really shrinks and cleans up updating that component. We went from an update function of 20 lines initially with the standard library, to 24 in the base Return, to 16 with refractl. That starts to add up fast in a large application and less lines of code means less lines of code to maintain. With this style, we can cut out component update into our main update and transform both models.

If you really don’t want to have to build the Lenses, using substitution you can use the constructor to build them inline (e.g.refractl (Lens .checkbox <| \c m -> { m | checkbox = c }) CheckBoxMsg <| Checkbox.update cbMsg), but that won’t be reusable.

Partnered with refractl is refracto that takes an Optional instead of a Lens.

Here’s the diff of the main update:

--- a.elm	2016-10-20 20:08:31.785515404 -0600
+++ b.elm	2016-10-20 20:09:01.896645780 -0600
@@ -1,11 +1,13 @@
 module Update exposing (update)

+import Return exposing (Return)
+import Return.Optics exposing (refractl)
 import Checkbox.Update as Checkbox
 import Model
 import Msg exposing (Msg(TitleChange, CheckboxMsg))


-update : Msg -> Model -> ( Model, Cmd Msg )
-update msg model =
+update : Msg -> Model -> Return Msg Cmd
+update msg =
     let
         cmdWeAlwaysDo : Cmd Msg
@@ -13,16 +15,12 @@
             -- insert a real command in a non-toy app
             Cmd.none
     in
-        case msg of
-            TitleChange title ->
-                ( { model | pageTitle = title }, cmdWeAlwaysDo )
+        Return.singleton
+            >> Return.command cmdWeAlwaysDo
+            >> case msg of
+                TitleChange title ->
+                    Return.map <| .set Model.pageTitlel title

-            CheckboxMsg cbMsg ->
-                let
-                    ( cbModel, cbCmd )
-                        Checkbox.Update cbMsg model.checkbox
-                in
-                    { model | checkbox = cbModel }
-                        ! [ Cmd.map CheckboxMsg cbCmd
-                          , cmdWeAlwaysDo
-                          ]
+                CheckboxMsg cbMsg ->
+                    refractl Model.checkboxl CheckboxMsg <|
+                        Checkbox.update cbMsg

Takeaway

Using the defined API for getters and setters of lenses, we can pass in a reusable, composable lens in as an argument. We know a lens is the appropriate data model for the argument because we require both the getter and the setter to merge these new values into our model. The way we partially apply the message into a component’s update, we can reuse the current standard for writing components with its own update meaning we can follow and borrow existing documentation and components using this interface. We can also easily add more and more components without dirtying our update function. Sometimes you need to hijack a component’s message at the top-level update to modify something else on the state and with these lenses already built, we’d have the ability to update nested components with much less boilerplate.