Foreach application function
Quirin Schroll
qs.il.paperinik at gmail.com
Tue Jul 16 15:45:53 UTC 2024
### 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:
```d
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:
```d
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:
```d
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.
```d
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*](https://dlang.org/spec/grammar.html#PrimaryExpression) other than a parenthesized [*Expression*](https://dlang.org/spec/grammar.html#Expression) with an optional [*NamedArgumentList*](https://dlang.org/spec/grammar.html#NamedArgumentList) in parentheses.
More information about the dip.ideas
mailing list