My Favorite Functions and Patterns: Function Clauses in Elixir

Elixir is far too generous. I realize how cossetted I am when I go to use function clauses in JavaScript. Because you can’t do that.

I personally detest if statements, along with case — perhaps to a fault. Function clauses redeem me.

Why?

Because there is lawtable by which I abide:

Let the complexity of our calculations accord with the data upon which they operate.

And what might I mean?

I mean that, ideally, our code should not introduce additional complexity. If, for example, our data stores a value that will never be nil, our code need not account for a case in which the value is nil.

Hence, I love function clauses: more effectively than other control structures, they delegate handling by virtue of data values.

Example Using Elixir Function Clause

Let’s assume our web-app has a table of results to which a diversity of filters can be applied. We want to enable users to delimit results by age, email address, and username:

# In this case, we _will_ account for `nil`, as it represents a common scenario
# when assembling search queries.
defp filter({_key, nil}, %Ecto.Query{} = query), do: query
defp filter({:age, age}, %Ecto.Query{} = query), do: query |> ...
defp filter({:email, email}, %Ecto.Query{} = query), do: query |> ...
defp filter({:username, username}, %Ecto.Query{} = query), do: query |> ...

@spec apply_filters(keyword({atom(), String.t()}), Ecto.Query.t()) :: Ecto.Query.t()
def apply_filters(filters, %Ecto.Query{} = query) do
  Enum.reduce(filters, query, &filter/2)
end

Which can be used like:

iex> apply_filters([age: 18, email: "myname@example.com"], query)

Further Reading

Read the Full Series

A Compilation of Jonathan’s Favorite Functions and Patterns in Functional Programming.

See all the functions.

More Posts by Jonathan Walters: