Some thoughts on Elm Development

Background

I have been playing with Elm for a few months now and have gotten to the point where I am starting to feel like I kn0w my way around the language and how to get things done. This has been a very nice feeling. However, one thing that was gnawing at me this whole time was how a component and an overall application should be sized and set up. While this might seem like a trivial detail, and is something that is easily overlooked, I have found this small detail can have profound impacts on the outcome of a project maintained by one or a few people over a long period or built and maintained by a larger team in a short period.

In looking through the Elm code examples, there didn’t seem to be an obvious example, so I looked to personal examples and compared to what seemed to be the opinion of others.

Step One: Look to what you have already done…

Having come from what now feels like an endless stream of angular applications, I will start there. Based on guides from the omnipresent John Papa in the Angular community, best practice seems to have arrived at dividing controllers, services and factories into the tiniest little blocks possible. On the UI front, directives are the way to go, but I have observed less discipline over their sizing than that of factories and controllers.

…quite honestly, it’s 2016. We have enormously productive tools, shortcuts, tricks and abstractions for the backend, why is anyone still writing HTML in the front-end?

Practically speaking, I have seen and experienced projects that take tiny services, factories and controllers, share them between directives and, as the code base evolves, create really subtle errors and edge cases that are tricky to track down. Also, directives have a nasty tendency to bloat quite a bit over time. However, the thing that frustrates me the most is: quite honestly, it’s 2016. We have enormously productive tools, shortcuts, tricks and abstractions for the backend, why is anyone still writing HTML on the front-end? The fact that anyone not only wants to continue using HTML, but actually thinks it would be an even better idea to randomly interleave Javascript into a soup of HTML tags just seems like salting an open wound to me.

To me, Typescript and Angular 2 just feel like a regression back to Java for the web; and that makes me really sad.

So that’s been my experience with Angular 1. Now I need to decide if I want to upgrade to 2 or jump ship. With the Angular folks aggressively pushing Typescript, OOP, and increasingly heavy overhead for creating new projects, it feels like the only fun things in Angular 1 have been sucked out. To me, Typescript and Angular 2 just feel like a regression back to Java for the web; and that makes me really sad.

Step Two: Consider alternatives…

So the two answers to Angular, as far as I can tell, are React and Elm. I looked into React and actually got really excited about this post about Functional Components with React stateless functions and Ramda. I read through it, liked what I saw a lot and thought that would be the model. Chopping everything down to microscopic bits of DOM with data flowing down through their respective components seemed like just the way I thought about applications. I was hooked.

Unfortunately, for all of the great ideas coming out of the React community, and there are many, there are two things that worry me:

  1. Tooling, libraries, best practices, APIs and even architectures have been churning every quarter for two years straight. While too much innovation is a classy problem to have, JS fatigue is real and worsens exponentially when working across distributed teams.
  2. React is as much a discipline as a framework/design/architecture. If anyone on the team isn’t disciplined, things start to get messy.

Thus bringing me to Elm…

Elm is wonderful. It handles the state issue by minimizing it as much as practical. It handles the “seriously HTML….it’s 2016” issue by generating it through functions on my behalf. It reduces my applications to testable functions. It reduces the risk of refactor-induced errors through a cheerful and helpful compiler. It eliminates type invariants of all persuasions. It even formats my files for me after each save. It has solved so many of my JS pains I could very well do a backflip for joy. Yet despite all that, something didn’t quite feel right.

Spiritual guidance on Brannan Street…

So I sought out professional help. I talked to the ever-delightful Richard Feldman at an Elm meetup recently in SF. I shared with him the dream of writing stateless components which could be used like “jigsaw pieces” to build up to bigger and bigger pieces and make a website. He kindly listened and provided feedback that completely changed my train of thought about how an application should be architected.

“Make the record as large as you need. When it makes you uncomfortable, it may be time to refactor.”

This was the opposite of how I interpreted everything I had read about Elm to date. When I read composable stateless components, I thought about a zillion three line functions slowly building up to a website. Turns out Elm can do that too (and quite well as you will see shortly). But that’s not actually the hard part. It’s the reducer and state management part that really matters. Passing state and messages around an Elm application is an area of lively discussion right now, but prevailing wisdom is that there’s no need to cut the application into smaller pieces than required. It is entirely natural to have a full blown page or major component of an Elm application in one elm file; model, update, view and all. Though you certainly can break each of those three pieces apart if you like.

So I took this advice while working through a lovely free Elm course this weekend with this advice in mind. It was fun, natural and reconnected me with one of the major benefits of functional programming I have experienced: functional programming gives you the tools and framework to build a beautiful API for yourself and others. Don’t believe me, just check out this update function (where the real action of an elm app is):

update : Msg -> Model -> Model
update msg model =
case msg of
-- On new input, update the text in input box
Input name ->
{ model | name = name }
        --On clicking the save button
Save ->
case (model.playerId) of
-- If the user is being edited, IE already has a
-- playerId
Just playerId ->
model
|> updatePlayerNames playerId
|> updateNameInPlays playerId
|> clearInputBox
|> clearPlayerIndex
|> deactivateEditMode
                -- If the user is new, IE doesn't yet have 
-- a playerId
Nothing ->
model
|> addNewPlayer
|> clearInputBox
|> clearPlayerIndex
|> deactivateEditMode
        -- On clicking the cancel button
Cancel ->
model
|> clearInputBox
|> clearPlayerIndex
|> deactivateEditMode
        -- When the edit mode is activated by "pencil button" click
Edit player ->
model
|> putNameInInputBox player.name
|> setPlayerIndex player.playerId
|> activateEditMode player.playerId
        -- When the two or three point buttons are clicked, 
-- update scores
Score playerId points ->
model
|> updatePlayerScore playerId points
|> appendPlays playerId points
        DeletePlay play ->
model
|> removePlay play
|> updatePlayerScore
play.playerId
(negate play.points)

If that’s not zen, I don’t know what is. That is code fit for human consumption. Code you could show a non-technical manager….or your Mom and they would totally understand it. “Oh yeah, when you edit a player, put their name in the input box, set player index and activate edit mode…” . Perfect.

How I learned to stop worrying and just make stuff again…

So my lesson from this was…to relax. Elm takes care of the things that have blown you and your team up when your application goes from a POC to production with a host of new features in between. Chopping your application into microscopic parts is the symptom, not the cure. You can reduce each of your DOM-rendering bits into ever smaller and more reusable components, such as this:

playerSection : Model -> Html Msg
playerSection model =
div
[]
[ playerListHeader
, playerListModel model
, pointTotal model
]
pointTotal : Model -> Html Msg
pointTotal model =
let
total =
List.map .points model.players
|> List.sum
in
footer []
[ div [] [ text "Total: " ]
, div [] [ text <| toString total ]
]
playerListHeader : Html Msg
playerListHeader =
header
[]
[ div [] [ text "Name" ]
, div [] [ text "Points" ]
]
playerListModel : Model -> Html Msg
playerListModel model =
model.players
|> List.sortBy .name
|> List.map (domPlayerMaker model)
|> ul []
domPlayerMaker : Model -> Player -> Html Msg
domPlayerMaker model player =
let
editChecker =
if
model.editMode
== True
&& model.editPlayerId
== player.playerId
then
class "edit"
else
class ""
in
li []
[ i
[ class "edit"
, onClick (Edit player)
]
[]
, div [ editChecker ]
[ text player.name ]
, pointButtonMaker 2 player
, pointButtonMaker 3 player
, div []
[ text <| toString player.points ]
]
pointButtonMaker : Int -> Player -> Html Msg
pointButtonMaker i player =
button
[ type' "button"
, onClick <| Score player.playerId i
]
[ text <| toString (i) ++ "pt" ]

…which is super cool and what made the React article so neat to begin with. However, microscopic DOM functions aren’t really a problem that required an answer.

The thing that makes my blood pressure go down is that state is minimized and in one place for each component start to finish and solely the responsibility of that code’s update function. All of the helper functions to build up to your declarative API are able to stay in the file or move to a helper function file; that’s your call. However, functional programming gives you all the tools you need to quickly build up your API and hopefully, others will find that API useful and able to be leveraged in their projects. Most importantly, because all of those functions are pure functions, you no longer need to worry about someone changing an object, a prototype or anything else in that code while you both are using it. Given an x, that function will always produce a y, regardless of who, when, where and in what context that function is called.

I realize this is a major push of React in stateless functional components, but I need to stress, that is a recommendation, not a requirement. Even the most disciplined teams cut corners and POCs have a very nasty habit of outliving their usefulness and scaling terrifyingly beyond their intended use. My experience in software development continually has reminded me of two adages:

  1. Any corners that can be cut, at some point will be.
  2. There’s nothing longer than temporary.

Elm removes all of the major stumbling points of JS frameworks I have seen with very little to give up. But as everything is still so new, best practices seem to be more of an oral tradition than anything committed to writing. So I would like to share the thoughts, experiences and conversations I have had with others so that this language and this community of Elm may flourish. Richard’s feedback was the guidance I needed to push forward and feel confident in what I was doing and made me look forward to building bigger and more interesting things. My sincere hope is something I have written or will write here will be equally useful to others and I will continue to write about my experiences with and learnings about Elm going forward.

Thanks to James Moore for putting the great free Elm tutorial online and Richard Feldman for listening to me and giving me the confidence to keep building cool things.

Code available for your review : here.