If and Cond statements

The last language features being discussed for managing Code Flow are the if and cond statements. The reason the if is introduced at this late stage is to emphasize how less important it is in Elixir. The if statement works like you might expect from other languages. However, an if statement does not support pattern matching. By now you’ve seen that pattern matching is everywhere in Elixir! This similarity to other languages is a weakness for the if statement.

if statements

There are valid uses for if conditions like needing to test a single value. Typically, if you find yourself reaching for an if by default, realize there is probably a better way to write the code. In general, consider the writing of an if statement as a possible anti-pattern. With that said, let’s look at the syntax and talk about how it works.

if condition do
  # executed when condition is truthy
else
  # executed when condition is falsy
end

A falsy value is nil or false and a truthy value is everything else.

If you want to test that a value is false, you would write it as if value == false do... A better way is to use pattern matching.

Variables and leaking

By now you’ve seen that variables bound inside a clause don’t leak out. This is also true for the if. It is a common pattern in other languages to assign variables inside an if statement. Beneficially, the compiler recognizes this common mistake and points you in the right direction.

# This doesn't work!
do_thing = false

if some_condition do
  do_thing = true
end

#=> warning: variable "do_thing" is unused
#=> 
#=> Note variables defined inside case, cond, fn, if and similar do not leak. If you want to conditionally override an existing variable "do_thing", you will have to explicitly return the variable. For example:
#=> 
#=>     if some_condition? do
#=>       atom = :one
#=>     else
#=>       atom = :two
#=>     end
#=> 
#=> should be written as
#=> 
#=>     atom =
#=>       if some_condition? do
#=>         :one
#=>       else
#=>         :two
#=>       end

That’s a pretty helpful error message! This shows that the if statement returns a value and that’s how you get conditional values out of it.

Note that you aren’t able to bind multiple results like this. That is an extra signal that an if isn’t the preferred way to manage Code Flow.

Ternary statements

In languages like Javascript, there is a ternary operator. This doesn’t exist in Elixir. However, the if statement supports being expressed as a Keyword list putting it inline. Note that it doesn’t use the end.

result = if true, do: "true", else: "false"
result
#=> "true"

Nested if statements

When you see a nested if statement, that is definitely a code smell and an anti-pattern in Elixir. This is an opportunity to refactor into one or more of the following alternative structures:

  • multiple function clauses with pattern matching
  • a case statement
  • possibly a cond statement (up next)

unless statement

If usage of an if clause is a code smell of a possible anti-pattern then usage of the unless statement is even more so.

unless condition do
  # executed when condition is falsy
end

The unless clause exists, but is negative logic. This is harder to reason about and is best avoided.

No “else if” conditions

Note that the if statement does not have an elsif or built-in else if style clause. If you need that, then the cond statement is a better fit.

cond statements

The cond statement is well suited for replacing the if ... else if clauses you find in many other languages. However, because Elixir provides many more powerful features for controlling Code Flow, cond is used much less frequently.

It evaluates a series of conditions and stops at the first truthy one.

value = 123

cond do
  value > 200 ->
    "Greater than 200"
  value > 100 ->
    "Greater than 100"
  value > 50 ->
    "Greater than 100"
end
#=> "Greater than 100"

If nothing matches, it raises an exception.

value = 123

cond do
  value in 1..10 -> 
    "Between 1 an 10"
  value > 200 ->
    "Greater than 200"
  value < 100 ->
    "Less than 100"
end
#=> ** (CondClauseError) no cond clause evaluated to a truthy value

To ensure something matches like the last else clause of an if ... else if statement, use true as your condition.

value = 123

cond do
  value in 1..10 -> 
    "Between 1 an 10"
  value > 200 ->
    "Greater than 200"
  value < 100 ->
    "Less than 100"
  true ->
    "It was something else..."
end
#=> "It was something else..."

The final true provides a “default” or grand else clause to ensure you have a match.

Recap

  • “falsy” is when a condition is nil or false
  • “truthy” is anything not falsy
  • if statements do not support pattern matching
  • Nested if statements are an anti-pattern
  • You can do simple inline statements: if 1 == 1, do: "One!", else: "...uh..."
  • Don’t use unless statements. They are negative if statements.
  • cond handles the if ... else if statements you may be familiar with
  • cond statements can end with true to ensure a match

Comments are closed

This is a static version of the site. Comments are not available.

1 Comments

  1. Zac B on May 4, 2023 at 7:45 am

    I really don’t understand why cond exists. I can’t think up a scenario where I’d use it rather than a case statement. Any thoughts? It might be worth pointing out either its total obsolescence vis-a-vis ‘case’ or… if you have a potential constructive use, maybe pointing out that use.

Comments are closed on this static version of the site.