Parallel collection (++)¶
The dual of ||: where || runs blocks until one
succeeds, ++ runs every block and collects every surviving
result into an array.
Syntax¶
Each block declares its output with (:var). All blocks must use
the same variable name and produce the same value type — checked at
compile time.
Semantics¶
- All blocks are evaluated independently, each starting from the same input state.
- Variables modified inside a block stay inside that block — there is no leakage between paths.
- Each block that produces a value for
:varcontributes to the result. - Blocks that fail (an
Assertfires) contribute nothing and are silently dropped — failure of one path does not abort the others. - The output of
++is alwaysArray(T), whereTis the declared type of:var.
Result flattening¶
Outputs are flattened one level:
| Path A produces | Path B produces | ++ output |
|---|---|---|
scalar 1 |
scalar 2 |
Array(Int) = [1, 2] |
[1, 2] |
scalar 3 |
Array(Int) = [1, 2, 3] |
[1, 2] |
[3, 4] |
Array(Int) = [1, 2, 3, 4] |
So a path that internally produces multi-results — typically through
UsagiMap()[] — contributes each of those individually to the
collected output. Array(Array(T)) never occurs for scalar/array
outputs. Tuple outputs are kept as Array(Tuple(...)) to preserve
the correlation between fields.
Worked example — concepts from two parallel maps¶
Same gender_concept_id field as the fallback example,
but here we want every concept any map can offer — both the
precise and the broad — and let downstream rules deduplicate.
Read it as: run map3.csv and map4.csv against the same input,
keep all matches from both, deliver a single flattened
Array(Int) containing every match.
The CDM-table-level LoopOver([:person_id, :gender_concept_id])
then expands the array into one row per match.
When to reach for ++ vs ||¶
||— prefer one path; only fall back to the next if the preferred fails. Used when one path is canonical and others are backups.++— every path is equally valid; the union of their results is the answer. Used when source data has multiple valid interpretations or when you want to apply several mappings without picking a winner.
When ++ is overkill¶
If the parallel paths produce scalar results that you want
combined with arithmetic — e.g. compute several values and add
them — you do not need ++. Just write the rules sequentially and
combine with Add,
Selector, etc.
++ is specifically for the "collect array of results" case.
Restrictions¶
++blocks cannot directly contain another++. Indirect containment (e.g. inside anIfbranch within a++block) is fine.||,If, and multi-result rules (UsagiMap()[]) are all valid inside a++path.- All blocks must declare the same output variable with the same type.
See also¶
- Fallback chains (
||) — the dual of++. - Multi-result mappings — how
Array(T)outputs fan into rows viaLoopOver. - Arrays and New Mode — first-class
ARRAYtype that++produces.
Source tests¶
tests/test091/ — basic ++ between two UsagiMap blocks.