3 Lists and Simple Data

3.3 Comprehensions

In the section on lists, we gave an example of building a list using the map function:

> (lists:map
    (lambda (x)
      (trunc
        (math:pow 3 x)))
    '(0 1 2 3))
(1 3 9 27)
>

This sort of approach should be familiar to many programmers, even those who aren't adepts at functional programming. This is a well-known pattern. Erlang offers another pattern, though: comprehensions.

LFE supports Erlang comprehensions via two macros: lc for list comprehensions and bc for bitstring comprehensions.

3.3.1 List Comprehensions

Let's take a look at an example and then discuss it. Here's a list comprehension version of our map/lambda combo above:

> (list-comp
    ((<- x '(0 1 2 3)))
    (trunc
      (math:pow 3 x)))
(1 3 9 27)
>

This can be translated to English as "the list of integers whose values are x raised to the power of 3 where x is taken from the list we provided, iterated in order from first to last."

In Erlang, this would have looked like the following:

1> [trunc(math:pow(X,3)) || X <- [0,1,2,3]].
[0,1,8,27]
2>

As we can see, the LFE syntax is not as concise as the native Erlang syntax, though it is pretty close. Our original example is 62 characters long; the LFE list comprehension is 49 characters long; the Erlang version is 41 characters.

To a Lisper, the original is probably much more legible. However, in Erlang these is no question that the list comprehensions are shorter and easier to read than using anonymous functions.

The story changes somewhat one we need iterate over several lists, such as when calculating permutations. Using the functional approach, we end up with nested anonymous functions (or nested function calls), and this gets cumbersome with dimensions greated than one.

Here's a list comprehensions that finds all the combinations of two sets of numbers:

(list-comp ((<- x (lists:seq 0 4))
            (<- y (lists:seq 0 5)))
           (list x y))

It's just as easy if we want to add more dimensions:

(list-comp ((<- l (lists:seq 0 3))
            (<- m (lists:seq 0 4))
            (<- n (lists:seq 0 5))
            (<- o (lists:seq 0 6)))
           (list l m n o))

3.3.1 Bitstring Comprehensions

For binary data, we have something similar to the list comprehension. Here's what a bitstring comprehension looks like (adapted from the example given by Francesco Cesarini and Simon Thompson in their book, "Erlang Programming"):

> (binary-comp
    ((<= (x (size 1)) (binary (42 (size 6)))))
    ((bnot x) (size 1)))
#B((21 (size 6)))
>

Note that the bitstring comprehension uses the <= operator (not to be confused with the =< equality operator!) instead of the <- that list comprehensions use.

Here's the Erlang version:

2> << <<bnot(X):1>> || <<X:1>> <= <<42:6>> >>.
<<21:6>>
3>

As we might expect, the native Erlang version is much more concise. Fortunately, though, in LFE we don't need to enter the whole binary form, just the bit syntax portion. In other words, instead of writing this:

(binary (x (size 1)))

and this:

(binary ((bnot x) (size 1)))

we only had to write this:

(x (size 1))

and this:

((bnot x) (size 1))