Let's pretend we are writing some Ruby code, and we need a function that takes two optional arguments, returning "happy path" if either are present, otherwise returning "red herring" if both are nil:
-- CODE line-numbers language-rb --
<!--
def some_func(one, two)
if one || two do
puts one
puts two
return "happy path"
end
"red herring"
end
-->
### Now imagine a coworker who is troubleshooting this function for the first time:
Without the benefit of our description above, they might expect that "red herring" is actually the normal, expected return value of this method. The code technically works, but our intention could be clearer.
Using **early returns**, we can refactor this, so that the happy path is within the normal linear flow of the method, and the special case is the one surrounded by additional logic:
-- CODE line-numbers language-rb --
<!--
def some_func(one, two)
if one.nil? && two.nil?
return "red herring"
end
puts(one)
puts(two)
"happy path"
end
-->
The above function expresses our intent: i.e. "happy path" is the normal intended usage of this function, and "red herring" is some special case, when the conditions of the function are not met.
## There is no return in Elixir!!!
Let's try a naive "early return" implementation in Elixir:
-- CODE line-numbers language-elixir --
<!--
def some_func(one, two) do
if !one || !two do return "red herring" end
IO.puts(one)
IO.puts(two)
"happy path"
end
-->
At this point, the compiler yells at you, telling you that this is not valid Elixir.
## Pattern matching to the rescue
Instead of dealing with the execution conditions inside the function, we can handle them in entirely separate **function clauses**:
-- CODE line-numbers language-elixir --
<!--
def some_func(nil, _), do: "red herring" end
def some_func(_, nil), do: "red herring" end
def some_func(one, two) do
IO.puts(one)
IO.puts(two)
"happy path"
end
-->
## Guards for the win
The above code still contains duplicate definitions when either argument is nil. We can combine these definitions into one using a **guard clause**, which allows more complex checks to be performed, on top of pattern matching:
-- CODE line-numbers language-elixir --
<!--
def some_func(one, two) when is_nil(one) or is_nil(two), do: "red herring" end
def some_func(one, two) do
IO.puts(one)
IO.puts(two)
"happy path"
end
-->
The code snippet above expresses the "happy path" of the function, as well as what specific execution conditions we are pulling out, along with action taken if they match.
## Other paradigms and techniques
When thinking about the different types of values our functions must accept and return, and communicating this intent to other developers, Elixir is full of options:
- use function clauses and guards, as discussed in this article
- use `case`, `cond`,or `with`, and return the different values inside of a match clause
- add type annotations to your functions, and enforce them with the Dialyzer static analysis tool
#### The key question I would ask myself in this situation is:
"What do I want this code to communicate to other developers?"
If I want to communicate that _"supplying two nil values is part of the normal execution flow of this function"_, then I would use `case` or `cond` and return the values inside of a pattern match clause.
On the other hand, if I want to communicate that _"supplying two nil arguments is outside the domain of this function"_, then a function clauses with guards might be just the ticket!
## Elixir resources