Abstract
The general idea is that the foreach application function answers the question: “How do we iterate?” It does so by applying the function to the foreach
aggregate.
Description
Allow a foreach application function lexically between foreach
and the opening parenthesis. It is applied to the foreach
aggregate, otherwise it’s a normal foreach
. If reverse
were added to object.d, foreach_reverse
could become foreach reverse
and we could get rid of an oddly specific keyword. This pattern reads a lot nicer in many cases than applying a function to the foreach
aggregate.
Practically, foreach F (…; expr)
just lowers to foreach (…; F(expr))
.
In foreach F (i; L .. U)
, the foreach application function F
is called with two arguments as foreach (i; F(L, U))
.
A foreach
with application function can have more than one aggregate:
foreach F (x, y; xs, ys) { … }
// lowers to
foreach F (x, y; F(xs, ys)) { … }
The aggregates always must be valid for a foreach
at least insofar that they are:
- static arrays, slices, or associative arrays, or
- have the members for the range interface, or
- have a member
opApply
.
For the case of ..
, the language must ensure L
and U
would work without the application function.
This is part of the stated objective of foreach application functions: They define how to iterate. Their purpose is not to enable iteration of what isn’t iterable to begin with.
Otherwise, in principle, aggregates could function as mere function arguments to the application function. This is undesirable. However, regular function parameters can be passed to the aggregate function directly:
foreach F(e) (x, y; xs, ys) { … }
// lowers to
foreach (x, y; F(xs, ys, e)) { … }
This is in line with how UFCS calls work.
More than one application function can be allowed:
foreach F(a) G(b, c) (x; xs) { … }
// lowers to
foreach G(b, c) (x; F(xs, a)) { … }
// lowers to
foreach (x; G(F(xs, a), b, c)) { … }
(The intermediate step is relevant: The result of F(xs, a)
must be iterable.)
This is also in line with how UFCS calls work: xs.F(a).G(b, c)
is G(F(xs, a), b, c)
.
Examples
Other than reverse
, there are applications for parallel or lockstep or cross-product iteration, probably more.
foreach reverse (i; 0 .. n) {}
enum sameLength = StoppingPolicy.requireSameLength
foreach lockstep(sameLength) reverse (i, x, y; xs, ys) {}
foreach parallel (x; xs) {}
foreach reverse parallel (i; 0 .. n) {}
// somewhat contrived:
alias multiply = t => t[0] * t[1];
foreach zip map!multiply filter!(x => x != 0) (x; xs, ys) {}
reverse
could use DbI to determine if it’s given numeric or range/opApply arguments. It implements reverse iteration essentially as iota
does, it forwards a bidirectional range interface to the forward range iterface, and makes its opApply
be the argument’s opApplyReverse
.
Possible Problems
While technically, a foreach application function can’t distinguish the ..
and ,
case with 2 arguments, the ..
case has numeric arguments and the other has iterable arguments. As stated above, two comma-separated aggregates will be guaranteed by the language to be reasonably close to being iterable, whereas slicing-separated arguments are guaranteed to be reasonably close to being numeric.
Grammar
Grammatically, a foreach application functions can’t be an arbitrary expression, but must be a PrimaryExpression other than a parenthesized Expression with an optional NamedArgumentList in parentheses.