Image for post
Image for post
Make the part, then join them

Breaking Up Update in Elm

I’ve been working for some time on an Elm app whose update function has grown to over two thousand lines of code, code that processes over eighty messages. Surprisingly, this works out rather well, so long as you keep the code organized, with signposts placed as comments at strategic locations. This makes it is easy to move around in the file and to find the parts you want to work on. The structure is not so different from organizing the code into a set of files, where file names play the role of the signposts.

Despite this, I had been thinking of breaking up my huge update function. Then, this morning, over a steaming cup of fresh coffee, I read the first part of Sonnym’s post Types in Elm: Decomposition and Ad Hoc Polymorphism, in which he describes his method for doing just this. I decided to try his method, with the goal of adding a new component, Bozo, to the app I had been working on. Bozo would have a model with states Start, Up, and Down. It would have its own update function, which would act on the messages GoUp and GoDown. It would also have its own view functions — one to display the state, and two buttons labeled “Up” and “Down” to change the state. The code would be organized in three modules, Bozo.Model, Bozo.Update, and Bozo.View. Finally, integration of Bozo and the main app should be accomplished through a very small interface — just a few lines of code.

To carry out the plan, we first made the Bozo.Model module, listed below. Notice that it has no imports, hence no dependencies of any kind.

The next step was to construct the view module (see below). Notice that it depends on Mathew Griffith’s elm-ui library for style, and also on Bozo.Model. There are no other dependencies. In particular, Bozo.View does not depend on the main app.

To complete the definition of Bozo, we add the update function:

This module has one internal and one external dependency: Bozo.Model and Model, respectively. Note that the only dependency of the entire Bozo.* packet is on the Model module of the main app.

So far, so good. The main app, with Bozo.* added, compiles. It is now time to connect the new code to the main app. This will require small changes to the Model, View, and Update modules of the main app. For the Model, there are four additions, the first of which is an import:

Next, we add a bozo field to the main model:

This requires an addition to the init code:

Finally, we add Bozo BozoMsg to Msg:

To connect the two update functions, we add two lines to the main update module. The first is the line import Bozo.Update. The second is as below:

The footer of the app was a natural place for connecting the view of the main app to Bozo’s views:

Notice that we had to apply Element.map Bozo to the button views to ensure that they have the correct type.

The results of the experiment (which required an additional cup of coffee) were excellent. The state of the Bozo “component” showed up in the footer, along with the two buttons. Clicking the buttons modified the state accordingly. The design goals were met: a set of modules Bozo.* with a very small dependency (one line) on the main app, and a relatively small number of lines added to the main app needed to perform a “mind meld” of the two. The process of constructing the Browser.* packet and integrating it with the main app was straightforward and hassle-free, given structural plan laid out in Sonnym’s post. With a validated strategy for breaking up my huge update function, I feel that I can now proceed with confidence.

Addendum

Several members of the Elm Slack commented on the fact that Bozo.* is not completely isolated from the main app. (Thanks @augustin82 !!) One can do better as follows. First, in Bozo.Update, delete the line import Model exposing(Model, Msg). Disconnect achieved! Second, in the main Update module, add the line

just as done in the main Model. Third, still working in Update, make the definition

Fourth, change the Bozo bozoMsg clause of the main update function to read as follows:

With these changes, one achieves complete isolation of Bozoin the sense that it imports nothing from the main app. Of course the main app must import Bozo.* if it is to make use of it.

I thank members of the Elm Slack for their insightful comments on this post.

Written by

jxxcarlson on elm slack, http://jxxcarlson.github.io

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store