A common trend I see in Elixir projects is that modules tend to become large. Sometimes very large. This isn’t necessarily an issue, but it goes against some deep seated heuristics I have for building software.
Chord project started to get complex, I repeatedly found myself reaching for a pattern to keep my module size and complexity down, while still maintaining a friendly and approachable API.
Let’s dig into some examples.
What’s the problem?
Chord module is the heart of my
Chord project. Using
Chord you can generate guitar chord voicings, generate possible fingerings for a given voicing, and even calculate the distances between various chord voicings and fingerings.
It’s conceivable that lots of this functionality should live directly under the
Chord module. For example, we’d want to be able to ask for
Chord.fingerings/1, or even convert a chord into a chord chart with
The problem is that each of these pieces of functionality comes along with a non-trivial implementation. If we put our
to_string/1 functions in the
Chord module, their implementations would likely live in the
Chord module as well. In my mind, this would quickly turn
Chord into an unmaintainable mess.
There has to be a better way.
What’s the solution?
It turns out there is a better way. And, like most better ways, it turns out that the solution to our problem is obviously simple.
Let’s use our
voicings/1 function as an example. Rather than defining our
voicings/1 function and implementation within our
Chord module, we’ll create a
Chord.Voicing module and define our
voicings/1 function there.
defmodule Chord.Voicing do def voicings(notes, notes_in_chord \\ nil), do: ... end
Chord.Voicing module is entirely concerned with the act of generating chord voicings for a given set of notes.
However, we still want this functionality available through our
Chord module. To accomplish this, we simply need to write a
Chord.voicings/1 function that matches the signature of our
Chord.Voicing.voicings/1 module and passes the call straight through to our
defmodule Chord do def voicings(notes, notes_in_chord \\ nil), do: Chord.Voicing.voicings(notes, notes_in_chord) end
We can continue on with this pattern by creating a new module to implement each of our features:
Chord.Renderer. From there we can flesh our our
Chord module to wire our convenience functions up to their actual implementations:
defmodule Chord do def voicings(notes, notes_in_chord \\ nil), do: Chord.Voicing.voicings(notes, notes_in_chord) def to_string(chord, chord_name \\ nil), do: Chord.Renderer.to_string(chord, chord_name) def fingerings(chord), do: Chord.Fingering.fingerings(chord) end
Using the above pattern, we might make a mistake when passing the arguments from our first function head into the function in our implementation module (I’ve done it before).
Thankfully, Elixir gives us a way to prevent this mistake:
defmodule Chord do defdelegate voicings(notes, notes_in_chord \\ nil), to: Chord.Voicing defdelegate to_string(chord, chord_name \\ nil), to: Chord.Renderer defdelegate fingerings(chord), to: Chord.Fingering end
defdelegate macro, we can define the interface for each of our
fingerings/1 functions in our
Chord module, and point each of these function heads to their implementation module.
Elixir automatically wires each of the delegated functions together, preventing any mindless developer mistakes from creeping into our codebase.
What’s in a name?
In the previous example, the
Chord module is essentially acting as a “facade” that wraps and hides the complexity of our
I use the term “facade” loosely, and in real-life, I don’t use it at all. The “facade pattern”, and honestly all classic Gang of Four design patterns, carry baggage that I like to think I’ve let go of in my transition into the world of functional programming.
Another less weighty way to think of
Chord is as an “API module”. It’s sole purpose is to act as an “application programming interface” within our application.
What would you call this kind of pattern?