Skip to content

Commit

Permalink
Apply vctrs principles to map() and modify() (#894)
Browse files Browse the repository at this point in the history
New `map_vec()`, `map2_vec()`, and `pmap_vec()` return a vector the same length as `x` with type determined by the returned type of `.f`. Fixes #435

`modify()`, `modify_if()`, `modify_at()`, and `modify2()` have been rewritten from scratch. They are no longer generic and should work with a wider range of (future) classes from the vctrs package. Fixes #928

All the tests for these functions have been substantially revamped to do a better job of illustrating the underlying invariants.
  • Loading branch information
hadley authored Sep 17, 2022
1 parent 426acdd commit db7bd02
Show file tree
Hide file tree
Showing 18 changed files with 569 additions and 385 deletions.
24 changes: 3 additions & 21 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()`.

Expand Down
38 changes: 28 additions & 10 deletions R/map.R
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
#'
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion R/map2.R
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,16 @@ 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
walk2 <- function(.x, .y, .f, ...) {
map2(.x, .y, .f, ...)
invisible(.x)
}

Loading

0 comments on commit db7bd02

Please sign in to comment.