From 5601db25aeaa4ea0feba37c9983a6854ea0ec4ca Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Wed, 5 Aug 2020 09:30:50 +0200 Subject: [PATCH] Add `.simplify` argument to `accumulate()` To disable the default simplification --- NEWS.md | 10 ++++++++++ R/reduce.R | 37 +++++++++++++++++++++++++----------- man/accumulate.Rd | 17 +++++++++++++++-- tests/testthat/test-reduce.R | 5 +++++ 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/NEWS.md b/NEWS.md index dc9070d6..8ffd670a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,16 @@ # purrr (development version) +* Because of historical reasons, `accumulate()` automatically + simplifies the accumulated list to an atomic vector if possible. To + control this behaviour, `accumulate()` gains a `.simplify` argument + which is `TRUE` by default for backward compatibility. + + Ideally the simplification step would be performed with `simplify()` + by composition. Since that ship has sailed, we've at least made it + possible to disable the simplification by setting `.simplify` to + `FALSE`. + * `accumulate()` now uses vctrs for simplifying the output. This ensures a more principled and flexible coercion behaviour. diff --git a/R/reduce.R b/R/reduce.R index 232b8942..ea969464 100644 --- a/R/reduce.R +++ b/R/reduce.R @@ -330,7 +330,6 @@ seq_len2 <- function(start, end) { #' needs to be 1 element shorter than the vector to be accumulated (`.x`). #' If `.init` is set, `.y` needs to be one element shorted than the #' concatenation of the initial value and `.x`. -#' #' @param .f For `accumulate()` `.f` is 2-argument function. The function will #' be passed the accumulated result or initial value as the first argument. #' The next value in sequence is passed as the second argument. @@ -342,17 +341,20 @@ seq_len2 <- function(start, end) { #' #' The accumulation terminates early if `.f` returns a value wrapped in #' a [done()]. -#' #' @param .init If supplied, will be used as the first value to start #' the accumulation, rather than using `.x[[1]]`. This is useful if #' you want to ensure that `reduce` returns a correct value when `.x` #' is empty. If missing, and `.x` is empty, will throw an error. -#' #' @param .dir The direction of accumulation as a string, one of #' `"forward"` (the default) or `"backward"`. See the section about #' direction below. +#' @param .simplify If `TRUE`, the default, the accumulated list of +#' results is simplified to an atomic vector if possible. See +#' [simplify()]. #' -#' @return A vector the same length of `.x` with the same names as `.x`. +#' @return A list the same length of `.x` with the same names as `.x`. +#' If `.simplify` is `TRUE`, the list is simplified to an atomic +#' vector if possible. #' #' If `.init` is supplied, the length is extended by 1. If `.x` has #' names, the initial value is given the name `".init"`, otherwise @@ -454,20 +456,22 @@ seq_len2 <- function(start, end) { #' ggtitle("Simulations of a random walk with drift") #' } #' @export -accumulate <- function(.x, .f, ..., .init, .dir = c("forward", "backward")) { +accumulate <- function(.x, + .f, + ..., + .init, + .dir = c("forward", "backward"), + .simplify = TRUE) { .dir <- arg_match(.dir, c("forward", "backward")) .f <- as_mapper(.f, ...) res <- reduce_impl(.x, .f, ..., .init = .init, .dir = .dir, .acc = TRUE) names(res) <- accumulate_names(names(.x), .init, .dir) - # It would be unappropriate to simplify the result rowwise with - # `accumulate()` because it has invariants defined in terms of - # `length()` rather than `vec_size()` - if (some(res, is.data.frame)) { - res + if (.simplify) { + acc_simplify(res) } else { - vec_simplify(res) + res } } #' @rdname accumulate @@ -491,6 +495,17 @@ accumulate_names <- function(nms, init, dir) { nms } +acc_simplify <- function(x) { + # It would be unappropriate to simplify the result rowwise with + # `accumulate()` because it has invariants defined in terms of + # `length()` rather than `vec_size()` + if (some(x, is.data.frame)) { + x + } else { + vec_simplify(x) + } +} + #' Reduce from the right (retired) #' #' @description diff --git a/man/accumulate.Rd b/man/accumulate.Rd index ce2ca1df..14c85525 100644 --- a/man/accumulate.Rd +++ b/man/accumulate.Rd @@ -5,9 +5,16 @@ \alias{accumulate2} \title{Accumulate intermediate results of a vector reduction} \usage{ -accumulate(.x, .f, ..., .init, .dir = c("forward", "backward")) accumulate2(.x, .y, .f, ..., .init) +accumulate( + .x, + .f, + ..., + .init, + .dir = c("forward", "backward"), + .simplify = TRUE +) } \arguments{ \item{.x}{A list or atomic vector.} @@ -35,13 +42,19 @@ is empty. If missing, and \code{.x} is empty, will throw an error.} \code{"forward"} (the default) or \code{"backward"}. See the section about direction below.} +\item{.simplify}{If \code{TRUE}, the default, the accumulated list of +results is simplified to an atomic vector if possible. See +\code{\link[=simplify]{simplify()}}.} + \item{.y}{For \code{accumulate2()} \code{.y} is the second argument of the pair. It needs to be 1 element shorter than the vector to be accumulated (\code{.x}). If \code{.init} is set, \code{.y} needs to be one element shorted than the concatenation of the initial value and \code{.x}.} } \value{ -A vector the same length of \code{.x} with the same names as \code{.x}. +A list the same length of \code{.x} with the same names as \code{.x}. +If \code{.simplify} is \code{TRUE}, the list is simplified to an atomic +vector if possible. If \code{.init} is supplied, the length is extended by 1. If \code{.x} has names, the initial value is given the name \code{".init"}, otherwise diff --git a/tests/testthat/test-reduce.R b/tests/testthat/test-reduce.R index 72e07714..6bde58fa 100644 --- a/tests/testthat/test-reduce.R +++ b/tests/testthat/test-reduce.R @@ -132,6 +132,11 @@ test_that("accumulate() does not simplify data frame rowwise", { expect_identical(out, exp) }) +test_that("accumulate() simplifies optionally", { + expect_identical(accumulate(1:3, function(x, y) y), 1:3) + expect_identical(accumulate(1:3, function(x, y) y, .simplify = FALSE), as.list(1:3)) +}) + # reduce2 -----------------------------------------------------------------