digitalmars.dip.ideas - Foreach application function
- Quirin Schroll (85/85) Jul 16 ### 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. 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)`. 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`. 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. 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.
Jul 16