Skip to main content

fold, map, filter, flatmap

Looping expressions over a collection. Each takes a source, binds one element at a time, and runs a block body whose tail expression is that iteration's result.

function total_range_m(ranges_m: int32[]) -> int32 = fold(total: int32 = 0; r in ranges_m) {
    total + r;
};

Sources and bindings​

fold, map, filter, and flatmap are all expressions rather than statements.

  • fold takes an array. map, filter, and flatmap take an array or an Optional.
  • Over an Optional, the body runs only when the source is some, and the result is an Optional.
  • The iteration variable is bound in the body and nowhere else.
  • The variable takes the same patterns a let does, so a tuple element can be destructured in the header.
  • Ascribing the variable a type pins the source's element type, which is how a collection of otherwise unpinned literals becomes usable.

fold​

fold(acc = init; x in source) reduces an array left to right. Each body tail becomes the next accumulator value, and the last one is the result.

  • The accumulator's type annotation is optional, and is inferred from the initializer when omitted.
  • The initializer must be assignable to the accumulator type, and so must the body tail.
  • The result type is the accumulator type.
  • A tuple accumulator carries several running values, and then the body must produce a tuple.
const legs_m: int32[] = [100, 250, 400];

fold(total: int32 = 0; leg in legs_m) { total + leg; }                    // => 750
fold(total = (0: int32); leg in legs_m) { total + leg; }                  // => 750
fold((lo, hi) = ((0: int32), (0: int32)); leg in legs_m) { (lo + leg, hi + (1: int32)); }   // => (750, 3)

map​

map(x in source) applies the body to every contained value. An array of T becomes an array of the body's type, and an Optional<T> becomes an Optional of it.

The body may have any type. It is the only one of these forms that changes the element type freely.

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

map(b in bearings_deg) { b * 2; }                       // => [20, 90, 180]
map(b in some((45: int32))) { b * 2; }                   // => some(90)
map(b in (none: Optional<int32>)) { b * 2; }             // => none

filter​

filter(x in source) keeps the values whose body is true. The result has the source's own type.

The body must have type bit. Filtering an Optional either keeps the some or turns it into none.

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

filter(b in bearings_deg) { b > 20; }        // => [45, 90]
filter(b in some((10: int32))) { b > 20; }    // => none

flatmap​

flatmap(x in source) maps each value to a collection and flattens one level. Arrays are concatenated in iteration order; a nested optional collapses to a single one.

  • The body must produce the same kind of collection as the source. An array source needs an array body, and an optional source an optional body.
  • The body's element type need not relate to the source's.
  • An empty array body contributes nothing, which is how one pass both filters and maps.
const bearings_deg: int32[] = [10, -45, 90];

flatmap(b in bearings_deg) { [b, b]; }                              // => [10, 10, -45, -45, 90, 90]
flatmap(b in bearings_deg) { if b > 0 then [b] else []; }            // => [10, 90]
flatmap(b in some((45: int32))) { some(b * 2); }                     // => some(90)

Errors​

A fold over something that is not an array​

function total(range_m: int32) -> int32 = fold(total: int32 = 0; r in range_m) {   // error: fold-source-type
    total + r;
};

A fold initializer of the wrong type​

function total(ranges_m: int32[]) -> int32 = fold(total: int32 = "none"; r in ranges_m) {   // error: fold-init-type
    total + r;
};

A fold body that is not the accumulator's type​

function total(ranges_m: int32[]) -> int32 = fold(total: int32 = 0; r in ranges_m) {   // error: fold-body-type
    "counted";
};

A map over something that is neither an array nor an optional​

function doubled(range_m: int32) -> int32[] = map(r in range_m) {   // error: map-source-type
    r * 2;
};

A filter over something that is neither an array nor an optional​

function nearby(range_m: int32) -> int32 = filter(r in range_m) {   // error: filter-source-type
    r < 500;
};

A filter body that is not bit​

function nearby(ranges_m: int32[]) -> int32[] = filter(r in ranges_m) {   // error: filter-body-type
    r * 2;
};

A flatmap over something that is neither an array nor an optional​

function paired(range_m: int32) -> int32[] = flatmap(r in range_m) {   // error: flatmap-source-type
    [r, r];
};

A flatmap body that is not a collection​

function paired(ranges_m: int32[]) -> int32[] =   // error: type-mismatch
    flatmap(r in ranges_m) {   // error: flatmap-body-type
        r;
    };
  • comprehension — the same iteration in bracket form, with clauses instead of a body.
  • array — the source all of these are usually written over.
  • optional — the other source, and what a map over one returns.