17.14.5 Reducer Macros
space | |
definition | |
This first example simply defines a short-cut for reducing into an array of length 5:
reducer.macro '(Array.len5)':
'Array.of_length(5)'
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':
'sptt_return',
'(accum = 0)',
#false,
'sptt_step_defs',
#false,
'sptt_final',
'sptt_step_result',
'()',
'[new_accum, accum]'
)
'cond
| ~else: $e'
defn.macro 'sptt_step_defs [$new_accum, $accum] $e':
| ~else: $accum'
expr.macro 'sptt_final [$new_accum, $_]':
expr.macro 'sptt_step_result [$new_accum, $_]':
'$new_accum'
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 [si, ...]:
let sis = statinfo_meta.lookup(si, statinfo_meta.values_key)
if sis
| statinfo_meta.unpack_group(sis)
| [si]
'build_return',
'build_inc',
'build_finish',
$(statinfo_meta.pack_group('$si ... ()'))))',
'[[count, $id, ...],
$data]'
)
expr.macro 'build_return [$_, $wrap, $_, $_, $_, $_, $_, $data] $e':
| (c, r):
// optimize the common case
| (c, r, $('...')):
))'
defn.macro 'build_pre [$_, $_, $pre, $_, $_, $_, $_, $data]':
defn.macro 'build_inc [$_, $_, $_, $step, $_, $_, $_, $data] $e':
expr.macro 'build_break [$_, $_, $_, $_, $break, $_, $_, $data]':
expr.macro 'build_final [$_, $_, $_, $_, $_, $final, $_, $data]':
expr.macro 'build_finish [[$count, $id, ...],
$data]':
'block:
// static information is also chained
def (map, count):
> values(map, count)
{0: "val0", 2: "val2", 4: "val4", 6: "val6", 8: "val8"}
5
> block:
map.remove(2)
{0: "val0", 4: "val4", 6: "val6", 8: "val8"}
> // cooperate with multiple-value reducers
10
0
5
function | ||||||||||
|
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—
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.
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 | ||||||
| ||||||
| ||||||
syntax class | ||||||
| ||||||
| ||||||
syntax class | ||||||
|
Analogous to expr_meta.Parsed, etc., but for reducers.