Development

How to Return Early From Elixir

Learn how you can implement the “Return Early” paradigm in Elixir pattern matching and guards to clarify the intent of your functions.

5 min
October 20, 2020
Raphael Spencer
Developer

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

Actionable UX audit kit

  • Guide with Checklist
  • UX Audit Template for Figma
  • UX Audit Report Template for Figma
  • Walkthrough Video
By filling out this form you agree to receive our super helpful design newsletter and announcements from the Headway design crew.

Create better products in just 10 minutes per week

Learn how to launch and grow products with less chaos.

See what our crew shares inside our private slack channels to stay on top of industry trends.

By filling out this form you agree to receive a super helpful weekly newsletter and announcements from the Headway crew.