purrr::reduce
Overview
reduce() takes a binary function (a function that takes two arguments) and applies it cumulatively to a list or vector. Given [a, b, c] and a function f, it computes f(f(a, b), c). This is called a “fold” or “accumulation” in functional programming.
It’s useful when you have a list of things that need to be combined into one value, merging data frames, building up a string, composing functions, or chaining operations.
Basic usage
library(purrr)
# Sum numbers by folding addition
nums <- c(1, 2, 3, 4, 5)
reduce(nums, `+`)
#> [1] 15
# That's equivalent to ((((1 + 2) + 3) + 4) + 5)
The function + is passed infix-style using the backtick syntax. You can also use a lambda, which makes the accumulation logic explicit: the first argument is the running total and the second is the current element. The lambda form is easier to read when the combining operation is more complex than simple addition, and it gives you the flexibility to name the accumulator and element parameters descriptively.
reduce(c(1, 2, 3, 4, 5), \(acc, x) acc + x)
#> [1] 15
Providing an initial value
Use .init to start with a seed value so the first call uses your seed rather than the first two elements. For an addition-based reduction, starting from zero is the natural identity, but for other operations like string concatenation or list building you would want a different seed that matches the type and semantics of what you are accumulating.
# Start from 10, then add each element
reduce(c(1, 2, 3), `+`, .init = 10)
#> [1] 16
When .init is provided, the first call is f(.init, first_element) instead of f(first_element, second_element). This means the final result always has a predictable starting point, which is especially important when the input list might be empty — without .init, reduce() on an empty list throws an error, but with a seed value it returns the seed directly.
Chaining with pipes
reduce() fits naturally in a %>% pipeline, letting you apply a sequence of data-wrangling steps and then collapse the result into a single object:
library(dplyr)
list(
tibble(x = 1:3, y = c("a", "b", "c")),
tibble(x = 4:6, y = c("d", "e", "f")),
tibble(x = 7:9, y = c("g", "h", "i"))
) |>
reduce(bind_rows)
#> # A tibble: 9 x 2
#> x y
#> <int> <chr>
#> 1 1 a
#> 2 2 b
#> 3 3 c
#> 4 4 d
#> 5 5 e
#> 6 6 f
#> 7 7 g
#> 8 8 h
#> 9 9 i
Here bind_rows() takes two data frames and combines them. reduce() applies it repeatedly until all data frames are merged. The same row-binding pattern works for any binary function that accepts two inputs and returns a single output of the same type, making reduce() a compact way to collapse a list of intermediate results after a split-apply-combine workflow without writing an explicit loop.
merge()ing named lists
configs <- list(
list(host = "localhost", port = 5432),
list(user = "alice", password = "secret"),
list(db = "production")
)
reduce(configs, function(acc, x) {
modifyList(acc, x)
})
#> $host
#> [1] "localhost"
#>
#> $port
#> [1] 5432
#>
#> $user
#> [1] "alice"
#>
#> $password
#> [1] "secret"
#>
#> $db
#> [1] "production"
modifyList() recursively merges two lists, so reduce() merges all of them together. This is a powerful pattern for combining configuration files or parameter sets that are stored as separate list elements, where later entries override earlier ones. Another classic use of reduce() is function composition, where you build a new function that applies several transformations in sequence by chaining them together from right to left.
Composing functions
A functional programming classic, build up a composed function from right to left:
f <- function(x) x + 1
g <- function(x) x * 2
h <- function(x) x - 3
# ((x - 3) * 2) + 1 — note the order is right to left
pipeline <- reduce(list(f, g, h), compose)
pipeline(10)
#> [1] result: ((10 - 3) * 2) + 1 = 15
reduce() with compose() builds the function composition. Note that compose() applies right-to-left, so the last function in the list is applied first.
reduce() vs for loops
reduce() replaces accumulation loops with a functional style that makes the intent explicit and avoids mutable state. Where a for loop requires a temporary variable that mutates on each iteration, reduce() expresses the same computation as “fold this function over this sequence.”
# Traditional for loop — mutable accumulator
result <- 1
for (x in c(2, 3, 4, 5)) {
result <- result * x
}
# reduce() — declarative, no side effects
result <- reduce(c(2, 3, 4, 5), `*`, .init = 1)
reduce_right() and accumulate()
reduce_right() folds from the last element to the first. For commutative operations like + and *, the result is identical — but for non-commutative operations like subtraction or string concatenation, the direction changes the outcome entirely. accumulate() returns every intermediate result rather than just the final value, which is useful for inspecting the progression of the accumulation.
# Left fold: (((1 - 2) - 3) - 4)
reduce(c(1, 2, 3, 4), `-`) #> [1] -8
# Right fold: (1 - (2 - (3 - 4)))
reduce_right(c(1, 2, 3, 4), `-`) #> [1] -2
# accumulate shows every step
accumulate(c(1, 2, 3, 4, 5), `+`) #> [1] 1 3 6 10 15
Short-Circuiting with done()
From purrr 1.0, you can terminate an accumulation early by returning done(value) from the reducing function. This is useful for find-first patterns — stopping iteration as soon as a condition is met, without processing the rest of the list.
values <- c(1, 2, 5, 3, 8, 12, 15)
accumulate(values, function(acc, x) {
if (x > 10) done(x) else x
})
#> [1] 1 2 5 3 8 12 # stops at first value > 10
When the input is empty, reduce() errors by default — provide .init to define a fallback return value for empty sequences. This is a common source of bugs in production code that processes dynamic query results, where an empty result set from a database query silently propagates to reduce() and causes an unexpected crash. Always supply .init when the input list might legitimately be empty under normal operating conditions.
reduce(integer(), `+`) #> Error
reduce(integer(), `+`, .init = 0) #> [1] 0
Common use cases
Merging lists or data frames, reduce(list_of_dfs, full_join) combines multiple tables by a common key.
Building a character string, reduce(vec, paste0) concatenates strings.
Chaining transformations, reduce(list(fn1, fn2, fn3), compose) builds a processing pipeline.
Set operations, reduce(list_of_sets, union) finds the union of many sets.
See also
- purrr::map() — Apply a function to each element and collect results.
- purrr::pmap() — Map over multiple inputs in parallel.
- purrr Functional Programming — Functional programming patterns with purrr including reduce.