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. Evan Czaplicki, Q&A Panel

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:

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

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 Return API and it’s monadic properties give you a way to update little parts of your model/cmd tuple without dropping commands (because the Platform.Cmd s 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 API s 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 defined API 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:

A Lens of top-level model and the component model ( Model and Checkbox.Model.Model ). A function to get our component’s Cmd to our top-level Cmd ( Checkbox.Msg.Msg -> Msg ). A component update function ( Checkbox.Update.update ). A top-level return ( aka model/cmd tuple). 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:

Using our Lens, get our component from the model. Run our component’s update on it. 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. 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):

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 Lens es, 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