Functional Array Methods (map/filter/reduce)
/*
Thread Safety
Grapa is fully thread safe in all supported environments (command line, Grapa shell, and Python/GrapaPy). All built-in operations—including map, filter, reduce, $thread, and $net—are safe to use concurrently. Users do not need to take any special precautions for thread safety in these environments.
Note: Only if Grapa is integrated directly into a non-thread-safe C++ host (not anticipated for normal users) would additional thread safety considerations arise. */
/*
Quick Reference: map, filter, reduce
| Method | Signature | Parallel? | Purpose |
|---|---|---|---|
| map | arr.map(op(x) { ... }, [params...]) | Yes | Transform each item |
| filter | arr.filter(op(x) { ... }, [params...]) | Yes | Select items matching a test |
| reduce | arr.reduce(op(acc, x) { ... }, init, [params]) | No | Accumulate to a single value |
- All methods accept an operation (op) as the first argument.
- For map/filter, the op is called in parallel for each item.
- For reduce, the op is called sequentially, passing the accumulator.
- See also: examples.md, use_cases/index.md */
while
Note: This section covers basic while loop syntax. For comprehensive loop documentation including for loops, do/while loops, and loop control flow, see Basic Syntax: Loops.
Syntax: * while(bool) statement;
Example:
i = 0;
while (i < 5) {
i += 1;
(i.str() + ":").echo();
};
"\n".echo();
/* Output: 1:2:3:4:5: */
map
Syntax: * arr.map(op(x) { ... }, [params...]);
- Calls the operation for each item in the array, in parallel.
- Returns a new array with the results.
- Context objects are passed by reference for performance (not copied).
Example:
arr = [1,2,3];
doubled = arr.map(op(n) { n * 2; });
doubled.echo();
/* Output: [2,4,6] */
Context Object Reference Passing
When using context objects (optional parameters), they are passed by reference for enhanced performance:
/* WARNING: This demonstrates race conditions */
x = {a: 0, b: 0};
y = 20.range().map(op(x, y) {
y.a += 1; /* Race condition: multiple threads may read same value */
y.b += x; /* Race condition: multiple threads may read same value */
y.a * y.b; /* Return computed value */
}, x, 4); /* 4 threads processing */
/* Result: Race conditions cause incorrect values */
/* Expected: x: {"a": 20, "b": 190} */
/* Actual: x: {"a": 19, "b": 180} - showing lost updates */
Better Approach - Map/Reduce Pattern:
/* Recommended: Use .map() for computation, .reduce() for aggregation */
x = {a: 1, b: 2};
y = 20.range().map(op(x, y) {
x + y.a * y.b; /* Pure computation - no mutations */
}, x, 4).reduce(op(a, b) {
a += b; /* Sequential aggregation */
});
/* Result: Correct and fast */
/* x: {"a": 1, "b": 2} - unchanged */
/* y: 230 - correct sum */
Performance Benefits: - Reference passing: Context objects are passed by reference (not copied) for better performance - Read-only usage: Use context objects for configuration, not for state mutations - Parallel efficiency: Each thread can read shared context without synchronization overhead
filter
Syntax: * arr.filter(op(x) { ... }, [params...]);
- Calls the operation for each item in the array, in parallel.
- Returns a new array containing only items for which the op returns a non-null, non-zero, non-empty value.
- Context objects are passed by reference for performance (not copied).
Minimal Example:
arr = [1,2,3,4];
filtered = arr.filter(op(x) { x > 2; });
filtered.echo();
/* Output: [3,4] */
Context Object Reference Passing
When using context objects (optional parameters), they are passed by reference for enhanced performance:
/* WARNING: This demonstrates race conditions */
x = {count: 0, sum: 0};
y = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter(op(x, y) {
y.count += 1; /* Race condition: multiple threads may read same value */
if (x % 2 == 0) { /* Filter even numbers */
y.sum += x; /* Race condition: multiple threads may read same value */
true; /* Include in result */
} else {
false; /* Exclude from result */
};
}, x, 4); /* 4 threads processing */
/* Result: Race conditions cause incorrect values */
/* Expected: x: {"count": 10, "sum": 30} */
/* Actual: x: {"count": 9, "sum": 28} - showing lost updates */
Better Approach - Separate Filtering and Aggregation:
/* Recommended: Use .filter() for selection, separate aggregation */
x = {threshold: 2};
y = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter(op(x, y) {
x > y.threshold; /* Pure filtering - no mutations */
}, x, 4);
/* Separate aggregation if needed */
sum = y.reduce(op(a, b) { a += b; });
count = y.len();
/* Result: Correct and fast */
/* y: [3, 4, 5, 6, 7, 8, 9, 10] */
/* sum: 52, count: 8 */
Performance Benefits: - Reference passing: Context objects are passed by reference (not copied) for better performance - Read-only usage: Use context objects for configuration, not for state mutations - Parallel efficiency: Each thread can read shared context without synchronization overhead
Edge Cases:
arr = [1,2,3,4];
filtered_none = arr.filter(op(x) { 0; });
filtered_none.echo();
/* Output: [] */
reduce
Syntax: * arr.reduce(op(acc, x) { ... }, init, [params...]);
- Calls the operation sequentially for each item, passing the accumulator (acc) and the current item (x).
- The accumulator must be mutated inside the op (e.g., acc += x;), not just returned as a new value.
- Returns the final value of the accumulator.
- If init is omitted, the first item is used as the initial value.
- Reduce is always sequential (not parallel).
Minimal Example (Sum):
arr = [1,2,3,4];
sum = arr.reduce(op(acc, x) { acc += x; }, 0);
sum.echo();
/* Output: 10 */
Canonical Example (All Together):
arr = [1,2,3,4];
filtered_gt2 = arr.filter(op(x) { x > 2; });
filtered_eq2 = arr.filter(op(x) { x == 2; });
filtered_none = arr.filter(op(x) { 0; });
reduced_sum = arr.reduce(op(acc, x) { acc += x; }, 0);
("filtered_gt2: " + filtered_gt2.str() + "\n").echo();
("filtered_eq2: " + filtered_eq2.str() + "\n").echo();
("filtered_none: " + filtered_none.str() + "\n").echo();
("reduced_sum: " + reduced_sum.str() + "\n").echo();
/*
Output:
filtered_gt2: [3,4]
filtered_eq2: [2]
filtered_none: []
reduced_sum: 10
*/
Important: Accumulator Mutation in reduce
- The accumulator variable (acc) must be mutated inside the op for reduce to work as expected.
- This means you must use
acc += x;or similar, not justacc + x;. - Returning a new value does NOT update the accumulator.
- Common mistake:
arr = [1,2,3,4]; sum = arr.reduce(op(acc, x) { acc + x; }, 0); sum.echo(); /* Output: 0 (WRONG!) */ - Correct:
arr = [1,2,3,4]; sum = arr.reduce(op(acc, x) { acc += x; }, 0); sum.echo(); /* Output: 10 */
Troubleshooting reduce
- If your reduce result is always the initial value, check that you are mutating the accumulator.
- Use
acc += x;oracc = acc * x;as needed. - If you want to build an array, use
acc += x;with an array accumulator.
Best Practices for map/filter/reduce
- Use map for transformations, filter for selection, reduce for aggregation.
- For side effects, prefer reduce (sequential) over map/filter (parallel).
- Always use block comments (
/* ... */) for documentation. - Test your op in the REPL with small arrays before using in scripts.
- For complex reductions, consider using an object or array as the accumulator.
See also
- Basic Syntax: Loops - Traditional loop constructs (
while,for,do/while) - API Reference
- Examples
- Use Cases
- Object Methods: Iterate - Object method reference for iteration functions
/ End of canonical map/filter/reduce documentation. /