Skip to content

List comprehensions

François Fonteyn edited this page May 14, 2014 · 2 revisions

How to use list comprehensions

Principle

List comprehensions allow users to easily declare all kinds of lists is a similar way as their mathematical declarations.
Here is an example: L = {2*a | a in A if a > 10} = {22, 28 30} if A = {1, 6, 9, 10, 11, 14, 15}
In this brief wiki, we will only explain the syntax to use with list comprehensions. For more information about list comprehensions, please consult externals sources such as http://en.wikipedia.org/wiki/List_comprehension.

Other resources

A tutorial can be found at https://github.com/francoisfonteyn/thesis_public/blob/master/Tutorial.pdf.
The complete grammar and syntax can be found at https://github.com/francoisfonteyn/thesis_public/blob/master/Thesis.pdf.

Get started

To declare a list with the squares of integers from 1 to 5, one can write

[A*A suchthat A in 1..5] % [1 4 9 16 25]

Any list comprehension is composed of at least two main parts: the output specification and the levels. In the above example, A*A is the output specification and the rest is one level (a level always starts with suchthat). So here, we put the expression A*A in the output list for each value that A takes, the integers from 1 to 5.
In the paragraphs to come, we'll start from the above example to introduce new concepts.

Generators

A generator is the structure that defines a range to iterate over. It always begins with in or from. There exist five different ways of defining range of value to iterate over.

Integer generator

A in Low..High ; Step : A goes from integers Low to High by step of Step (default is 1), HighLow.

[A suchthat A in 1..5]     % [1 2 3 4 5]
[A suchthat A in 1..5 ; 2] % [1 3 5]

C-style generator

A in Init ; Cond ; Next : A is initialized at Init, then Next while Cond evaluates to true. Cond is optional, default is true. Parenthesis can be used.

[A suchthat A in 1 ; A < 6 ; A+1]                        % [1 2 3 4 5]
[A suchthat A in ([5] ; {Length A} < 6 ; {Nth A 1}-1|A)] % [[5] [4 5] [3 4 5] [2 3 4 5] [1 2 3 4 5]]
[A suchthat A in 1 ; A+1]                                % 1|2|3|4|5|... (infinite list)

List generator

A in List : A goes trough all elements of List, respecting their order.

[A suchthat A in [1 2 3 4 5]]            % [1 2 3 4 5]
[A suchthat A in [1 2 [3] 4 5]]          % [1 2 [3] 4 5]
[A suchthat A in [A suchthat A in 1..5]] % [1 2 3 4 5]

Function generator

A from Fct : infinitely iterates over {Fct}.

[A suchthat A from fun{$} 1 end] % 1|1|1|1|1|... (infinite list)

Record generator

F:V in Record : F and V respectively take the feature and the value of every fields of the record Record, respecting the order defined by Arity. If Record contains nested records then they are traversed in depth-first mode. F:V in Record of Fct : similar to the above generator except that if V is a record, then if {Fct F V} evaluates to false, V is not considered as a record.

R = r(a:1 b:r(1 2))
fun {Fct F V} F \= b end
[V suchthat _:V in 1#2#3] % [1 2 3]
[V suchthat _:V in R]     % [1 1 2]
[F suchthat F:_ in R]     % [a 1 2]
[F#A suchthat F:A in r(a:1 b:r(1 2))] % [a#1 1#1 2#2] 
[F#A suchthat F:A in R of Fun]        % [a#1 b#r(1 2)]

Multi layer

List comprehensions offer the possibility to iterate over several generators simultaneously. Generators can be mixed at will.

[A#B suchthat A in [a b c] B in 1 ; B+1]                     % [a#1 b#2 c#3]
[A+B+C suchthat A in 1..5 _:B in 4#5#6 C from fun{$} 10 end] % [15 17 19]

Multi level

When one uses several layers, they are traversed simultaneously. But one could want nested iterations. For this, we use several levels. Recall that a level always starts with suchthat.

[A#B suchthat A in 1..2 suchthat B in [a b]]                       % [1#a 1#b 2#a 2#b]
[A+B+C suchthat A in 1..2 suchthat B in A..3 suchthat C in A+B..4] % [4 5 6 6 7 8 8]

In addition, each level can have a condition called a filter. It allows us to skip some values of the range.

[A#B suchthat A in 1..2 if A == 1 suchthat B in [a b]]                % [1#a 1#b]
[A#B suchthat A in 1..2 if A == 1 suchthat B in [a b] if B \= a]      % [1#b]
[A#B suchthat A in 1..2 suchthat B in [a b] if A == 1 andthen B \= a] % [1#b]

Multi output

Up to now, we only created one output list. It turns out that we can output several lists. When this is the case, then the lists are placed inside a record. One can decide to specify some features, otherwise an integer counter is used. The label is always '#' to allow comparison with tuple declare with their syntactic sugar.

[1:A if A < 3 suchthat A in 1..5] % ’#’(1:[1 2])
[A if A < 3 suchthat A in 1..5]   % [1 2]
[a b suchthat _ in 1..2]     % [a a]#[b b]
[a:a b:b suchthat _ in 1..2] % ’#’(a:[a a] b:[b b])
[a 1:b suchthat _ in 1..2]   % [b b]#[a a]

In addition, output-specific filters can be used. Thanks to the latters, the output lists can have different lengths.

[smallerEquals:A if A=< 3 bigger:A if A> 3 suchthat A in [3 4 2 8 5 7 6]] % ’#’(smallerEquals :[3 2] bigger :[4 8 5 7 6])

Laziness

In the same way that functions can be specified lazy, list comprehensions can also be lazy. More specifically each can be lazy.

declare
L = thread [A suchthat lazy A in 1..5] end
% L=_
{List.drop L 1 _}
% L = 1|_
{List.drop L 3 _}
% L = 1|2|3|_

As levels are specified as lazy, one must choose wisely which levels must be lazy.

declare
L = thread [A#B suchthat lazy A in 1..5 suchthat B in [a b]] end 
% L=_
{List.drop L 1 _}
% L = 1#a|1#b|_
{List.drop L 2 _}
% L = 1#a|1#b|_
{List.drop L 3 _ }
% L = 1#a|1#b|2#a|2#b|_

Body

The body of a list comprehensions is a statement that will be executed just before each time the comprehensions adds an element to each output - or when the comprehension tries appending, even if output conditions are false. In comparison with nested loops, the body is the statement inside the deepest loop of the nesting.

[A suchthat A in 1..5 do {Delay 1000}] % [1 2 3 4 5] after 5 seconds