Reading the Elixir source code to learn how if blocks work
Tags: elixir, functional, devjournal, todayilearned
Published: 2022-01-12 12:18:34

As I’ve been working with Elixir, I’ve been wondering about the ways that macros can be leveraged. So once I realized that `if` statements are implemented as macros, I figured that reading the source code would be a good way to learn from the creators of the language.

## Finding the code

In the Elixir documentation, every function has a `</>` button that links to its source code. If you follow that button in [the documentation for `if/2`](https://hexdocs.pm/elixir/Kernel.html#if/2), you’ll find the code that makes `if` statements work. I’ve pasted it below:

```elixir
defmacro if(condition, clauses) do
  build_if(condition, clauses)
end

defp build_if(condition, do: do_clause) do
  build_if(condition, do: do_clause, else: nil)
end

defp build_if(condition, do: do_clause, else: else_clause) do
  optimize_boolean(
    quote do
      case unquote(condition) do
        x when :"Elixir.Kernel".in(x, [false, nil]) -> unquote(else_clause)
        _ -> unquote(do_clause)
      end
    end
  )
end

defp build_if(_condition, _arguments) do
  raise ArgumentError,
        "invalid or duplicate keys for if, only \"do\" and an optional \"else\" are permitted"
end
```

Let’s break this down...

## Defining the macro

```elixir
defmacro if(condition, clauses) do
  build_if(condition, clauses)
end
```

It starts out by defining a macro for `if/2` that accepts the parameters `condition` and `clauses`. While the documentation says “this macro expects the first argument to be a condition and the second argument to be a keyword list” there is no programmatic enforcement of that at this level.

Within the macro’s body, all we have is a call to `build_if/2` that passes along the unmodified parameters of `if/2`.

## `build_if/2`

```elixir
defp build_if(condition, do: do_clause) do
  build_if(condition, do: do_clause, else: nil)
end

defp build_if(_condition, _arguments) do
  raise ArgumentError,
        "invalid or duplicate keys for if, only \"do\" and an optional \"else\" are permitted"
end
```

This is a private function called by the macro and exists in two forms.

The first instance of this function has pattern matching for the `clauses` argument to only accept a value that starts with `do`. If the pattern matching succeeds, a call is made to `build_if/3` with a value of `nil` for the `else` clause.

The second instance does not use pattern matching, but instead has an underscore at the beginning of the parameter names to indicate that their values will not be used. This function raises an `ArgumentError` to show that the requisite pattern matching for the `do` clause has not been met.

## `build_if/3`

```elixir
defp build_if(condition, do: do_clause, else: else_clause) do
  optimize_boolean(
    quote do
      case unquote(condition) do
        x when :"Elixir.Kernel".in(x, [false, nil]) -> unquote(else_clause)
        _ -> unquote(do_clause)
      end
    end
  )
end
```

*Note: I’m not quite sure what the purpose of `optimize_boolean` is, but the source code for it is [here](https://github.com/elixir-lang/elixir/blob/33f9d04851a2664aeae5f5f78eeef9e5b4c38030/lib/elixir/lib/kernel.ex#L1761) if you want to take a look at it. If you know what it’s purpose is, let me know!*

This function is where the actual logic of the `if` is carried out.

It makes use of `quote/2` and `unquote/1`. Quoting returns the internal representation of an expression, and unquoting gets an expression from an internal representation. You can read more about them [here](https://elixir-lang.org/getting-started/meta/quote-and-unquote.html#quoting).

First, we enter a call to `quote/2` and start a `case` statement that unquotes the `condition` parameter in order to evaluate it.

Let’s break down the first pattern in the `case`:

1. `:"Elixir.Kernel".in` makes a call to `in/2` which is actually just a macro for `Enum.member?/2`
2. `Enum.member?/2` checks whether an element is within an enumerable
3. So, `:"Elixir.Kernel".in(x, [false, nil])` checks whether `x` (the condition) is equal to either `false` or `nil`

From there, we see that `condition` evaluating to either `false` or `nil` will result in `else_clause` being returned.

The default case returns the `do_clause`, which is what we would expect to be returned if the condition was truthy.

## Conclusions

We can draw a few interesting observations from reading through this code. The first is that truthiness in Elixir’s `if` statements is defined as being neither `false` nor `nil`. This means that `if/2` can be a simple way to test whether a variable has neither of those values.

The other lesson is that macros can work well for implementing control flow. By passing the `do` and `else` clauses around as keyword lists, it prevents that code from being run unless we decide it should run.

## More Content

If you liked this, you might also like some of my other posts. If you want to be notified of my new posts, [follow me on Dev](https://dev.to/brewinstallbuzzwords) or subscribe to my [brief monthly newsletter](https://mailchi.mp/89dc077154c7/brewinstallbuzzwords).

* [What's the best questions you've been asked in a job interview?](https://dev.to/brewinstallbuzzwords/whats-the-best-question-youve-been-asked-in-a-job-interview-4a1j)
* [What was the first program you wrote?](https://dev.to/brewinstallbuzzwords/what-was-the-first-program-you-wrote-1gjp)
* [The weird quirk of JavaScript arrays (that you should never use)](https://dev.to/brewinstallbuzzwords/the-weird-quirk-of-javascript-arrays-that-you-should-never-use-4l3b)
* [Does Elixir have for loops?](https://www.brewinstallbuzzwords.com/posts/does-elixir-have-for-loops/)
* [Learn Elixir with me!](https://www.brewinstallbuzzwords.com/posts/learn-elixir-with-me/)
* [Project Tours: Bread Ratio Calculator](https://www.brewinstallbuzzwords.com/posts/bread-ratio-calculator/)
* [Changing Emoji Skin Tones Programmatically](https://www.brewinstallbuzzwords.com/posts/emoji-skin-tone/)
* [I made my first svg animation!](https://dev.to/brewinstallbuzzwords/i-made-my-first-svg-animation-29j3)
* [5 tips for publishing your first npm package](https://www.brewinstallbuzzwords.com/posts/tips-for-first-npm-package/)
* [4 Hugo Beginner Mistakes](https://www.brewinstallbuzzwords.com/posts/hugo-beginner-mistakes/)