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.

As my 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?

The 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.voicings/1, or Chord.fingerings/1, or even convert a chord into a chord chart with Chord.to_string/1.

The problem is that each of these pieces of functionality comes along with a non-trivial implementation. If we put our voicings/1, fingerings/1, and 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

Now our 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 Chord.Voicing module:


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

Beautiful.

Enter Delegates

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

Using the defdelegate macro, we can define the interface for each of our voicings/2, to_string/2, and 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 Chord.Voicing, Chord.Fingering, and Chord.Renderer modules.

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?