1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/*!
Adds comprehensions to Rust. This is achieved through a functional macro,
[`rcomp!`], that does all the heavy lifting for you.
# Basic Usage
The core idea is simple: provide an easy and concise way to flatten, filter,
map, and collect iterators. For a full breakdown of the syntax, see
the docs for the [`rcomp!`] macro. For now, consider this simple example:
```rust
# use rustcomp::rcomp;
let v = rcomp![Vec<_>; for x in 0..10 => x];
let it = (0..10).collect::<Vec<_>>(); // all examples show an equivalent iterator
assert_eq!(v, it);
```
This will make a `Vec<i32>` with the numbers 0 through 9... not very useful,
is it? Let's add a guard to filter out the odd numbers:
```rust
# use rustcomp::rcomp;
let v = rcomp![Vec<_>; for x in 0..10 => x, if x % 2 == 0];
let it = (0..10).filter(|x| x % 2 == 0).collect::<Vec<_>>();
assert_eq!(v, it);
```
Now we're getting somewhere! You can also map the values, so let's double
them for fun:
```rust
# use rustcomp::rcomp;
let v = rcomp![Vec<_>; for x in 0..10 => x * 2, if x % 2 == 0];
let it = (0..10)
.filter(|x| x % 2 == 0)
.map(|x| x * 2)
.collect::<Vec<_>>();
assert_eq!(v, it);
```
Notice how the `map` call comes _after_ the `filter` call in the iterator example.
This is also how the comprehension works: the guard applies to the _input_ value,
not the output value.
Speaking of iterators, if you don't want to collect the results into a container,
you can get the iterator directly by omitting the collection type:
```rust
# use rustcomp::rcomp;
// now we have to collect the iterator ourselves
let v = rcomp![for x in 0..10 => x].collect::<Vec<_>>();
// equivalent to:
let vv = rcomp![Vec<_>; for x in 0..10 => x];
# let it = (0..10)
# .collect::<Vec<_>>();
# assert_eq!(v, vv);
# assert_eq!(v, it);
```
# Destructuring
Comprehensions also support destructuring. For example, tuples:
```rust
# use rustcomp::rcomp;
let pairs = vec![(1, 2), (3, 4), (5, 6)];
let v = rcomp![Vec<_>; for (x, y) in &pairs => x + y];
let it = pairs.into_iter().map(|(x, y)| x + y).collect::<Vec<_>>();
assert_eq!(v, it);
```
or structs:
```rust
# use rustcomp::rcomp;
struct Point {
x: i32,
y: i32,
}
#
# impl Point {
# fn new(x: i32, y: i32) -> Self {
# Self { x, y }
# }
# }
let points = vec![Point::new(1, 2), Point::new(3, 4), Point::new(5, 6)];
let v = rcomp![Vec<_>; for Point { x, y } in &points => x + y];
let it = points.into_iter().map(|Point { x, y }| x + y).collect::<Vec<_>>();
assert_eq!(v, it);
```
# Flattening
Flattening nested iterators is supported up to the recursion
limit by chaining the `for-in` clauses:
```rust
# use rustcomp::rcomp;
let matrix = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
let v = rcomp![Vec<_>; for row in &matrix, col in row => *col * 2, if *col % 2 == 0];
// nested loops are a much nicer example than iterators here
let mut it = Vec::new();
for row in &matrix {
for col in row {
if *col % 2 == 0 {
it.push(*col * 2);
}
}
}
assert_eq!(v, it);
```
# Advanced Examples
See the [`rcomp!`] macro documentation for some advanced examples,
like creating a `HashMap` or `HashSet`.
# Note on Iterator Examples
It's important to note that iterator examples used to test the
comprehensions are _equivalent_ to the comprehensions, but not
_identical_. The macro expands to nested chains of `flat_map`
and `filter_map` calls; the examples are written for clarity
and to show the order of operations in the comprehension. For
example, the matrix example from earlier expands to:
```rust
# use rustcomp::rcomp;
# let matrix = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
let v = (&matrix)
.into_iter()
.flat_map(|row| {
row.into_iter().filter_map(|col| {
if (*col % 2 == 0) && true {
Some((*col * 2))
} else {
None
}
})
})
.collect::<Vec<_>>();
# let mut it = Vec::new();
# for row in &matrix {
# for col in row {
# if *col % 2 == 0 {
# it.push(*col * 2);
# }
# }
# }
# assert_eq!(v, it);
```
Notice the use of `into_iter` in the expansion.
# What about `mapcomp`?
I'm aware of the existence of the [`mapcomp`](https://docs.rs/mapcomp/latest/mapcomp/index.html)
crate, but it differs from this crate in a few ways. For starters,
`mapcomp` aims to make their syntax as close to Python as possible and
I think they did a great job; this crate is not trying to do that. The
goal of this crate is to add comprehensions to Rust in an idiomatic way
with a syntax that flows naturally with the rest of the language while
still being concise and powerful. `mapcomp` also provides multiple
macros for different types of comprehensions while this crate provides
only one.
On a more technical note, `mapcomp` uses generators internally which was
okay for Rust 2018, but generators and `yield`-ing are now experimental
features. This was a big inspiration for this crate, as I wanted to make
a macro-based solution that didn't require nightly, so I settled on iterators
in lieu of generators.
*/
/// Generates an iterator that yields the results of the comprehension. The
/// syntax allows for flattening, filtering, mapping, and collecting iterators
/// (in that order).
///
/// There are 4 main components to a comprehension:
/// - The optional collection type, which is passed to a `collect` call by the
/// macro. If this is omitted, the macro will return an iterator instead of
/// a collection.
/// - The `for-in` clause, which iterates over the input(s). This can be
/// chained (e.g. `for i in v1, j in v2, k in v3, ...`) to flatten nested
/// iterators, up to the recursion limit.
/// - The mapping expression, which transforms the input.
/// - The optional guard expression, which filters the input. Although this is
/// the last component of the comprehension, it is applied _before_ the
/// mapping expression. In a sense, the end of the comprehension looks like
/// a `match` arm. This has a few implications which are explored more in
/// the [crate-level documentation](crate).
///
/// With that explained, here's the full syntax:
///
/// ```text
/// rcomp!([collect_ty;] for <pattern> in <iterator>, ... => <mapper>[, if <guard>]);
/// ```
///
/// # Examples
///
/// Comprehensions can be as simple or complex as you want. They can collect
/// the input, filter it, map it, and flatten it all in one go. For example,
/// here's how you can create a `HashMap` of numbers and their squares using
/// a comprehension:
///
/// ```rust
/// # use rustcomp::rcomp;
/// # use std::collections::HashMap;
/// let m = rcomp![HashMap<_, _>; for i in 0..10 => (i, i * i)];
/// # let it = (0..10).map(|i| (i, i * i)).collect::<HashMap<_, _>>();
/// # assert_eq!(m, it);
/// ```
///
/// Another example is removing duplicates from a `Vec` by converting it to
/// a `HashSet` and back:
///
/// ```rust
/// # use rustcomp::rcomp;
/// # use std::collections::HashSet;
/// let v = vec![1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
/// let s = rcomp![Vec<_>; for i in rcomp![HashSet<_>; for j in &v => *j] => i];
/// # let mut s = s;
/// # let mut it = v
/// # .into_iter().collect::<HashSet<_>>()
/// # .into_iter().collect::<Vec<_>>();
/// # it.sort();
/// # s.sort();
/// # assert_eq!(s, it);
/// ```
///
/// See the [crate-level documentation](crate) for more examples.