For comprehension in Elixir
The for comprehension is a powerful language construct in Elixir that allows developers to express complex iterations and conditional logic in a declarative way. It is quite similar to the for loop of other programming languages, but with more functionality and more concise syntax. It is used to filter and transform collections of data and can be used as an alternative to recursive functions or nested Enum
functions. Personally, I find it very interesting and readable given its declarative nature.
At a high level, a for comprehension consists of a sequence of generators, filters, and collectors. Each generator represents a sequence of values, and the comprehension iterates over these sequences in a nested way. Filters allow you to specify conditions that must be met for a value to be included in the final result. If the condition of the filter evaluates to true, the value is passed to the collector. Lastly, collectors specify how the values should be combined and transformed.
Here is the basic pipeline that shows that a value gets generated from the generator, filtered by the filter and, eventually, collected by the collector.
Here is a basic example of a for comprehension that filters a list of numbers to only include even numbers:
In this example, the generator is n <- numbers
, which specifies that the for comprehension will iterate over the numbers list. The filter is n rem 2 == 0
, which specifies that the comprehension will only include elements n
for which the remainder when n
is divided by 2 is equal to 0. The do:
keyword specifies the transformation that should be applied to each element that satisfies the filter. In this case, the transformation is the identity function, so the element is returned unchanged.
It’s also possible to specify multiple generators in a single for comprehension. In this case, the comprehension will iterate over the Cartesian product of the generators. In the following example we use two generators:
Filters can also be specified for each generator. For example:
In addition to filtering and transforming elements, for comprehensions can also be used to perform side effects. For example, the following code snippet will print each element of the numbers list:
It’s also possible to bind the result of a for comprehension to a variable using the into
keyword. For example:
Passing uniq: true
can guarantee that the results are only added to the collection if they were not returned before. Here is an example of the uniq keyword:
The :reduce
option, instead, allows us to implement a reduce step. It takes an accumulator and the do
block that uses ->
clauses:
When the :reduce
keyword is given , its value is used as the initial accumulator and the do
block must be changed to use ->
clauses, where the left side of ->
receives the accumulated value of the previous iteration and the expression on the right side must return the new accumulator value. Once there are no more elements, the final accumulated value is returned. If there are no elements at all, the initial accumulator value is returned.
In conclusion, for comprehensions are a convenient and expressive way to iterate over and transform collections of data in Elixir. They can be used as an alternative to recursive functions or nested Enum functions, and offer a clear and concise syntax for specifying complex iterations and conditional logic.