July 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.

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.