Abstraction in Elm: Utility-Based Modularization
Table of Contents
- Introduction
- One Expression to Rule Them All
- Introduce Comments
- Introduce Local Functions
- Utility-Based Modularization
- Domain-Based Modularization
- Wrappers and Generics for Everything
- Conclusion
Code
In the last section, we saw a lot of benefit from a little bit of abstraction. We introduced a handful of functions, and all of a sudden a bunch of relationships came into focus. Let’s take abstraction a step further and see what happens. Let’s introduce modules.
Discussion
In the previous section, we grouped together chunks of intermediate logic and put each one into its own function. In this section, we’ve grouped together functions and put them into separate modules. This act of putting like things together and separating them from unrelated parts of the system is called partitioning.
What do these new partitions get us?
Some would argue that this particular partitioning is superficial. And, for a chunk of code that was originally only forty lines, I would agree that this indeed seems to be more abstraction than is strictly necessary for a code sample of this size. However, most programs do not remain a mere forty lines for very long.
File-level partitioning abstractions (modules in Elm, classes in object-oriented languages) serve another important purpose that often goes unremarked, both in object-oriented and functional paradigms. Such abstractions act as “behavior attractors,” as Corey Haines describes in his book Understanding the Four Rules of Simple Design. The idea is that a well-considered partition acts as a magnet for new behaviors that “belong” conceptually inside its boundaries.
In the example above, say I need a new function that converts any given RunStatus
to a display string. In the early examples, I would just throw that function in any old where, and maybe I wouldn’t even bother to wrap that behavior in a function. With my partitions in place, it is clear to anyone using this system that such behavior belongs in the RunStatus
module.
Building a team attitude of always searching out the right partitions for the job makes everyone’s lives easier, because the placement and integration of new functionality will be both obvious and simple. In the extremely concrete systems we saw at the very beginning of this article, a new feature could be placed trivially, but correct integration of that behavior would be significantly more cumbersome.
The downside of this level of abstraction should be obvious. In our previous section, we introduced a level of indirection that caused us to go searching the file for other functions. In this section, we introduced a new level of indirection. Now, when seeing an unfamiliar symbol, we may additionally have to go searching our project for other files.
We can now see a pattern emerging. Every step we take in using structure to clarify why our code is the way it is, comes at the cost of another form of clarity: that of what the application specifically does.
Analysis
Pros
- Logical groups of functions, called modules in Elm, add increased clarity as to why our code is the way it is, and what its intended behavior is.
- Modules act as behavior attractors, making it easier to fit new features into the system (assuming the partitions are well-drawn).
- Relationships between the various parts of the code are clearer than ever, as we now have two tiers of partitions (functions and modules) that distinguish code as being more or less related to other parts of the system.
Cons
- The ease of discovery of what our code does has taken another hit. It is no longer possible to find out everything a program does by looking at a single file. When encountering an unknown symbol, the module it belongs to must be identified, found, opened, and scanned before we can know how it works.
Continue to Abstraction in Elm: Domain-Based Modularization.