Elm Refactor Library: Extract Method


In this series, I describe the refactoring strategies I have learned using the Elm programming language. Elm makes refactoring more bug-proof than other language contexts, but many strategies from object-oriented contexts don’t apply in a functional context like Elm. In order to refactor productively, one must be capable of recognizing problem contexts and applying revisions. The intent of this series is to raise aware of the contexts I have discovered using Elm.

Code Smell: Inline Lambdas

Consider the following code:

This code is difficult to read at a glance, and poor readability strongly indicates a code smell. Readability is to be preferred to cleverness, and in many cases, even a certain of amount of performance can be sacrificed if we end up with code that other engineers can easily read. The value to a business is that such code ends up being easier to change, and in a fast-paced context like the web, adaptability is crucial.

A large number of inline lambdas smells in a single Elm function. This smell has a dual character to its odor: the “big function” smell from object-oriented languages on the one hand, and the “callback hell” smell of Vanilla JavaScript on the other. As with both of those smells, the refactoring strategy has a similar character—break up the Frankenstein creation into more reasonable pieces.

However, upon looking at the above example, Elm seems to throw up an insurmountable wall. How should the engineer keep all necessary variables in scope and preserve the behavior?

The goal of the make function above is to create a two-dimensional grid of cells, stored in a Dict where the keys are of type (Int, Int) (type aliased to Board). It may, at first, seem impossible to break up the nested folds and still end up with our two-dimensional grid of cell states.

However, this is quite possible. We need only turn to the functional paradigm of partial application for assistance.

Refactor: Extract Method

By extracting the double-nested lambdas into functions and giving them explicit names, we end up with much more readable code.

Partial application is what makes this pattern possible. When I was a newcomer to Elm, I assumed that in order to keep variables like rowIndex in scope, I had to nest lambdas in this way. However, Elm gives us the power of partial application. Notice line 19. (insertDefaultCellAt rowIndex) yields a function whose signature is Int -> Board -> Board, which is exactly the type we want to return from insertRowOfDefaultCells. We are thus able to reference rowIndex in insertDefaultCellAt without having access to it in the traditional object-oriented sense.

I find that often a good way to think through a problem from the general down to the specific is, in fact, to write out a chain of nested lambdas. However, such code isn’t something I would want to foist on my future self in three to six months, let alone another human being. Nowadays, I make sure to refactor such initial passes by extracting my lambda expressions and giving them descriptive, readable names.