No history yet

Erlang Sequential Fundamentals

The Immutable World

In many languages, a variable is like a box you can put things in, take them out, and replace them. In Erlang, a variable is more like a label you stick on a value. Once it's stuck, it stays there forever. This is called immutability, and it's not just a feature—it's the foundation of the language.

When you write X = 10, you aren't assigning 10 to X. You are binding the name X to the value 10. You cannot re-bind X to a new value within the same scope.

This might seem restrictive, but it eliminates a whole class of bugs related to state changing unexpectedly. It also makes concurrent programming, Erlang's main strength, much safer and easier to reason about. Since data never changes, you don't need to worry about one process altering data while another is trying to read it.

Pattern Matching Logic

Instead of if/else or switch statements, Erlang uses for control flow. It’s a way of asserting the shape of data and binding variables at the same time. Think of it less like an assignment and more like an equation.

When you write {A, B} = {hello, 42}, you are asserting that the right side is a tuple with two elements. If it is, A gets bound to hello and B gets bound to 42. If it isn't, the match fails and your program will crash. This "fail-fast" approach helps catch errors right where they happen.

This concept becomes incredibly powerful in function definitions. You can define multiple versions, or clauses, of the same function. Erlang will execute the first one that successfully matches the arguments.

handle_response({ok, Data}) ->
    io:format("Success! Data: ~p~n", [Data]);

handle_response({error, Reason}) ->
    io:format("Failure. Reason: ~p~n", [Reason]);

handle_response(_) ->
    io:format("Unknown response format.~n").

Here, handle_response has three clauses. If you call it with {ok, "some data"}, the first clause runs. If you call it with {error, not_found}, the second runs. The underscore _ is a wildcard that matches anything, acting as a catch-all for any other input.

Core Data Structures

Erlang's functional nature is reflected in its primary data structures.

Atom

noun

A literal, constant value where its name is its value. Atoms are used to represent non-numerical, fixed values.

Atoms are highly efficient. You've already seen them in tags like :ok and :error. They are guaranteed to be unique across the entire system.

Tuples are fixed-size containers for grouping related data. They are ideal when you know exactly how many items you need to store. We often use an atom as the first element to describe the tuple's purpose. {person, "Ada Lovelace", 1815}

Lists are variable-length collections. The most important thing to know about Erlang lists is their internal structure: every list is composed of a head (the first element) and a tail (a list of the remaining elements). This [Head | Tail] syntax is the key to processing lists with recursion. [1, 2, 3] is syntactic sugar for [1 | [2 | [3 | []]]] where [] is the empty list.

Guards and Comprehensions

Sometimes, pattern matching isn't quite enough. You might need to check if a value meets a certain condition. For this, you use guards. Guards are extra checks added to a function clause using the when keyword.

factorial(N) when N > 0 ->
    N * factorial(N - 1);
factorial(0) ->
    1.

In this example, the when N > 0 guard ensures the first clause only matches for positive integers. It adds a layer of logic that the pattern (N) alone cannot express. are limited to a small set of pure, side-effect-free functions to keep function matching predictable.

For transforming lists, Erlang provides list comprehensions, a concise syntax inspired by set-builder notation in mathematics.

They allow you to build a new list by defining the properties of its members based on another list.

Numbers = [1, 2, 3, 4, 5, 6].

% Create a new list with each number doubled
Doubled = [N * 2 || N <- Numbers].
% Result: [2, 4, 6, 8, 10, 12]

% Create a list of doubled even numbers
DoubledEvens = [N * 2 || N <- Numbers, N rem 2 == 0].
% Result: [4, 8, 12]

The || separates the expression that generates the new elements from the generator N <- Numbers, which iterates through the list. You can also add filters, like N rem 2 == 0, to include only certain elements.

Thinking Recursively

Without for or while loops, recursion is how you perform iteration in Erlang. A recursive function is one that calls itself, typically with a slightly smaller version of the problem, until it reaches a base case that stops the process.

Let's write a function to find the length of a list. We need two clauses:

  1. Base Case: The length of an empty list [] is 0.
  2. Recursive Step: The length of a non-empty list [Head | Tail] is 1 plus the length of its Tail.
len([]) -> 0;
len([_ | Tail]) -> 1 + len(Tail).

Each recursive call processes a smaller list, moving closer to the empty list [], which terminates the recursion. This pattern of matching on [] and [H|T] is fundamental to nearly all list processing in Erlang.

Mastering these sequential concepts—immutability, pattern matching, and recursion—is the key to unlocking Erlang. They form the bedrock upon which the language's famed concurrency features are built.