What We Do

Company

Resources

Events

Blog

Free Consultation

ahoy@headway.io

(920) 309 - 5605

5 min
How to Return Early From Elixir
Subscribe

How to Return Early From Elixir

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

Asking Better Questions About Your Product

Download our free guide to begin implementing feedback loops in your organization.

By filling out this form, you agree to receive marketing emails from Headway.

Scaling products and teams is hard.

In this free video series, learn how the best startup growth teams overcome common challenges and make impact.

Scaling products and teams is hard.

In this free video series, learn how the best startup growth teams overcome common challenges and make impact.

You don’t need developers to launch your startup

In this free video series, learn proven tactics that will impact real business growth.

By filling out this form, you agree to receive marketing emails from Headway.

Make better decisions for your product

Dive deeper into the MoSCoW process to be more effective with your team.

By filling out this form, you agree to receive marketing emails from Headway.

A mindset for startup growth

In this free video series, learn the common mistakes we see and give yourself a greater chance for success.

By filling out this form, you agree to receive marketing emails from Headway.

The ultimate UX audit kit

Everything you need for a killer DIY audit on your product.

  • UX Audit Guide with Checklist
  • UX Audit Template for Figma
  • UX Audit Report Template for Figma

Enjoyed this post?

Other related posts

See all the ways we can help you grow through design, development, marketing, and more.

View All

Listen and learn from anywhere

Listen and learn from anywhere

Listen and learn from anywhere

The Manifest

Level up your skills and develop a startup mindset.
Stay up to date with the latest content from the Headway team.