-
Notifications
You must be signed in to change notification settings - Fork 96
List comprehensions
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.
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.
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.
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.
A in Low..High ; Step
: A
goes from integers Low
to High
by step of Step
(default is 1), High
≥ Low
.
[A suchthat A in 1..5] % [1 2 3 4 5]
[A suchthat A in 1..5 ; 2] % [1 3 5]
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)
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]
A from Fct
: infinitely iterates over {Fct}
.
[A suchthat A from fun{$} 1 end] % 1|1|1|1|1|... (infinite list)
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)]
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]
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]
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])
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|_
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