On this page:
reducer
reducer.macro
reducer_  meta.pack
reducer_  meta.unpack
reducer_  meta.Parsed
reducer_  meta.After  Prefix  Parsed
reducer_  meta.After  Infix  Parsed
8.12
7.13.5 Reducer Macros🔗ℹ

space

reducer

The space for bindings of identifiers that can be used in reducer positions, such as within for.

Like expr.macro, but defines an identifier or operator as a reducer form in the reducer space. The result of the macro expansion can be a low-level reducer description created with reducer_meta.pack.

This first example simply defines a short-cut for reducing into an array of length 5:

reducer.macro '(Array.len5)':

  'Array.of_length(5)'

> for Array.len5 (i: 0..3): i

Array(0, 1, 2, 0, 0)

Here’s an example that sums only the positive numbers that result from the loop body, and halts as soon as the sum becomes larger than 20. This illustrates the use of step_id to create a binding that can be used by both the final_id and step_result_id macros.

reducer.macro 'sum_pos_to_20':

  reducer_meta.pack(

    'sptt_return',

    '(accum = 0)',

    #false,

    'sptt_step_defs',

    #false,

    'sptt_final',

    'sptt_step_result',

    '()',

    '[new_accum, accum]'

  )

expr.macro 'sptt_return [$_, $_] $e':

  'cond

   | $e > 20: "bigger than 20"

   | ~else: $e'

defn.macro 'sptt_step_defs [$new_accum, $accum] $e':

  'def $new_accum:

     cond

     | $e > 0: $accum + $e

     | ~else: $accum'

expr.macro 'sptt_final [$new_accum, $_]':

  '$new_accum > 20'

expr.macro 'sptt_step_result [$new_accum, $_]':

  '$new_accum'

> for sum_pos_to_20 (a: [6, -4, 3]): a

9

> for sum_pos_to_20 (a: 3..): a

"bigger than 20"

It is recommended that a reducer macro only consume a finite number of terms, as opposed to the whole tail, to account for the maybe_each position in for.

This example shows that reducers can be chained; specifically, it creates a counted reducer, that chains onto an existing reducer and keeps track of the number of elements while allowing the prior reducer to operate normally. The bulk of the code in this example is in showing how to explicitly fall through to the macros defined in the existing reducer.

reducer.macro 'counted($(r :: reducer_meta.Parsed))':

  let '($wrap, ($(bind && '$id $_'), ...),

        $pre, $step, $break, $final, $finish,

        $si, $data)':

    reducer_meta.unpack(r)

  let [si, ...]:

    let sis = statinfo_meta.lookup(si, statinfo_meta.values_key)

    if sis

    | statinfo_meta.unpack_group(sis)

    | [si]

  reducer_meta.pack(

    'build_return',

    '(count = 0, $bind, ...)',

    pre.unwrap() && 'build_pre',

    'build_inc',

    break.unwrap() && 'build_break',

    final.unwrap() && 'build_final',

    'build_finish',

    '(($statinfo_meta.values_key,

       $(statinfo_meta.pack_group('$si ... ()'))))',

    '[[count, $id, ...],

      $wrap, $pre, $step, $break, $final, $finish,

      $data]'

  )

expr.macro 'build_return [$_, $wrap, $_, $_, $_, $_, $_, $data] $e':

  'call_with_values(

     fun (): $e,

     fun

     | (c, r):

         // optimize the common case

         values($wrap $data r, c)

     | (c, r, $('...')):

         call_with_values(

           fun (): $wrap $data (values(r, $('...'))),

           fun (r, $('...')): values(r, $('...'), c)

         ))'

defn.macro 'build_pre [$_, $_, $pre, $_, $_, $_, $_, $data]':

  '$pre $data'

defn.macro 'build_inc [$_, $_, $_, $step, $_, $_, $_, $data] $e':

  '$step $data $e'

expr.macro 'build_break [$_, $_, $_, $_, $break, $_, $_, $data]':

  '$break $data'

expr.macro 'build_final [$_, $_, $_, $_, $_, $final, $_, $data]':

  '$final $data'

expr.macro 'build_finish [[$count, $id, ...],

                          $_, $_, $_, $_, $_, $finish,

                          $data]':

  'block:

     def ($id, ...) = $finish $data

     values($count + 1, $id, ...)'

> for counted(List) (i: 0..3): i

[0, 1, 2]

3

// static information is also chained

def (map, count):

  for counted(Map) (i: 0..10):

    keep_when i mod 2 == 0

    values(i, "val" +& i)

> values(map, count)

{0: "val0", 2: "val2", 4: "val4", 6: "val6", 8: "val8"}

5

> block:

    use_static

    map.remove(2)

{0: "val0", 4: "val4", 6: "val6", 8: "val8"}

> // cooperate with multiple-value reducers

  for counted(values(i = 0, j = 10)) (k: 0..5):

    values(i+k, j-k)

10

0

5

function

fun reducer_meta.pack(complete_id :: Identifier,

                      binds :: Syntax,

                      pre_clause_id :: maybe(Identifier),

                      step_id :: Identifier,

                      break_id :: maybe(Identifier),

                      final_id :: maybe(Identifier),

                      step_result_id :: Identifier,

                      static_info :: Syntax,

                      data :: Syntax)

  :: Syntax

Provided as meta.

Packs reducer information that is represented by a syntax object with eight parts. The parts are taken separately by reducer_meta.pack, but they are combined in the form

'(complete_id,    // expression macro

  (accum_id = accum_expr, ...),

  pre_clause_id,  // optional definition macro

  step_id,        // definition macro

  break_id,       // optional expression macro

  final_id,       // optional expression macro

  step_result_id, // expression macro

  ((var_static_key, var_static_value), ...),

  data)'

These pieces give the reducer control over how elements of the iteration are accumulated on each step and a single completion action performed for the accumulated values. Configuration of the step is split into four parts—step_id, break_id, final_id, step_result_idto enable early termination of the iteration depending on element values or an accumulated value.

As an example, for the List reducer, complete_id reverses an accumulated list, one accum_id is initialized to [] and represents an accumulated (in reverse) list, pre_clause_id is false, step_id adds a new value to the front of the list and binds it to a fresh variable next_accum_id, break_id and final_id are false (because early termination is never needed by the reducer), step_result_id returns next_accum_id, the var_static_keys with var_static_values provide static information for a list, and data has accum_id and next_accum_id (so that step_id and step_result_id are able to refer to them).

In detail:

See reducer.macro for an example.

Provided as meta.

Roughly the inverse of reducer_meta.pack, except that the pieces are returned in a combined syntax object instead of as multiple values. Unpacking can be useful for defining a new form that works with reducers or for defining a reducer in terms of another reducer.

See reducer_meta.pack for an example.

syntax class

syntax_class reducer_meta.Parsed:

  kind: ~group

  fields:

    group

 

syntax class

syntax_class reducer_meta.AfterPrefixParsed(op_name):

  kind: ~group

  fields:

    group

    [tail, ...]

 

syntax class

syntax_class reducer_meta.AfterInfixParsed(op_name):

  kind: ~group

  fields:

    group

    [tail, ...]

Provided as meta.

Analogous to expr_meta.Parsed, etc., but for reducers.