I wrote clj-iterate after using Daniel Janus excellent [clj-iter] 1 package. I needed a larger subset of common lisp's iterate than clj-iter provided. Unfortunately the bit I needed (the INTO clause) required a rewrite.
To use clj-iterate, add it to your lein project.clj, as follows:
:dependencies [[clj-iterate "0.96"]]
The 'iter' macro takes a list of expressions. Lists are treated as clojure code to be executed inside the loop and maps are treated as iteration clauses.
user> (iter {for x from 1 to 10}
(println "On" x)
{collect x})
On 1
On 2
On 3
On 4
On 5
On 6
On 7
On 8
On 9
On 10
(1 2 3 4 5 6 7 8 9 10)
user>
The macro expands into a loop/recur form that is side-effect free and typically very fast.
There are several different types of iteration clauses:
- Driver clauses specify values to iterate over
- Gatherer clauses collect information during the iteration
- Control flow clauses can cause early termination of the loop
For example:
user> (iter {for x from 1 to 10}
{return-if (= x 5)}
{collect x})
(1 2 3 4)
user>
For is a driver clause, collect is a gatherer clause, and
return is a flow control clause.
Clauses are declarative. They have the same effect no matter where they are placed in the iter form. Updates and tests occur in lockstep.
Therefore:
user> (iter {for x from 1 to 10}
{collect x}
{return-if (= x 5)})
(1 2 3 4)
user>
Order is significant for clojure code, of course.
Some clauses that introduce a new loop variable take a type
option. If given, the loop variable will be of that primitive type,
making the code more efficient.
For example:
user> (time (iter {repeat 10000000}
{sum 1}))
"Elapsed time: 421.595 msecs"
10000000
user> (time (iter {repeat 10000000}
{sum 1 type int}))
"Elapsed time: 249.043 msecs"
10000000
user>
Driver clauses are all executed in parallel. The loop terminates when any of the driver clauses reach their stopping criteria.
user> (iter {for x from 1 to 5}
{for y downfrom 5 to -100}
{collect [x y]})
([1 5] [2 4] [3 3] [4 2] [5 1])
user>
Clj-iterate supports the following numeric driver clauses:
Iterates over the integers from the from expr to the to expr. If
to is missing, this clause will count forever. If there is a 'by'
clause increment by that amount on each iteration.
Same as the from form, except we are counting down instead of
up. Note: the by expression must be negative (or absent) for the
loop to terminate.
Repeat the loop n times. If the using option is present, expose
the iteration variable by that name.
Iterates over the sequence returned by expr, binding var to each
element. Destructuring is supported:
user> (iter {for [key val] in {1 2 3 4} }
(println "Key" key "Val" val))
Key 1 Val 2
Key 3 Val 4
nil
user>
Note that there is no type option for this clause because the items
in the sequence are probably already boxed. Adding a type declaration
would probably slow the loop down.
Iterates over successive subsequences of the sequence returned by
expr, binding var to each subsequence.
General construct for doing loop like iteration within iter. Sets
var to initially on first pass, then to then on successive
passes. Terminates if the until expression is true.
All gather clauses support two options: into and if.
Into is used to gather the results into a new loop variable. Into
clauses are extremely useful if you'd like to calculate more than one
result during a single pass through the data. You can store each
result into a new loop variable.
If a gatherer clause does not have an into option, the values are
collected into a hidden variable which will be the return value of the
iter expression. If more than one gather clause is missing the into
option, the results are undefined. If no gather clauses are missing
the into option, you can use the returning clause to specify a
value to return at the end of the loop.
If a gather clause has an if option, the clause will only collect the
value if the provided expression is true.
Here is an example illustrating these options:
user> (iter {for x from 0 to 10}
{collect x into evens if (even? x)}
{collect x into odds if (odd? x)}
{returning [evens odds]})
[(0 2 4 6 8 10) (1 3 5 7 9)]
user>
Clj-iterate supports the following gatherer clauses:
Sum the expr over all loop iterations.
Mutiply the expr together. Return 1 if there are no values.
Collect expr into a sequence (specifically a persistent
queue). Optionally start with the collect specified by the initially
option, which can by any data structure that supports conj.
Reduce the values returned by expr using reduce-fn. Iter mimics the
clojure reduce function in that if zero values are reduced, the
result is the function applied with no arguments.
For example, if iter did not have a sum clause you could implement
it like this:
(iter {for x from 1 to 10}
{reduce x by +})
If you provide an initial value with initially, that value is used
as a starting point for the reduction. Iter can generate more
efficient code with the initially option, and the reduction
function need not accept zero elements. Therefore, we could implement
sum more efficiently as follows:
(iter {for x from 1 to 10}
{reduce x by + initially 0})
This is, in fact, how the sum clause is implemented.
Collect the values into a set (or the data type provided by the initially expr).
Concatenate the results.
Create a map of the expr values indexed by key:
user> (iter {for x from 1 to 10}
{assoc x key x})
{1 1, 2 2, 3 3, 4 4, 5 5, 6 6, 7 7, 8 8, 9 9, 10 10}
user>
If by is specified, use that function to reduce values with the same key:
user> (iter {for x from 1 to 100}
{assoc (list x) key (mod x 10) by concat})
{0 (10 20 30 40 50 60 70 80 90 100),
1 (1 11 21 31 41 51 61 71 81 91),
2 (2 12 22 32 42 52 62 72 82 92),
3 (3 13 23 33 43 53 63 73 83 93),
4 (4 14 24 34 44 54 64 74 84 94),
5 (5 15 25 35 45 55 65 75 85 95),
6 (6 16 26 36 46 56 66 76 86 96),
7 (7 17 27 37 47 57 67 77 87 97),
8 (8 18 28 38 48 58 68 78 88 98),
9 (9 19 29 39 49 59 69 79 89 99)}
user>
If 'initially' is specified, use that as the initial value for the key-based reductions. For example, you could implement the above example, creating less garbage, like so:
user> (iter {for x from 1 to 100}
{assoc x key (mod x 10) by conj initially ()})
{0 (100 90 80 70 60 50 40 30 20 10),
1 (91 81 71 61 51 41 31 21 11 1),
2 (92 82 72 62 52 42 32 22 12 2),
3 (93 83 73 63 53 43 33 23 13 3),
4 (94 84 74 64 54 44 34 24 14 4),
5 (95 85 75 65 55 45 35 25 15 5),
6 (96 86 76 66 56 46 36 26 16 6),
7 (97 87 77 67 57 47 37 27 17 7),
8 (98 88 78 68 58 48 38 28 18 8),
9 (99 89 79 69 59 49 39 29 19 9)}
user>
Expr should return a map. All maps are merged, as in the clojure merge function.
Keys that occur in more than one map are combined using the function
specified in the by option, which must take 2 arguments. If the by
option is not specified, entries in later iterations overwrite
previous iterations.
If pred is true, immediately exit the loop..
Not really control flow. Defines a variable inside the loop body.
If the type option is present, make the variable statically
typed. This is exactly equivalent to:
{for ^type var = expr}
{for var from expr [to expr] [by expr] [type type]}
{for var downfrom expr [to expr] [by expr] [type type]}
{for var initially expr then expr [until expr] [type type]}
{repeat n [using var]}
{for var in expr}
{for var on expr}
{sum expr [ into var ] [ if pred ] [type type]}
{multiply expr [ into var ] [ if pred ] [type type]}
{max expr [ into var ] [ if pred ] [type type] [by comparator]}
{min expr [ into var ] [ if pred ] [type type] [by comparator]}
{mean expr [ if pred ]}
{collect expr [ into var ] [ if pred ] [initially expr]}
{reduce expr by reduce-fn [ initially expr ] [ into var ] [ if pred ] [type type]}
{conj expr [ into var ] [ if pred ] [initially expr]}
{concat expr [ into var ] [ if pred ] [initially expr]}
{assoc expr key key [by reduce-fn] [ initially expr ] [ into var ] [ if pred ]}
{merge expr [ by fn ] [ into var ] [ if pred ] [initially expr]}
{returning expr}
{return-if pred}
{for var = expr}