In Chapter 6, we introduced map
, the fundamental tool for transforming arrays. However, transformation is only one part of the story. Often, you need to select a subset of items from an array or aggregate an entire array into a single value.
This chapter introduces the remaining array operators: filter
, reduce
, and specialized query operators (find
, some
, every
, flatMap
). Mastering these operators alongside map
will give you a complete and powerful toolkit for virtually any array manipulation task.
filter
: Selecting Items from an ArrayWhile map
transforms every item in an array, filter
selects items from an array. It iterates over an array and returns a new array containing only the items for which a given condition is "truthy".
The syntax is nearly identical to map
:
["filter", <array_expression>, ["lambda", ["<item_variable>"], <condition_expression>]]
The <condition_expression>
must evaluate to a "truthy" or "falsy" value, following the same rules as the if
operator. If the condition is truthy, the item is kept; if falsy, it is discarded.
filter
Example: Finding Active UsersLet's return to our users_input.json
and create a new list containing only the active users.
users_input.json
(for reference):
{
"users": [
{ "name": "Alice", "active": true, "plan": "premium" },
{ "name": "Bob", "active": false, "plan": "basic" },
{ "name": "Charlie", "active": true, "plan": "basic" }
]
}
filter_active.json
:
["filter",
["get", ["$input"], "/users"],
["lambda", ["user"], ["get", ["$", "/user"], "/active"]]
]
The lambda here is simple: for each user
object, it gets the value of the active
key. Since true
is truthy and false
is falsy, this works perfectly as a condition.
Output:
[
{
"active": true,
"name": "Alice",
"plan": "premium"
},
{
"active": true,
"name": "Charlie",
"plan": "basic"
}
]
The resulting array contains only the objects for Alice and Charlie, because Bob's active
field was false
.
filter
and map
The true power of these operators comes from chaining them together. Because filter
returns an array, you can feed its result directly into a map
operator.
Let's build on the previous example. We want a list of just the names of the active, premium users.
active_premium_names.json
:
["map",
["filter",
["filter",
["get", ["$input"], "/users"],
["lambda", ["user"], ["get", ["$", "/user"], "/active"]]
],
["lambda", ["user"], ["==", ["get", ["$", "/user"], "/plan"], "premium"]]
],
["lambda", ["user"], ["get", ["$", "/user"], "/name"]]
]
This script looks complex, but it's a very clear data pipeline if you read it from the inside out:
1. First filter
: Selects all users where active
is true.
2. Second filter
: Takes the result of the first filter and, from that subset, selects all users where plan
is equal to "premium"
.
3. map
: Takes the final filtered list (which now only contains Alice) and transforms it into an array of names.
Output:
[
"Alice"
]
This pattern—filter, then map—is one of the most common and powerful patterns in functional data processing.
reduce
: Aggregating an Array to a Single ValueWhile map
and filter
produce new arrays, reduce
(sometimes called "fold" or "accumulate") boils an entire array down to a single value. This is used for tasks like summing numbers, concatenating strings, or flattening a list of lists.
The reduce
operator is the most complex of the three, introducing an "accumulator".
["reduce", <array_expression>, <lambda>, <initial_value>]
The lambda
for reduce
takes two arguments:
["lambda", ["<accumulator>", "<current_item>"], <expression>]
Here's how it works:
1. The <accumulator>
is initialized with the <initial_value>
.
2. The lambda is called for the first item in the array. The result of its <expression>
becomes the new value of the accumulator.
3. The lambda is called for the second item, using the updated accumulator. This repeats for all items.
4. The final value of the accumulator is the result of the reduce
operation.
reduce
Example: Summing an ArrayLet's calculate the total cost of all user plans in our system.
total_cost.json
:
["let",
[
["costs", {"array": [20, 5, 5]}]
],
["reduce",
["$", "/costs"],
["lambda", ["total", "cost"], ["+", ["$", "/total"], ["$", "/cost"]]],
0
]
]
total
starts at 0
(the initial value).total
is 0
, cost
is 20
. The lambda returns 0 + 20 = 20
. total
is now 20
.total
is 20
, cost
is 5
. The lambda returns 20 + 5 = 25
. total
is now 25
.total
is 25
, cost
is 5
. The lambda returns 25 + 5 = 30
. total
is now 30
.reduce
returns the final value of the accumulator.Output: 30
Now we can combine all three to answer complex questions. For example: "What is the total monthly cost of all active, premium plans?"
premium_revenue.json
:
["reduce",
["map",
["filter",
["get", ["$input"], "/users"],
["lambda", ["u"],
["&&",
["get", ["$", "/u"], "/active"],
["==", ["get", ["$", "/u"], "/plan"], "premium"]
]
]
],
["lambda", ["u"], ["get", ["$", "/u"], "/monthly_cost"]]
],
["lambda", ["total", "cost"], ["+", ["$", "/total"], ["$", "/cost"]]],
0
]
(Note: This example uses a logical AND operator, &&
, which we haven't formally covered but whose function is intuitive here. We'll cover it later.)
The Pipeline:
1. filter
: Finds all users who are both active AND have a premium plan. (Result: just Alice's object).
2. map
: Takes that filtered list and transforms it into a list of costs. (Result: [20]
).
3. reduce
: Takes that list of costs and sums it. (Result: 20
).
Output: 20
Beyond the core transformation operators (map
, filter
, reduce
), Computo provides specialized operators for common array query patterns. These operators make it easy to answer questions like "Does any item match this condition?" or "What's the first item that meets my criteria?"
find
: Locating the First MatchThe find
operator searches through an array and returns the first item that matches a condition. If no item matches, it returns null
.
["find", <array_expression>, ["lambda", ["<item_variable>"], <condition_expression>]]
Example: Finding the first premium user
find_premium_user.json
:
[
/* Find the first user with a premium plan */
"find",
["get", ["$input"], "/users"],
["lambda", ["user"], ["==", ["get", ["$", "/user"], "/plan"], "premium"]]
]
Output:
{
"active": true,
"name": "Alice",
"plan": "premium"
}
If no premium users existed, the result would be null
.
some
: Testing for Any MatchThe some
operator returns true
if at least one item in the array matches the condition, false
otherwise. It's perfect for answering "Is there any..." questions.
["some", <array_expression>, ["lambda", ["<item_variable>"], <condition_expression>]]
Example: Checking if any users are inactive
has_inactive_users.json
:
[
/* Check if there are any inactive users in the system */
"some",
["get", ["$input"], "/users"],
["lambda", ["user"], ["==", ["get", ["$", "/user"], "/active"], false]]
]
Output: true
(because Bob is inactive)
This is much more efficient than filtering and checking the count, especially for large arrays, because some
stops as soon as it finds the first match.
every
: Testing for Universal MatchThe every
operator returns true
if all items in the array match the condition, false
otherwise. It answers "Are all..." questions.
["every", <array_expression>, ["lambda", ["<item_variable>"], <condition_expression>]]
Example: Verifying all users are active
all_users_active.json
:
[
/* Verify that all users in the system are active */
"every",
["get", ["$input"], "/users"],
["lambda", ["user"], ["get", ["$", "/user"], "/active"]]
]
Output: false
(because Bob is inactive)
Note: every
returns true
for empty arrays, which is mathematically correct (vacuous truth).
flatMap
: Transforming and FlatteningThe flatMap
operator is like map
, but if the transformation function returns an array, those arrays are flattened into a single result array. This is useful when each item needs to be expanded into multiple items.
["flatMap", <array_expression>, ["lambda", ["<item_variable>"], <transform_expression>]]
Example: Expanding user permissions
Let's say each user has multiple roles, and we want a flat list of all permissions across all users.
expanded_users_input.json
:
{
"users": [
{
"name": "Alice",
"roles": ["admin", "editor"]
},
{
"name": "Bob",
"roles": ["viewer"]
},
{
"name": "Charlie",
"roles": ["editor", "contributor"]
}
]
}
all_roles.json
:
[
/* Extract all roles from all users into a single flat array */
"flatMap",
["get", ["$input"], "/users"],
["lambda", ["user"], ["get", ["$", "/user"], "/roles"]]
]
Output:
["admin", "editor", "viewer", "editor", "contributor"]
Compare this to regular map
, which would give you nested arrays:
[["admin", "editor"], ["viewer"], ["editor", "contributor"]]
These operators work beautifully together for complex queries:
user_validation_report.json
:
["obj",
/* Check various conditions about our user base */
["has_premium_users", [
"some",
["get", ["$input"], "/users"],
["lambda", ["u"], ["==", ["get", ["$", "/u"], "/plan"], "premium"]]
]],
["all_users_active", [
"every",
["get", ["$input"], "/users"],
["lambda", ["u"], ["get", ["$", "/u"], "/active"]]
]],
["first_inactive_user", [
"find",
["get", ["$input"], "/users"],
["lambda", ["u"], ["==", ["get", ["$", "/u"], "/active"], false]]
]],
["total_user_count", [
"count",
["get", ["$input"], "/users"]
]]
]
Output:
{
"has_premium_users": true,
"all_users_active": false,
"first_inactive_user": {
"name": "Bob",
"active": false,
"plan": "basic"
},
"total_user_count": 3
}
Beyond filtering and aggregation, Computo provides specialized operators for working with multiple arrays simultaneously and for accessing positional information within arrays. These operators are essential for complex data correlation and position-aware transformations.
zip
: Combining Arrays Element-WiseThe zip
operator takes two arrays and combines them into an array of pairs (two-element arrays). Each pair contains corresponding elements from the two input arrays.
["zip", <array1_expression>, <array2_expression>]
Example: Pairing names with scores
["zip",
{"array": ["Alice", "Bob", "Charlie"]},
{"array": [95, 87, 92]}
]
Output:
[
["Alice", 95],
["Bob", 87],
["Charlie", 92]
]
If the arrays have different lengths, zip
stops at the shorter array:
["zip",
{"array": ["Alice", "Bob", "Charlie"]},
{"array": [95, 87]}
]
Output:
[
["Alice", 95],
["Bob", 87]
]
Practical Example: Correlating data from different sources
correlate_data.json
:
["let",
[
["user_names", ["map", ["get", ["$input"], "/users"], ["lambda", ["u"], ["get", ["$", "/u"], "/name"]]]],
["user_scores", ["map", ["get", ["$input"], "/users"], ["lambda", ["u"], ["get", ["$", "/u"], "/score"]]]]
],
["map",
["zip", ["$", "/user_names"], ["$", "/user_scores"]],
["lambda", ["pair"],
["obj",
["name", ["get", ["$", "/pair"], "/0"]],
["score", ["get", ["$", "/pair"], "/1"]],
["grade", ["if", [">", ["get", ["$", "/pair"], "/1"], 90], "A", "B"]]
]
]
]
]
zipWith
: Custom Array CombinationWhile zip
creates pairs, zipWith
allows you to specify a custom function for combining corresponding elements from two arrays.
["zipWith", <array1_expression>, <array2_expression>, ["lambda", ["item1", "item2"], <combination_expression>]]
Example: Adding corresponding numbers
["zipWith",
{"array": [1, 2, 3]},
{"array": [10, 20, 30]},
["lambda", ["a", "b"], ["+", ["$", "/a"], ["$", "/b"]]]
]
Output:
[11, 22, 33]
Practical Example: Calculating weighted scores
["let",
[
["scores", {"array": [85, 92, 78]}],
["weights", {"array": [0.4, 0.3, 0.3]}]
],
["reduce",
["zipWith",
["$", "/scores"],
["$", "/weights"],
["lambda", ["score", "weight"], ["*", ["$", "/score"], ["$", "/weight"]]]
],
["lambda", ["total", "weighted"], ["+", ["$", "/total"], ["$", "/weighted"]]],
0
]
]
This calculates a weighted average: (85×0.4) + (92×0.3) + (78×0.3) = 84.2
mapWithIndex
: Position-Aware TransformationThe mapWithIndex
operator is like map
, but the lambda function receives both the item and its index position within the array.
["mapWithIndex", <array_expression>, ["lambda", ["item", "index"], <transform_expression>]]
Example: Creating numbered items
["mapWithIndex",
{"array": ["apple", "banana", "cherry"]},
["lambda", ["fruit", "index"],
["obj",
["position", ["+", ["$", "/index"], 1]], // 1-based numbering
["name", ["$", "/fruit"]]
]
]
]
Output:
[
{"position": 1, "name": "apple"},
{"position": 2, "name": "banana"},
{"position": 3, "name": "cherry"}
]
Practical Example: Processing data with position-dependent logic
["mapWithIndex",
["get", ["$input"], "/daily_temperatures"],
["lambda", ["temp", "day"],
["obj",
["day", ["+", ["$", "/day"], 1]],
["temperature", ["$", "/temp"]],
["day_type", ["if",
["==", ["%", ["$", "/day"], 7], 6], // Check if day % 7 == 6 (Sunday, 0-based)
"weekend",
"weekday"
]],
["temperature_trend", ["if",
["==", ["$", "/day"], 0],
"baseline",
["if", [">", ["$", "/temp"], 75], "above_average", "normal"]
]]
]
]
]
enumerate
: Creating Index-Value PairsThe enumerate
operator transforms an array into an array of [index, value]
pairs, making it easy to work with both the position and content of each element.
["enumerate", <array_expression>]
Example: Basic enumeration
["enumerate", {"array": ["red", "green", "blue"]}]
Output:
[
[0, "red"],
[1, "green"],
[2, "blue"]
]
Practical Example: Processing with position awareness
["map",
["enumerate", ["get", ["$input"], "/tasks"]],
["lambda", ["indexed_task"],
["obj",
["task_id", ["get", ["$", "/indexed_task"], "/0"]],
["task_name", ["get", ["get", ["$", "/indexed_task"], "/1"], "/name"]],
["priority", ["if",
["<", ["get", ["$", "/indexed_task"], "/0"], 3],
"high",
"normal"
]],
["status", ["get", ["get", ["$", "/indexed_task"], "/1"], "/status"]]
]
]
]
This creates task objects where the first 3 tasks (indices 0, 1, 2) are marked as high priority.
These operators work well together for complex data processing:
complex_array_processing.json
:
["let",
[
["data", {"array": [10, 25, 15, 30, 20]}],
["weights", {"array": [0.1, 0.2, 0.3, 0.2, 0.2]}]
],
["obj",
// Create indexed data points
["indexed_data", ["enumerate", ["$", "/data"]]],
// Calculate weighted values using zipWith
["weighted_values", ["zipWith",
["$", "/data"],
["$", "/weights"],
["lambda", ["value", "weight"], ["*", ["$", "/value"], ["$", "/weight"]]]
]],
// Apply position-dependent transformations
["processed_data", ["mapWithIndex",
["$", "/data"],
["lambda", ["value", "index"],
["obj",
["original_value", ["$", "/value"]],
["position", ["$", "/index"]],
["is_first_half", ["<", ["$", "/index"], 3]],
["relative_to_average", ["-", ["$", "/value"], 20]]
]
]
]],
// Pair original data with processed data using zip
["comparison", ["zip",
["$", "/data"],
["map", ["$", "/processed_data"], ["lambda", ["item"], ["get", ["$", "/item"], "/relative_to_average"]]]
]]
]
]
This demonstrates how the indexing operators enable sophisticated data analysis patterns that would be difficult to achieve with basic map
, filter
, and reduce
alone.
You have now completed the comprehensive functional toolkit for array processing.
* You learned to use filter
to selectively create new arrays based on a condition.
* You learned to use reduce
to aggregate an array's contents into a single result.
* You saw how chaining filter
and map
creates powerful data pipelines.
* You combined all three operators—filter
, map
, and reduce
—to answer a complex question about a data set in a single, expressive script.
* You discovered specialized query operators: find
for locating items, some
for existence checks, and every
for universal validation.
* You explored flatMap
for transforming and flattening nested data structures.
* You mastered array pairing operations: zip
for element-wise combination and zipWith
for custom combining functions.
* You learned position-aware transformations with mapWithIndex
for accessing both element and index.
* You discovered enumerate
for creating index-value pairs and processing with position awareness.
* You saw how these operators combine to create comprehensive data analysis reports and sophisticated correlation patterns.
You are now equipped to handle a vast range of data transformation challenges. The following chapters will build on this foundation, exploring how to apply these patterns to real-world integration scenarios.