diff --git a/NAMESPACE b/NAMESPACE index bd433446..d4b725bf 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,27 +4,6 @@ S3method(as_mapper,character) S3method(as_mapper,default) S3method(as_mapper,list) S3method(as_mapper,numeric) -S3method(modify,character) -S3method(modify,default) -S3method(modify,double) -S3method(modify,integer) -S3method(modify,logical) -S3method(modify,pairlist) -S3method(modify2,character) -S3method(modify2,default) -S3method(modify2,double) -S3method(modify2,integer) -S3method(modify2,logical) -S3method(modify_at,character) -S3method(modify_at,default) -S3method(modify_at,double) -S3method(modify_at,integer) -S3method(modify_at,logical) -S3method(modify_if,character) -S3method(modify_if,default) -S3method(modify_if,double) -S3method(modify_if,integer) -S3method(modify_if,logical) S3method(print,purrr_function_compose) S3method(print,purrr_function_partial) S3method(print,purrr_rate_backoff) @@ -152,6 +131,7 @@ export(map2_dfr) export(map2_int) export(map2_lgl) export(map2_raw) +export(map2_vec) export(map_at) export(map_chr) export(map_dbl) @@ -163,6 +143,7 @@ export(map_if) export(map_int) export(map_lgl) export(map_raw) +export(map_vec) export(modify) export(modify2) export(modify_at) @@ -184,6 +165,7 @@ export(pmap_dfr) export(pmap_int) export(pmap_lgl) export(pmap_raw) +export(pmap_vec) export(possibly) export(prepend) export(pwalk) diff --git a/NEWS.md b/NEWS.md index 87b3033f..91b88d95 100644 --- a/NEWS.md +++ b/NEWS.md @@ -70,6 +70,10 @@ * `*_at()` can now take a function (or formula) that's passed the vector of element names and returns the elements to select. +* New `map_vec()`, `map2_vec()`, and `pmap_vec()` work on all types of vectors, + extending `map_lgl()`, `map_int()`, and friends so that you can easily work + with dates, factors, date-times and more (#435). + * New `keep_at()` and `discard_at()` that work like `keep()` and `discard()` but operation on element names rather than element contents (#817). @@ -78,6 +82,11 @@ * purrr is now licensed as MIT (#805). +* `modify()`, `modify_if()`, `modify_at()`, and `modify2()` are no longer + generics. We have discovered a simple implementation that no longer requires + genericity and methods were only provided by a very small number of packages + (#894). + * purrr now uses the base pipe (`|>`) and anonymous function short hand (`\(x)`), in all examples. This means that examples will no longer work in R 4.0 and earlier so in those versions of R, the examples are automatically converted @@ -122,6 +131,10 @@ * `map2()` and `pmap()` now recycle names of their first input if needed (#783). +* `modify()`, `modify_if()`, and `modify_at()` have been reimplemented using + vctrs principles. This shouldn't have an user facing impact, but it does + make the implementation much simpler. + ### Plucking * `vec_depth()` is now `pluck_depth()` and works with more types of input @@ -160,6 +173,8 @@ ## Minor improvements and bug fixes +* `modify()` no longer supports modifying calls or pairlists. + * `modify_depth()` is no longer a generic. This makes it more consistent with `map_depth()`. diff --git a/R/map.R b/R/map.R index 62f60e66..d069da5a 100644 --- a/R/map.R +++ b/R/map.R @@ -12,6 +12,9 @@ #' atomic vector of the indicated type (or die trying). For these functions, #' `.f` must return a length-1 vector of the appropriate type. #' +#' * `map_vec()` simplifies to the common type of the output. It works with +#' most types of simple vectors like Date, POSIXct, factors, etc. +#' #' * `walk()` calls `.f` for its side-effect and returns #' the input `.x`. #' @@ -36,15 +39,20 @@ #' for details. #' @returns #' The output length is determined by the length of the input. +#' The output names are determined by the input names. #' The output type is determined by the suffix: #' -#' * No suffix: a list. +#' * No suffix: a list; `.f()` can return anything. +#' +#' * `_lgl()`, `_int()`, `_dbl()`, `_chr()` return a logical, integer, double, +#' or character vector respectively; `.f()` must return a compatible atomic +#' vector of length 1. #' -#' * `_lgl`, `_int`, `_dbl`, `_chr` return a logical, integer, double, -#' or character vector respectively. It will be named if the input was named. +#' * `_vec()` return an atomic or S3 vector, the same type that `.f` returns. +#' `.f` can return pretty much any type of vector, as long as its length 1. #' #' * `walk()` returns the input `.x` (invisibly). This makes it easy to -#' use in a pipe. +#' use in a pipe. The return value of `.f()` is ignored. #' @export #' @family map variants #' @seealso [map_if()] for applying a function to only those elements @@ -116,23 +124,33 @@ map_lgl <- function(.x, .f, ..., .progress = FALSE) { #' @rdname map #' @export -map_chr <- function(.x, .f, ..., .progress = FALSE) { +map_int <- function(.x, .f, ..., .progress = FALSE) { .f <- as_mapper(.f, ...) - .Call(map_impl, environment(), ".x", ".f", "character", .progress) + .Call(map_impl, environment(), ".x", ".f", "integer", .progress) } #' @rdname map #' @export -map_int <- function(.x, .f, ..., .progress = FALSE) { +map_dbl <- function(.x, .f, ..., .progress = FALSE) { .f <- as_mapper(.f, ...) - .Call(map_impl, environment(), ".x", ".f", "integer", .progress) + .Call(map_impl, environment(), ".x", ".f", "double", .progress) } #' @rdname map #' @export -map_dbl <- function(.x, .f, ..., .progress = FALSE) { +map_chr <- function(.x, .f, ..., .progress = FALSE) { .f <- as_mapper(.f, ...) - .Call(map_impl, environment(), ".x", ".f", "double", .progress) + .Call(map_impl, environment(), ".x", ".f", "character", .progress) +} + +#' @rdname map +#' @param .ptype If `NULL`, the default, the output type is the common type +#' of the elements of the result. Otherwise, supply a "prototype" giving +#' the desired type of output. +#' @export +map_vec <- function(.x, .f, ..., .ptype = NULL, .progress = FALSE) { + out <- map(.x, .f, ..., .progress = .progress) + simplify_impl(out, ptype = .ptype) } #' @rdname map diff --git a/R/map2.R b/R/map2.R index a6a26fbd..d5027568 100644 --- a/R/map2.R +++ b/R/map2.R @@ -64,6 +64,12 @@ map2_chr <- function(.x, .y, .f, ..., .progress = NULL) { .Call(map2_impl, environment(), ".x", ".y", ".f", "character", .progress) } +#' @rdname map2 +#' @export +map2_vec <- function(.x, .y, .f, ..., .ptype = NULL, .progress = NULL) { + out <- map2(.x, .y, .f, ..., .progress = .progress) + simplify_impl(out, ptype = .ptype) +} #' @export #' @rdname map2 @@ -71,4 +77,3 @@ walk2 <- function(.x, .y, .f, ...) { map2(.x, .y, .f, ...) invisible(.x) } - diff --git a/R/modify.R b/R/modify.R index 1c76946b..d44f6b14 100644 --- a/R/modify.R +++ b/R/modify.R @@ -84,181 +84,82 @@ #' modify_if(iris, is.factor, as.character, .else = as.integer) #' @export modify <- function(.x, .f, ...) { - UseMethod("modify") -} -#' @rdname modify -#' @export -modify.default <- function(.x, .f, ...) { .f <- as_mapper(.f, ...) - for (i in seq_along(.x)) { - list_slice2(.x, i) <- .f(.x[[i]], ...) + if (vec_is_list(.x)) { + out <- map(vec_proxy(.x), .f, ...) + vec_restore(out, .x) + } else if (is.data.frame(.x)) { + size <- vec_size(.x) + out <- vec_proxy(.x) + out <- map(out, .f, ...) + out <- vec_recycle_common(!!!out, .size = size, .arg = "out") + out <- new_data_frame(out, n = size) + vec_restore(out, .x) + } else if (vec_is(.x)) { + map_vec(.x, .f, ..., .ptype = .x) + } else if (is.list(.x) || is.null(.x)) { + .x[] <- map(.x, .f, ...) + .x + } else { + cli::cli_abort( + "{.arg .x} must be a vector, list, or data frame, not {.obj_type_friendly {.x}}." + ) } - - .x -} -# TODO: Replace all the following methods with a generic strategy that -# implements sane coercion rules for base vectors -#' @export -modify.integer <- function (.x, .f, ...) { - .x[] <- map_int(.x, .f, ...) - .x -} -#' @export -modify.double <- function (.x, .f, ...) { - .x[] <- map_dbl(.x, .f, ...) - .x -} -#' @export -modify.character <- function (.x, .f, ...) { - .x[] <- map_chr(.x, .f, ...) - .x -} -#' @export -modify.logical <- function (.x, .f, ...) { - .x[] <- map_lgl(.x, .f, ...) - .x } -#' @export -modify.pairlist <- function(.x, .f, ...) { - as.pairlist(map(.x, .f, ...)) -} - - -# modify_if --------------------------------------------------------------- #' @rdname modify #' @inheritParams map_if #' @export modify_if <- function(.x, .p, .f, ..., .else = NULL) { - UseMethod("modify_if") -} -#' @rdname modify -#' @export -modify_if.default <- function(.x, .p, .f, ..., .else = NULL) { where <- where_if(.x, .p) - index <- seq_along(.x) - - .f <- as_mapper(.f, ...) - for (i in index[where]) { - list_slice2(.x, i) <- .f(.x[[i]], ...) - } + .x <- modify_where(.x, where, .f, ...) - if (!is_null(.else)) { + if (!is.null(.else)) { .else <- as_mapper(.else, ...) - for (i in index[!where]) { - list_slice2(.x, i) <- .else(.x[[i]], ...) - } - } - - .x -} -#' @export -modify_if.integer <- function(.x, .p, .f, ..., .else = NULL) { - modify_if_base(map_int, .x, .p, .true = .f, .false = .else, ...) -} -#' @export -modify_if.double <- function(.x, .p, .f, ..., .else = NULL) { - modify_if_base(map_dbl, .x, .p, .true = .f, .false = .else, ...) -} -#' @export -modify_if.character <- function(.x, .p, .f, ..., .else = NULL) { - modify_if_base(map_chr, .x, .p, .true = .f, .false = .else, ...) -} -#' @export -modify_if.logical <- function(.x, .p, .f, ..., .else = NULL) { - modify_if_base(map_lgl, .x, .p, .true = .f, .false = .else, ...) -} - -modify_if_base <- function(.fmap, .x, .p, .true, .false = NULL, ..., .error_call = caller_env()) { - where <- where_if(.x, .p, .error_call = .error_call) - .x[where] <- .fmap(.x[where], .true, ...) - - if (!is.null(.false)) { - .x[!where] <- .fmap(.x[!where], .false, ...) + .x <- modify_where(.x, !where, .else, ...) } .x } -# modify_at --------------------------------------------------------------- - #' @rdname modify #' @inheritParams map_at #' @export modify_at <- function(.x, .at, .f, ...) { - UseMethod("modify_at") -} -#' @rdname modify -#' @export -modify_at.default <- function(.x, .at, .f, ...) { - where <- where_at(.x, .at) - modify_if(.x, where, .f, ...) -} -#' @export -modify_at.integer <- function(.x, .at, .f, ...) { - where <- where_at(.x, .at) - .x[where] <- map_int(.x[where], .f, ...) - .x -} -#' @export -modify_at.double <- function(.x, .at, .f, ...) { - where <- where_at(.x, .at) - .x[where] <- map_dbl(.x[where], .f, ...) - .x -} -#' @export -modify_at.character <- function(.x, .at, .f, ...) { - where <- where_at(.x, .at) - .x[where] <- map_chr(.x[where], .f, ...) - .x -} -#' @export -modify_at.logical <- function(.x, .at, .f, ...) { where <- where_at(.x, .at) - .x[where] <- map_lgl(.x[where], .f, ...) - .x + modify_where(.x, where, .f, ...) } -# modify2 ----------------------------------------------------------------- - #' @rdname modify #' @export modify2 <- function(.x, .y, .f, ...) { - UseMethod("modify2") -} -#' @export -modify2.default <- function(.x, .y, .f, ...) { - modify2_base(map2, .x, .y, .f, ...) -} -# TODO: Improve genericity (see above) -#' @export -modify2.integer <- function(.x, .y, .f, ...) { - modify2_base(map2_int, .x, .y, .f, ...) -} -#' @export -modify2.double <- function(.x, .y, .f, ...) { - modify2_base(map2_dbl, .x, .y, .f, ...) -} -#' @export -modify2.character <- function(.x, .y, .f, ...) { - modify2_base(map2_chr, .x, .y, .f, ...) -} -#' @export -modify2.logical <- function(.x, .y, .f, ...) { - modify2_base(map2_lgl, .x, .y, .f, ...) -} - -modify2_base <- function(mapper, .x, .y, .f, ...) { .f <- as_mapper(.f, ...) - out <- mapper(.x, .y, .f, ...) - # if .x got recycled by map2 - if (length(out) > length(.x)) { - .x <- .x[rep(1L, length(out))] + if (vec_is_list(.x)) { + out <- map2(vec_proxy(.x), .y, .f, ...) + vec_restore(out, .x) + } else if (is.data.frame(.x)) { + size <- vec_size(.x) + out <- vec_proxy(.x) + out <- map2(out, .y, .f, ...) + out <- vec_recycle_common(!!!out, .size = size, .arg = "out") + out <- new_data_frame(out, n = size) + vec_restore(out, .x) + } else if (vec_is(.x)) { + map2_vec(.x, .y, .f, ..., .ptype = .x) + } else if (is.null(.x) || is.list(.x)) { + out <- map2(.x, .y, .f, ...) + if (length(out) > length(.x)) { + .x <- .x[rep(1L, length(out))] + } + .x[] <- out + .x + } else { + cli::cli_abort( + "{.arg .x} must be a vector, list, or data frame, not {.obj_type_friendly {.x}}." + ) } - .x[] <- out - .x } #' @rdname modify @@ -266,3 +167,31 @@ modify2_base <- function(mapper, .x, .y, .f, ...) { imodify <- function(.x, .f, ...) { modify2(.x, vec_index(.x), .f, ...) } + +# helpers ----------------------------------------------------------------- + +modify_where <- function(.x, .where, .f, ..., .error_call = caller_env()) { + if (vec_is_list(.x)) { + out <- vec_proxy(.x) + out[.where] <- map(out[.where], .f, ...) + vec_restore(out, .x) + } else if (is.data.frame(.x)) { + size <- vec_size(.x) + out <- vec_proxy(.x) + new <- map(out[.where], .f, ...) + out[.where] <- vec_recycle_common(!!!new, .size = size, .arg = "out") + out <- new_data_frame(out, n = size) + vec_restore(out, .x) + } else if (vec_is(.x)) { + .x[.where] <- map_vec(.x[.where], .f, ..., .ptype = .x) + .x + } else if (is.null(.x) || is.list(.x)) { + .x[.where] <- map(.x[.where], .f, ...) + .x + } else { + cli::cli_abort( + "{.arg .x} must be a vector, list, or data frame, not {.obj_type_friendly {.x}}.", + call = .error_call + ) + } +} diff --git a/R/pmap.R b/R/pmap.R index 4eed3634..2e9d15cf 100644 --- a/R/pmap.R +++ b/R/pmap.R @@ -129,6 +129,16 @@ pmap_chr <- function(.l, .f, ..., .progress = NULL) { .Call(pmap_impl, environment(), ".l", ".f", "character", .progress) } +#' @export +#' @rdname pmap +pmap_vec <- function(.l, .f, ..., .ptype = NULL, .progress = NULL) { + .f <- as_mapper(.f, ...) + + out <- pmap(.l, .f, ..., .progress = .progress) + simplify_impl(out, ptype = .ptype) +} + + #' @export #' @rdname pmap pwalk <- function(.l, .f, ...) { diff --git a/man/map.Rd b/man/map.Rd index db284774..5e1cd5f5 100644 --- a/man/map.Rd +++ b/man/map.Rd @@ -3,9 +3,10 @@ \name{map} \alias{map} \alias{map_lgl} -\alias{map_chr} \alias{map_int} \alias{map_dbl} +\alias{map_chr} +\alias{map_vec} \alias{walk} \title{Apply a function to each element of a vector} \usage{ @@ -13,12 +14,14 @@ map(.x, .f, ..., .progress = FALSE) map_lgl(.x, .f, ..., .progress = FALSE) -map_chr(.x, .f, ..., .progress = FALSE) - map_int(.x, .f, ..., .progress = FALSE) map_dbl(.x, .f, ..., .progress = FALSE) +map_chr(.x, .f, ..., .progress = FALSE) + +map_vec(.x, .f, ..., .ptype = NULL, .progress = FALSE) + walk(.x, .f, ...) } \arguments{ @@ -44,16 +47,24 @@ and the arguments that are the same come after \code{.f}.} \item{.progress}{Whether to show a progress bar. See \link{progress_bars} for details.} + +\item{.ptype}{If \code{NULL}, the default, the output type is the common type +of the elements of the result. Otherwise, supply a "prototype" giving +the desired type of output.} } \value{ The output length is determined by the length of the input. +The output names are determined by the input names. The output type is determined by the suffix: \itemize{ -\item No suffix: a list. -\item \verb{_lgl}, \verb{_int}, \verb{_dbl}, \verb{_chr} return a logical, integer, double, -or character vector respectively. It will be named if the input was named. +\item No suffix: a list; \code{.f()} can return anything. +\item \verb{_lgl()}, \verb{_int()}, \verb{_dbl()}, \verb{_chr()} return a logical, integer, double, +or character vector respectively; \code{.f()} must return a compatible atomic +vector of length 1. +\item \verb{_vec()} return an atomic or S3 vector, the same type that \code{.f} returns. +\code{.f} can return pretty much any type of vector, as long as its length 1. \item \code{walk()} returns the input \code{.x} (invisibly). This makes it easy to -use in a pipe. +use in a pipe. The return value of \code{.f()} is ignored. } } \description{ @@ -66,6 +77,8 @@ versions that return an object of the same type as the input. \item \code{map_lgl()}, \code{map_int()}, \code{map_dbl()} and \code{map_chr()} return an atomic vector of the indicated type (or die trying). For these functions, \code{.f} must return a length-1 vector of the appropriate type. +\item \code{map_vec()} simplifies to the common type of the output. It works with +most types of simple vectors like Date, POSIXct, factors, etc. \item \code{walk()} calls \code{.f} for its side-effect and returns the input \code{.x}. } diff --git a/man/map2.Rd b/man/map2.Rd index a3d4a21b..074568c5 100644 --- a/man/map2.Rd +++ b/man/map2.Rd @@ -6,6 +6,7 @@ \alias{map2_int} \alias{map2_dbl} \alias{map2_chr} +\alias{map2_vec} \alias{walk2} \title{Map over two inputs} \usage{ @@ -19,6 +20,8 @@ map2_dbl(.x, .y, .f, ..., .progress = NULL) map2_chr(.x, .y, .f, ..., .progress = NULL) +map2_vec(.x, .y, .f, ..., .ptype = NULL, .progress = NULL) + walk2(.x, .y, .f, ...) } \arguments{ @@ -42,16 +45,24 @@ and the arguments that are the same come after \code{.f}.} \item{.progress}{Whether to show a progress bar. See \link{progress_bars} for details.} + +\item{.ptype}{If \code{NULL}, the default, the output type is the common type +of the elements of the result. Otherwise, supply a "prototype" giving +the desired type of output.} } \value{ The output length is determined by the length of the input. +The output names are determined by the input names. The output type is determined by the suffix: \itemize{ -\item No suffix: a list. -\item \verb{_lgl}, \verb{_int}, \verb{_dbl}, \verb{_chr} return a logical, integer, double, -or character vector respectively. It will be named if the input was named. +\item No suffix: a list; \code{.f()} can return anything. +\item \verb{_lgl()}, \verb{_int()}, \verb{_dbl()}, \verb{_chr()} return a logical, integer, double, +or character vector respectively; \code{.f()} must return a compatible atomic +vector of length 1. +\item \verb{_vec()} return an atomic or S3 vector, the same type that \code{.f} returns. +\code{.f} can return pretty much any type of vector, as long as its length 1. \item \code{walk()} returns the input \code{.x} (invisibly). This makes it easy to -use in a pipe. +use in a pipe. The return value of \code{.f()} is ignored. } } \description{ diff --git a/man/modify.Rd b/man/modify.Rd index 5bc55681..30ff9a9c 100644 --- a/man/modify.Rd +++ b/man/modify.Rd @@ -2,27 +2,18 @@ % Please edit documentation in R/modify.R \name{modify} \alias{modify} -\alias{modify.default} \alias{modify_if} -\alias{modify_if.default} \alias{modify_at} -\alias{modify_at.default} \alias{modify2} \alias{imodify} \title{Modify elements selectively} \usage{ modify(.x, .f, ...) -\method{modify}{default}(.x, .f, ...) - modify_if(.x, .p, .f, ..., .else = NULL) -\method{modify_if}{default}(.x, .p, .f, ..., .else = NULL) - modify_at(.x, .at, .f, ...) -\method{modify_at}{default}(.x, .at, .f, ...) - modify2(.x, .y, .f, ...) imodify(.x, .f, ...) diff --git a/man/pmap.Rd b/man/pmap.Rd index 2d6ff389..409bd0a0 100644 --- a/man/pmap.Rd +++ b/man/pmap.Rd @@ -6,6 +6,7 @@ \alias{pmap_int} \alias{pmap_dbl} \alias{pmap_chr} +\alias{pmap_vec} \alias{pwalk} \title{Map over multiple input simultaneously (in "parallel")} \usage{ @@ -19,6 +20,8 @@ pmap_dbl(.l, .f, ..., .progress = NULL) pmap_chr(.l, .f, ..., .progress = NULL) +pmap_vec(.l, .f, ..., .ptype = NULL, .progress = NULL) + pwalk(.l, .f, ...) } \arguments{ @@ -48,16 +51,24 @@ and the arguments that are the same come after \code{.f}.} \item{.progress}{Whether to show a progress bar. See \link{progress_bars} for details.} + +\item{.ptype}{If \code{NULL}, the default, the output type is the common type +of the elements of the result. Otherwise, supply a "prototype" giving +the desired type of output.} } \value{ The output length is determined by the length of the input. +The output names are determined by the input names. The output type is determined by the suffix: \itemize{ -\item No suffix: a list. -\item \verb{_lgl}, \verb{_int}, \verb{_dbl}, \verb{_chr} return a logical, integer, double, -or character vector respectively. It will be named if the input was named. +\item No suffix: a list; \code{.f()} can return anything. +\item \verb{_lgl()}, \verb{_int()}, \verb{_dbl()}, \verb{_chr()} return a logical, integer, double, +or character vector respectively; \code{.f()} must return a compatible atomic +vector of length 1. +\item \verb{_vec()} return an atomic or S3 vector, the same type that \code{.f} returns. +\code{.f} can return pretty much any type of vector, as long as its length 1. \item \code{walk()} returns the input \code{.x} (invisibly). This makes it easy to -use in a pipe. +use in a pipe. The return value of \code{.f()} is ignored. } } \description{ diff --git a/tests/testthat/_snaps/map.md b/tests/testthat/_snaps/map.md index 2f024547..44eea4cf 100644 --- a/tests/testthat/_snaps/map.md +++ b/tests/testthat/_snaps/map.md @@ -22,3 +22,25 @@ Error in `purrr::map_int()`: ! Result 2 must have length 1, not 2. +# requires output be length 1 and have common type + + Code + map_vec(1:2, ~ rep(1, .x)) + Condition + Error in `map_vec()`: + ! All elements must be size 1. + i `out[[2]]` is size 2. + Code + map_vec(1:2, ~ if (.x == 1) factor("x") else 1) + Condition + Error: + ! Can't combine `..1` > and `..2` . + +# can enforce .ptype + + Code + map_vec(1:2, ~ factor("x"), .ptype = integer()) + Condition + Error: + ! Can't convert > to . + diff --git a/tests/testthat/_snaps/map2.md b/tests/testthat/_snaps/map2.md index 8f96fc4b..98adc018 100644 --- a/tests/testthat/_snaps/map2.md +++ b/tests/testthat/_snaps/map2.md @@ -1,28 +1,35 @@ -# map2 can't simplify if elements longer than length 1 +# verifies result types and length Code - map2_int(1:4, 5:8, range) + map2_int(1, 1, ~"x") + Condition + Error: + ! Can't coerce element 1 from a character to a integer + Code + map2_int(1, 1, ~ 1:2) Condition Error in `map2_int()`: ! Result 1 must have length 1, not 2. + Code + map2_vec(1, 1, ~1, .ptype = character()) + Condition + Error: + ! Can't convert to . -# fails on non-vectors +# requires vector inputs Code map2(environment(), "a", identity) Condition Error in `map2()`: ! `.x` must be a vector, not an environment. - ---- - Code - map2("a", environment(), identity) + map2("a", environment(), "a", identity) Condition Error in `map2()`: ! `.y` must be a vector, not an environment. -# map2 recycles inputs +# recycles inputs Code map2(1:2, 1:3, `+`) @@ -31,4 +38,11 @@ ! Mapped vectors must have consistent lengths: * `.x` has length 2 * `.y` has length 3 + Code + map2(1:2, integer(), `+`) + Condition + Error in `map2()`: + ! Mapped vectors must have consistent lengths: + * `.x` has length 2 + * `.y` has length 0 diff --git a/tests/testthat/_snaps/modify.md b/tests/testthat/_snaps/modify.md index 8d6606ff..94633130 100644 --- a/tests/testthat/_snaps/modify.md +++ b/tests/testthat/_snaps/modify.md @@ -1,3 +1,66 @@ +# modfiying data.frame preserves type and size + + Code + modify(df1, ~ integer()) + Condition + Error in `modify()`: + ! Can't recycle `out$x` (size 0) to size 2. + Code + modify(df1, ~ 1:4) + Condition + Error in `modify()`: + ! Can't recycle `out$x` (size 4) to size 2. + Code + modify_at(df1, 2, ~ integer()) + Condition + Error in `modify_where()`: + ! Can't recycle `out$y` (size 0) to size 2. + Code + modify2(df1, list(1, 1:3), ~.y) + Condition + Error in `modify2()`: + ! Can't recycle `out$y` (size 3) to size 2. + +# bad type has useful error + + Code + modify(1:3, ~"foo") + Condition + Error: + ! Can't convert to . + Code + modify_at(1:3, 1, ~"foo") + Condition + Error: + ! Can't convert to . + Code + modify_if(1:3, is_integer, ~"foo") + Condition + Error: + ! Can't convert to . + Code + modify2(1:3, "foo", ~.y) + Condition + Error: + ! Can't convert to . + +# modify2() recycles arguments + + Code + modify2(1:3, integer(), `+`) + Condition + Error in `map2()`: + ! Mapped vectors must have consistent lengths: + * `.x` has length 3 + * `.y` has length 0 + Code + modify2(1:3, 1:4, `+`) + Condition + Error in `map2()`: + ! Mapped vectors must have consistent lengths: + * `.x` has length 3 + * `.y` has length 4 + # modify_if() requires predicate functions Code @@ -6,11 +69,26 @@ Error in `modify_if()`: ! `.p()` must return a single `TRUE` or `FALSE`, not `NA`. ---- +# user friendly error for non-supported cases Code - modify_if(1:2, ~ c(TRUE, FALSE), ~"foo") + modify(mean, identity) + Condition + Error in `modify()`: + ! `.x` must be a vector, list, or data frame, not a function. + Code + modify_if(mean, TRUE, identity) Condition Error in `modify_if()`: - ! `.p()` must return a single `TRUE` or `FALSE`, not a logical vector. + ! `.x` must be a vector, list, or data frame, not a function. + Code + modify_at(mean, "x", identity) + Condition + Error in `modify_at()`: + ! `.x` must be a vector, list, or data frame, not a function. + Code + modify2(mean, 1, identity) + Condition + Error in `modify2()`: + ! `.x` must be a vector, list, or data frame, not a function. diff --git a/tests/testthat/_snaps/pmap.md b/tests/testthat/_snaps/pmap.md index 63000e4d..888486ca 100644 --- a/tests/testthat/_snaps/pmap.md +++ b/tests/testthat/_snaps/pmap.md @@ -1,31 +1,43 @@ -# input must be a list of vectors +# verifies result types and length + + Code + pmap_int(list(1), ~"x") + Condition + Error: + ! Can't coerce element 1 from a character to a integer + Code + pmap_int(list(1), ~ 1:2) + Condition + Error in `pmap_int()`: + ! Result 1 must have length 1, not 2. + Code + pmap_vec(list(1), ~1, .ptype = character()) + Condition + Error: + ! Can't convert to . + +# requires list of vectors Code pmap(environment(), identity) Condition Error in `pmap()`: ! `.l` must be a list, not an environment. - ---- - Code pmap(list(environment()), identity) Condition Error in `pmap()`: ! `.l[[1]]` must be a vector, not an environment. -# inputs are recycled +# recycles inputs Code - pmap(list(1:2, 1:3), identity) + pmap(list(1:2, 1:3), `+`) Condition Error in `pmap()`: ! `.l[[2]]` must have length 1 or 2, not 3. - ---- - Code - pmap(list(1:2, integer()), identity) + pmap(list(1:2, integer()), `+`) Condition Error in `pmap()`: ! `.l[[2]]` must have length 1 or 2, not 0. diff --git a/tests/testthat/test-map.R b/tests/testthat/test-map.R index 9da3f20f..86ada6f3 100644 --- a/tests/testthat/test-map.R +++ b/tests/testthat/test-map.R @@ -86,3 +86,34 @@ test_that("map() with empty input copies names", { expect_identical(map_dbl(named_list, identity), named(dbl())) expect_identical(map_chr(named_list, identity), named(chr())) }) + + +# map_vec ----------------------------------------------------------------- + +test_that("still iterates using [[", { + df <- data.frame(x = 1, y = 2, z = 3) + expect_equal(map_vec(df, length), c(x = 1, y = 1, z = 1)) +}) + +test_that("requires output be length 1 and have common type", { + expect_snapshot(error = TRUE, { + map_vec(1:2, ~ rep(1, .x)) + map_vec(1:2, ~ if (.x == 1) factor("x") else 1) + }) +}) + +test_that("row-binds data frame output", { + out <- map_vec(1:2, ~ data.frame(x = .x)) + expect_equal(out, data.frame(x = 1:2)) +}) + +test_that("concatenates list output", { + out <- map_vec(1:2, ~ list(.x)) + expect_equal(out, list(1, 2)) +}) + +test_that("can enforce .ptype", { + expect_snapshot(error = TRUE, { + map_vec(1:2, ~ factor("x"), .ptype = integer()) + }) +}) diff --git a/tests/testthat/test-map2.R b/tests/testthat/test-map2.R index 109e5c69..55ebfbc4 100644 --- a/tests/testthat/test-map2.R +++ b/tests/testthat/test-map2.R @@ -1,46 +1,62 @@ -test_that("map2 can't simplify if elements longer than length 1", { - expect_snapshot(map2_int(1:4, 5:8, range), error = TRUE) +test_that("x and y mapped to first and second argument", { + expect_equal(map2(1, 2, function(x, y) x), list(1)) + expect_equal(map2(1, 2, function(x, y) y), list(2)) }) -test_that("fails on non-vectors", { - expect_snapshot(map2(environment(), "a", identity), error = TRUE) - expect_snapshot(map2("a", environment(), identity), error = TRUE) +test_that("variants return expected types", { + x <- list(1, 2, 3) + expect_true(is_bare_list(map2(x, 0, ~ 1))) + expect_true(is_bare_logical(map2_lgl(x, 0, ~ TRUE))) + expect_true(is_bare_integer(map2_int(x, 0, ~ 1))) + expect_true(is_bare_double(map2_dbl(x, 0, ~ 1.5))) + expect_true(is_bare_character(map2_chr(x, 0, ~ "x"))) + expect_equal(walk2(x, 0, ~ "x"), x) + + x <- list(FALSE, 1L, 1) + expect_true(is_bare_double(map2_vec(x, 0, ~ .x))) }) -test_that("map2 recycles inputs", { - expect_equal(map2(1, 1, `+`), list(2)) +test_that("verifies result types and length", { + expect_snapshot(error = TRUE, { + map2_int(1, 1, ~ "x") + map2_int(1, 1, ~ 1:2) + map2_vec(1, 1, ~ 1, .ptype = character()) + }) +}) + +test_that("requires vector inputs", { + expect_snapshot(error = TRUE, { + map2(environment(), "a", identity) + map2("a", environment(), "a", identity) + }) +}) +test_that("recycles inputs", { expect_equal(map2(1:2, 1, `+`), list(2, 3)) expect_equal(map2(integer(), 1, `+`), list()) expect_equal(map2(NULL, 1, `+`), list()) - expect_snapshot(map2(1:2, 1:3, `+`), error = TRUE) + expect_snapshot(error = TRUE, { + map2(1:2, 1:3, `+`) + map2(1:2, integer(), `+`) + }) }) -test_that("map2 takes only names from x", { - x1 <- 1:3 - x2 <- set_names(x1) +test_that("only takes names from x", { + x1 <- 1:2 + x2 <- set_names(x1, letters[1:2]) + x3 <- set_names(x1, "") - expect_equal(names(map2(x1, x2, `+`)), NULL) - expect_equal(names(map2(x2, x1, `+`)), names(x2)) -}) - -test_that("map2 always returns a list", { - expect_bare(map2(mtcars, 0, ~mtcars), "list") -}) + expect_named(map2(x1, 1, `+`), NULL) + expect_named(map2(x2, 1, `+`), c("a", "b")) + expect_named(map2(x3, 1, `+`), c("", "")) -test_that("map2() with empty input copies names", { - named_list <- named(list()) - expect_identical( map2(named_list, list(), identity), named(list())) - expect_identical(map2_lgl(named_list, list(), identity), named(lgl())) - expect_identical(map2_int(named_list, list(), identity), named(int())) - expect_identical(map2_dbl(named_list, list(), identity), named(dbl())) - expect_identical(map2_chr(named_list, list(), identity), named(chr())) + # recycling them if needed (#779) + x4 <- c(a = 1) + expect_named(map2(x4, 1:2, `+`), c("a", "a")) }) -test_that("map2() recycle names (#779)", { - expect_identical( - map2(c(a = 1), 1:2, ~ .x), - list(a = 1, a = 1) - ) +test_that("don't evaluate symbolic objects (#428)", { + map2(exprs(1 + 2), NA, ~ expect_identical(.x, quote(1 + 2))) + walk2(exprs(1 + 2), NA, ~ expect_identical(.x, quote(1 + 2))) }) diff --git a/tests/testthat/test-modify.R b/tests/testthat/test-modify.R index af6a4bde..bd257e97 100644 --- a/tests/testthat/test-modify.R +++ b/tests/testthat/test-modify.R @@ -1,83 +1,83 @@ -test_that("modify returns same type as input", { - df1 <- data.frame(x = 1:3, y = 4:6) - df2 <- data.frame(x = 2:4, y = 5:7) - expect_equal(modify(df1, ~ .x + 1), df2) +# Input types, ordered by apperance +test_that("modifying vectors list preserves type", { x1 <- vctrs::list_of(c(1, 2), c(3, 6, 9)) x2 <- vctrs::list_of(c(2, 3), c(4, 7, 10)) expect_equal(modify(x1, ~ .x + 1), x2) }) -test_that("modify_if/modify_at return same type as input", { - df1 <- data.frame(x = "a", y = 2, stringsAsFactors = FALSE) - exp <- data.frame(x = "A", y = 2, stringsAsFactors = FALSE) +test_that("modfiying data.frame preserves type and size", { + df1 <- data.frame(x = 1:2, y = 2:1) + expect_equal(modify(df1, ~ 1), data.frame(x = c(1, 1), y = c(1, 1))) + expect_equal(modify_at(df1, 1, ~ 1), data.frame(x = c(1, 1), y = 2:1)) + expect_equal(modify2(df1, df1, ~ .x + .y), data.frame(x = c(2, 4), y = c(4, 2))) - df2a <- modify_if(df1, is.character, toupper) - expect_equal(df2a, exp) + df2 <- new_data_frame(n = 5L) + expect_equal(modify(df2, ~ 1), df2) - df2b <- modify_at(df1, "x", toupper) - expect_equal(df2b, exp) + expect_snapshot(error = TRUE, { + modify(df1, ~ integer()) + modify(df1, ~ 1:4) + + modify_at(df1, 2, ~ integer()) + modify2(df1, list(1, 1:3), ~ .y) + }) }) -test_that("negative .at omits locations", { - x <- list(1, 2, 3) - out <- modify_at(x, -1, ~ .x * 2) - expect_equal(out, list(1, 4, 6)) +test_that("data.frames are modified by column, not row", { + df1 <- data.frame(x = 1:3, y = letters[1:3]) + df2 <- data.frame(x = 2:4, y = letters[1:3]) + + expect_equal(modify(df1, ~ if (is.numeric(.x)) .x + 1 else .x), df2) + expect_equal(modify_at(df1, "x", ~ .x + 1), df2) }) -test_that("modify works with calls and pairlists", { - out <- modify(quote(f(x)), ~ quote(z)) - expect_equal(out, quote(z(z))) +test_that("modifying vectors preserves type", { + expect_identical(modify(1:3, ~ .x + 1), 2:4) + expect_equal(modify("a", ~ factor("b")), "b") - out <- modify(pairlist(1, 2), ~ . + 1) - expect_equal(out, pairlist(2, 3)) + expect_identical(modify_if(1:2, ~ .x %% 2 == 0, ~ 3), c(1L, 3L)) + expect_identical(modify_at(1:2, 2, ~ 3), c(1L, 3L)) + expect_identical(modify2(1:2, c(0, 1), `+`), c(1L, 3L)) }) -test_that("modify{,_at,_if} preserves atomic vector classes", { - expect_type(modify("a", identity), "character") - expect_type(modify(1L, identity), "integer") - expect_type(modify(1, identity), "double") - expect_type(modify(TRUE, identity), "logical") - - expect_type(modify_at("a", 1L, identity), "character") - expect_type(modify_at(1L, 1L, identity), "integer") - expect_type(modify_at(1, 1L, identity), "double") - expect_type(modify_at(TRUE, 1L, identity), "logical") - - expect_type(modify_if("a", TRUE, identity), "character") - expect_type(modify_if(1L, TRUE, identity), "integer") - expect_type(modify_if(1, TRUE, identity), "double") - expect_type(modify_if(TRUE, TRUE, identity), "logical") +test_that("bad type has useful error", { + expect_snapshot(error = TRUE, { + modify(1:3, ~ "foo") + modify_at(1:3, 1, ~ "foo") + modify_if(1:3, is_integer, ~ "foo") + modify2(1:3, "foo", ~ .y) + }) }) -test_that("modify() and variants implement sane coercion rules for base vectors", { - expect_error(modify(1:3, ~ "foo"), "Can't coerce") - expect_error(modify_at(1:3, 1, ~ "foo"), "Can't coerce") - expect_error(modify_if(1:3, is_integer, ~ "foo"), "Can't coerce") - expect_error(modify2(1:3, "foo", ~ .y), "Can't coerce") +test_that("modifying lists preserves NULLs", { + l <- list(a = 1, b = NULL, c = 3) + expect_equal(modify(l, identity), l) + expect_equal(modify_at(l, "b", identity), l) + expect_equal(modify_if(l, is.null, identity), l) + expect_equal( + modify2(l, list(NULL, 1, NULL), ~ .y), + list(a = NULL, b = 1, c = NULL) + ) }) -test_that("modify2() and imodify() preserve type of first input", { - x <- c(foo = 1L, bar = 2L) - y <- c(TRUE, FALSE) - expect_identical(modify2(x, y, ~ if (.y) .x else 0L), c(foo = 1L, bar = 0L)) +test_that("can modify non-vector lists", { + notlist <- function(...) structure(list(...), class = "notlist") + x <- notlist(x = 1, y = "a") - out <- imodify(mtcars, paste) - expect_s3_class(out, "data.frame") - expect_identical(out$vs, paste(mtcars$vs, "vs")) -}) + expect_equal(modify(x, ~ 2), notlist(x = 2, y = 2)) + expect_equal(modify_if(x, is.character, ~ 2), notlist(x = 1, y = 2)) + expect_equal(modify_at(x, "y", ~ 2), notlist(x = 1, y = 2)) -test_that("modify2() recycles arguments", { - expect_identical(modify2(1:3, 1L, `+`), int(2, 3, 4)) - expect_identical(modify2(1, 1:3, `+`), dbl(2, 3, 4)) - expect_identical(modify2(mtcars, seq_along(mtcars), `+`)$carb, mtcars$carb + ncol(mtcars)) - expect_identical(modify2(mtcars, 1, `+`)$carb, mtcars$carb + 1L) + local_bindings( + "[.notlist" = function(...) structure(NextMethod(), class = "notlist"), + .env = globalenv() + ) + expect_equal(modify2(x, list(3, 4), ~ .y), notlist(x = 3, y = 4)) + expect_equal(modify2(notlist(1), list(3, 4), ~ .y), notlist(3, 4)) }) -test_that("modify_if() requires predicate functions", { - expect_snapshot(modify_if(list(1, 2), ~ NA, ~ "foo"), error = TRUE) - expect_snapshot(modify_if(1:2, ~ c(TRUE, FALSE), ~ "foo"), error = TRUE) -}) +# other properties -------------------------------------------------------- test_that("`.else` modifies false elements", { exp <- modify_if(iris, negate(is.factor), as.integer) @@ -90,28 +90,44 @@ test_that("`.else` modifies false elements", { expect_equal(modify_if(c("a", "b"), ~ .x == "a", ~ "A", .else = ~ "B"), c("A", "B")) }) -test_that("modify family preserves NULLs", { - l <- list(a = 1, b = NULL, c = 3) - expect_equal(modify(l, identity), l) - expect_equal(modify_at(l, "b", identity), l) - expect_equal(modify_if(l, is.null, identity), l) - expect_equal( - modify(l, ~ if (!is.null(.x)) .x + .y, 10), - list(a = 11, b = NULL, c = 13) - ) +test_that("modify_at() can use tidyselect", { + local_options(lifecycle_verbosity = "quiet") + + df <- data.frame(x = 1, y = 3) expect_equal( - modify_if(list(1, 2), ~ .x == 2, ~NULL), - list(1, NULL) + modify_at(df, vars(x), ~ 2), + data.frame(x = 2, y = 3) ) }) -test_that("modify_at() can use tidyselect", { - skip_if_not_installed("tidyselect") - local_options(lifecycle_verbosity = "quiet") +test_that("imodify uses index", { + expect_equal(imodify(list(2), ~ .y), list(1)) + expect_equal(imodify(list(a = 2), ~ .y), list(a = "a")) +}) + +# input validation -------------------------------------------------------- + +test_that("modify2() recycles arguments", { + expect_equal(modify2(1:3, 1L, `+`), c(2, 3, 4)) + expect_equal(modify2(1, 1:3, `+`), c(2, 3, 4)) + + expect_snapshot(error = TRUE, { + modify2(1:3, integer(), `+`) + modify2(1:3, 1:4, `+`) + }) +}) + +test_that("modify_if() requires predicate functions", { + expect_snapshot(error = TRUE, { + modify_if(list(1, 2), ~ NA, ~ "foo") + }) +}) - one <- modify_at(mtcars, vars(cyl, am), as.character) - expect_bare(one$cyl, "character") - expect_bare(one$am, "character") - two <- modify_at(mtcars, vars(tidyselect::contains("cyl")), as.character) - expect_bare(two$cyl, "character") +test_that("user friendly error for non-supported cases", { + expect_snapshot(error = TRUE, { + modify(mean, identity) + modify_if(mean, TRUE, identity) + modify_at(mean, "x", identity) + modify2(mean, 1, identity) + }) }) diff --git a/tests/testthat/test-pmap.R b/tests/testthat/test-pmap.R index 8c0ceede..1a66b611 100644 --- a/tests/testthat/test-pmap.R +++ b/tests/testthat/test-pmap.R @@ -1,59 +1,83 @@ -test_that("input must be a list of vectors", { - expect_snapshot(pmap(environment(), identity), error = TRUE) - expect_snapshot(pmap(list(environment()), identity), error = TRUE) +test_that(".f called with named arguments", { + x <- list(x = 1, 2, y = 3) + expect_equal(pmap(x, list), list(x)) }) -test_that("inputs are recycled", { - expect_equal(pmap(list(1, 1), c), list(c(1, 1))) - expect_equal(pmap(list(1:2, 1), c), list(c(1, 1), c(2, 1))) +test_that("... are passed after varying argumetns", { + out <- pmap(list(x = 1:2), list, n = 1:2) + expect_equal(out, list( + list(x = 1, n = 1:2), + list(x = 2, n = 1:2) + )) +}) - expect_equal(pmap(list(list(), 1), ~ 1), list()) - expect_equal(pmap(list(NULL, 1), ~ 1), list()) +test_that("variants return expected types", { + l <- list(list(1, 2, 3)) + expect_true(is_bare_list(pmap(l, ~ 1))) + expect_true(is_bare_logical(pmap_lgl(l, ~ TRUE))) + expect_true(is_bare_integer(pmap_int(l, ~ 1))) + expect_true(is_bare_double(pmap_dbl(l, ~ 1.5))) + expect_true(is_bare_character(pmap_chr(l, ~ "x"))) + expect_equal(pwalk(l, ~ "x"), l) - expect_snapshot(pmap(list(1:2, 1:3), identity), error = TRUE) - expect_snapshot(pmap(list(1:2, integer()), identity), error = TRUE) + l <- list(list(FALSE, 1L, 1)) + expect_true(is_bare_double(pmap_vec(l, ~ .x))) }) -test_that(".f called with named arguments", { - out <- pmap(list(x = 1, 2, y = 3), list)[[1]] - expect_equal(names(out), c("x", "", "y")) +test_that("verifies result types and length", { + expect_snapshot(error = TRUE, { + pmap_int(list(1), ~ "x") + pmap_int(list(1), ~ 1:2) + pmap_vec(list(1), ~ 1, .ptype = character()) + }) }) -test_that("names are preserved", { - out <- pmap(list(c(x = 1, y = 2), 3:4), list) - expect_equal(names(out), c("x", "y")) +test_that("requires list of vectors", { + expect_snapshot(error = TRUE, { + pmap(environment(), identity) + pmap(list(environment()), identity) + }) }) -test_that("pmap() recycles names (#779)", { - expect_identical( - pmap(list(c(a = 1), 1:2), ~ .x), - list(a = 1, a = 1) - ) -}) +test_that("recycles inputs", { + expect_equal(pmap(list(1:2, 1), `+`), list(2, 3)) + expect_equal(pmap(list(integer(), 1), `+`), list()) + expect_equal(pmap(list(NULL, 1), `+`), list()) -test_that("... are passed on", { - out <- pmap(list(x = 1:2), list, n = 1) - expect_equal(out, list( - list(x = 1, n = 1), - list(x = 2, n = 1) - )) + expect_snapshot(error = TRUE, { + pmap(list(1:2, 1:3), `+`) + pmap(list(1:2, integer()), `+`) + }) }) -test_that("outputs are suffixes have correct type", { - x <- 1:3 - expect_bare(pmap_lgl(list(x), is.numeric), "logical") - expect_bare(pmap_int(list(x), length), "integer") - expect_bare(pmap_dbl(list(x), mean), "double") - expect_bare(pmap_chr(list(x), paste), "character") +test_that("only takes names from x", { + x1 <- 1:2 + x2 <- set_names(x1, letters[1:2]) + x3 <- set_names(x1, "") + + expect_named(pmap(list(x1, x2), `+`), NULL) + expect_named(pmap(list(x2, x2), `+`), c("a", "b")) + expect_named(pmap(list(x3, x2), `+`), c("", "")) + + # recycling them if needed (#779) + x4 <- c(a = 1) + expect_named(pmap(list(x4, 1:2), `+`), c("a", "a")) }) -test_that("pmap on data frames performs rowwise operations", { - mtcars2 <- mtcars[c("mpg", "cyl")] - expect_length(pmap(mtcars2, paste), nrow(mtcars)) - expect_bare(pmap_lgl(mtcars2, function(mpg, cyl) mpg > cyl), "logical") - expect_bare(pmap_int(mtcars2, function(mpg, cyl) as.integer(cyl)), "integer") - expect_bare(pmap_dbl(mtcars2, function(mpg, cyl) mpg + cyl), "double") - expect_bare(pmap_chr(mtcars2, paste), "character") +test_that("avoid expensive [[ method on data frames", { + local_bindings( + `[[.mydf` = function(x, ...) stop("Not allowed!"), + .env = global_env() + ) + + df <- data.frame(x = 1:2, y = 2:1) + class(df) <- c("mydf", "data.frame") + + expect_equal(pmap(df, list), list(list(x = 1, y = 2), list(x = 2, y = 1))) + expect_equal(pmap_lgl(df, ~ TRUE), c(TRUE, TRUE)) + expect_equal(pmap_int(df, ~ 2), c(2, 2)) + expect_equal(pmap_dbl(df, ~ 3.5), c(3.5, 3.5)) + expect_equal(pmap_chr(df, ~ "x"), c("x", "x")) }) test_that("pmap works with empty lists", { @@ -66,21 +90,7 @@ test_that("preserves S3 class of input vectors (#358)", { expect_output(pwalk(list(date), print), format(date)) }) -test_that("walk2() and pwalk() don't evaluate symbolic objects", { - walk2(exprs(1 + 2), NA, ~ expect_identical(.x, quote(1 + 2))) - pwalk(list(exprs(1 + 2)), ~ expect_identical(.x, quote(1 + 2))) -}) - -test_that("map2() and pmap() don't evaluate symbolic objects", { - map2(exprs(1 + 2), NA, ~ expect_identical(.x, quote(1 + 2))) +test_that("don't evaluate symbolic objects (#428)", { pmap(list(exprs(1 + 2)), ~ expect_identical(.x, quote(1 + 2))) -}) - -test_that("pmap() with empty input copies names", { - named_list <- list(named(list())) - expect_identical( pmap(named_list, identity), named(list())) - expect_identical(pmap_lgl(named_list, identity), named(lgl())) - expect_identical(pmap_int(named_list, identity), named(int())) - expect_identical(pmap_dbl(named_list, identity), named(dbl())) - expect_identical(pmap_chr(named_list, identity), named(chr())) + pwalk(list(exprs(1 + 2)), ~ expect_identical(.x, quote(1 + 2))) })