On this page:
reducer
reducer.macro
reducer_  meta.space
reducer_  meta.pack
reducer_  meta.unpack
reducer_  meta.Parsed
reducer_  meta.After  Prefix  Parsed
reducer_  meta.After  Infix  Parsed
reducer_  meta.Name  Start
8.17.0.6

4.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 in 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 in [6, -4, 3]): a

9

> for sum_pos_to_20 (a in 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 in 0..3): i

[0, 1, 2]

3

// static information is also chained

def (map, count):

  for counted(Map) (i in 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 in 0..5):

    values(i+k, j-k)

10

0

5

Provided as meta.

A compile-time value that identifies the same space as reducer. See also SpaceMeta.

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:

  • The complete_id should refer to a macro that expects data followed by an expression that produces a final value for each accum_id. The result of that macro use is the overall accumulated result (e.g., the result of the for form using the packed reducer).

  • The accum_ids correspond to the state of the reducer as it receives a stream of input values. As internal state, these identifiers generally should not be directly visible to clients of the reducer. The accum_exprs determine the initial values, the step_id determines how each is updated for an input. When no further inputs are available, complete_id receives the final state to convert it in to the result value.

  • The optional pre_clause_id should refer to a macro that expects data and produces definitions to be placed before any for clauses, therefore visible to the whole for body. For example, it can be used to provide static information for accum_ids, which should be visible even in for clauses.

  • The step_id should refer to a macro that expects data followed by an expression that produces a value (or multiple values) to be accumulated. It should expand to definitions that bind whatever is needed by break_id, final_id, and especially step_result_id.

  • The optional break_id should refer to a macro that expects data and produces a boolean that indicates whether to stop the iteration with the value(s) accumulated through previous steps, not accumulating in this step. Supplying #false for break_id is a hint that breaking is never needed before the iteration would otherwise complete, which might enable a more efficient compilation.

  • The optional final_id should refer to a macro that expects data and produces a boolean that indicates whether to stop the iteration after the accumulation of the current step. Like break_id, Supplying #false for final_id serves as a performance hint.

  • The step_result_id should refer to a macro that expects data and produces a number of results corresponding to the number of accum_ids. Each result becomes the new value of the corresponding accum_id.

  • The var_static_keys with var_static_values provide static information for the reducer’s result.

  • The data component is effectively the data half of a closure for complete_id and step_id. It can have any shape that is needed to provide information to complete_id and step_id. It often will include the accum_ids so that step_id can refer to them.

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, ...]

 

syntax class

syntax_class reducer_meta.NameStart:

  kind: ~group

  fields:

    name

    [head, ...]

    [tail, ...]

Provided as meta.

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