How Do Control Flows Work In Elixir?

Quick Primer on Conditional Constructs, Pattern Matching, Recursion and Comprehensions

Sep 1, 2024    m. Sep 14, 2024    #elixir  

Introduction

Elixir is a functional programming language designed for concurrency, fault tolerance, and distributed computing. Understanding control flows in Elixir is crucial for writing effective and idiomatic Elixir code. This primer will cover the key control flow constructs in Elixir, including conditionals, loops, and pattern matching.

1. Conditionals

Elixir provides several mechanisms for handling conditional logic:

1.1 if

The if macro is used for simple conditional expressions. It evaluates a condition and executes code based on whether the condition is true or false.

if condition do
  # Code executed if condition is true
else
  # Code executed if condition is false
end

Unlike other languages, there is no else if clause in Elixir.

For single line operations, we can also shorten this to :

if condition, do: "My single do line", else: "Else line"

1.2 unless

Similar to Ruby, the unless macro is the opposite of if. It executes code if the condition is false.

unless condition do
  # Code executed if condition is false
else
  # Code executed if condition is true
end

1.3 cond

cond allows for multiple conditions, similar to a switch statement in other languages. It evaluates each condition in order until it finds one that is true.

value = 3

cond do
  value < 0 -> IO.puts("Negative number")
  value == 0 -> IO.puts("Zero")
  value > 0 and value <= 10 -> IO.puts("Between 1 and 10")
  true -> IO.puts("Greater than 10")
end

2. Pattern Matching

Pattern matching is a central concept in Elixir. It allows for more sophisticated control flows, especially within functions and case expressions.

2.1 case

case is used for simpler pattern matching against values and is one of Elixir’s most powerful control flow tools. Each case clause can match a pattern and execute corresponding code.

number = 2

case number do
  1 -> IO.puts("One")
  2 -> IO.puts("Two")
  _ -> IO.puts("Other number")
end

2.2 Functions

Typically, more sophisticated pattern matching are used within functions, like so:

defmodule Example do
  def print_message({:ok, message}) do
    IO.puts("Success: #{message}!")
  end

  def print_message({:error, reason}) do
    IO.puts("Error: #{reason}")
  end
end

Example.print_message({:ok, "Operation completed"})
> "Success: Operation completed!"

In this example, the function print_message handles different tuples based on their structure and content.

3. Recursion

Recursion is a fundamental control flow mechanism in functional programming. Elixir encourages using recursion instead of loops for iteration.

3.1 Simple Recursion

Here’s an example of a simple recursion using a reduce algorthim approach:

defmodule Factorial do
  def compute(0), do: 1
  def compute(n) when n > 0 do
    n * compute(n - 1)
  end
end

IO.puts(Factorial.compute(5))  # Output: 120

3.2 Tail Recursion

Elixir optimizes tail-recursive functions to avoid growing the call stack. This means that tail-recursive functions can handle large inputs efficiently.

defmodule Factorial do
  def compute(n), do: compute(n, 1)

  defp compute(0, acc), do: acc
  defp compute(n, acc) when n > 0 do
    compute(n - 1, n * acc)
  end
end

IO.puts(Factorial.compute(5))  # Output: 120

In this above tail-recursive version, the recursion is in the tail position, allowing Elixir to optimize it.

4. Comprehensions

Comprehensions in Elixir provide a syntax for generating and transforming collections by iterating over enumerables. They can be thought of as a way to create lists or other data structures based on existing ones, applying transformations and filters in a declarative manner.

4.1 Basic Syntax

The basic syntax of a comprehension is:

for <generators>, <filters>, do: <expression>

4.2 Example: Generating a List of Squares

Here’s a simple example that generates a list of squares of numbers from 1 to 5:

squares = for n <- 1..5, do: n * n
IO.inspect(squares)  # Output: [1, 4, 9, 16, 25]

You can include filters and generators in comprehensions to create more complex data processing (this deserves its own article).

Conclusion

Control flow in Elixir revolves around pattern matching, recursion, and conditional constructs. These tools work together to manage program logic in a functional and expressive manner. By mastering these constructs, you can harness Elixir’s full potential and write clear, maintainable code.

Feel free to experiment with these concepts and see how they fit into your coding style!



Next: Working With Atoms in Elixir