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.
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
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.