Skip to main content

comprehension

An array whose elements are computed. The clauses describe an iteration and expr runs once per step. An expr of type T gives T[].

const bearings_deg: int32[] = [10, 45, 90];

const reciprocals_deg: int32[] = [b + 180 for b in bearings_deg];

Clauses​

A comprehension is the element expression followed by a flat list of clauses. The for keyword after the first element is what distinguishes it from a plain array literal.

  • The clauses are for, if, let, scan, and zip for.
  • The list is freely ordered and each clause may repeat. There is no required for … if … scan shape.
  • Clauses are processed left to right. Every binding is visible to the clauses after it and to the element expression, and not to itself.
  • Each pattern is the same pattern a let takes: a name, a name with a type, a tuple to destructure, or _.
const bearings_deg: int32[] = [10, 45, 90];

[b + 1 for b in bearings_deg]                             // => [11, 46, 91]
[b for b in bearings_deg if b > 20]                       // => [45, 90]
[d for b in bearings_deg let d = b * 2]                   // => [20, 90, 180]
[s for b in bearings_deg scan s: int32 = 0 in s + b]      // => [10, 55, 145]
There Is No Loop Statement

Flex has no for or while in an expression: a comprehension and fold, map, filter, and flatmap are the whole of iteration, and loop belongs to a protocol. Every one of them produces a value, so nothing iterates for its effects.

for​

for pattern in source iterates the source. A second for is a nested loop, not a lockstep one: the clauses form a cartesian product, and the leftmost for varies slowest.

A later source may name a binding an earlier clause introduced.

const sectors: int32[] = [1, 2];
const sweeps: int32[] = [10, 20];

[s + w for s in sectors for w in sweeps]         // => [11, 21, 12, 22]

if​

if predicate drops the iterations where the predicate is false. The predicate has type bit.

let​

let pattern = expr binds a value once per iteration, for the clauses after it and the element expression. It is the place to name a subexpression that would otherwise be repeated.

scan​

scan pattern = init in fold carries an accumulator across iterations. The type annotation is optional and is inferred from the initializer when it is left out.

  • The accumulator is visible in its own fold expression, in every later clause, and in the element expression. It is not visible in its own initializer.
  • The element expression sees the accumulator for the current iteration — the value the fold expression just produced, not the one it started from.
  • Several scan clauses may be threaded through one comprehension.
const legs_m: int32[] = [100, 250, 400];

[total for leg in legs_m scan total: int32 = 0 in total + leg]     // => [100, 350, 750]
[total for leg in legs_m scan total = (0: int32) in total + leg]   // => [100, 350, 750]

zip for​

zip for pattern in source advances its source in lockstep with the frames already established instead of crossing with them. The result is as long as the shortest source.

  • zip cannot appear with scan in one comprehension.
  • zip cannot appear with if in one comprehension.
const sectors: int32[] = [1, 2, 3];
const sweeps: int32[] = [10, 20];

[s + w for s in sectors zip for w in sweeps]        // => [11, 22]
[(s, w) for s in sectors zip for w in sweeps]       // => [(1, 10), (2, 20)]

Errors​

A filter that is not bit​

const bearings_deg: int32[] = [10, 45, 90];

const wrong: int32[] = [b for b in bearings_deg if b];   // error: comprehension-filter-type

zip together with scan​

const sectors: int32[] = [1, 2, 3];
const sweeps: int32[] = [10, 20];

const wrong: int32[] =
    [t for s in sectors zip for w in sweeps scan t: int32 = 0 in t + s];   // error: comprehension-zip-scan-exclusive

zip together with a filter​

const sectors: int32[] = [1, 2, 3];
const sweeps: int32[] = [10, 20];

const wrong: int32[] = [s + w for s in sectors zip for w in sweeps if s > 1];   // error: comprehension-zip-filter-exclusive