diff --git a/NAMESPACE b/NAMESPACE index e754bc4a..bb906a65 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -60,6 +60,7 @@ export(imap_dfr) export(imap_int) export(imap_lgl) export(imap_raw) +export(imap_vec) export(imodify) export(insistently) export(invoke) diff --git a/NEWS.md b/NEWS.md index 636668d4..b26c4487 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # purrr (development version) +* Added `imap_vec()` (#1084) +* `list_transpose()` inspects all elements to determine the correct + template if it's not provided by the user (#1128, @krlmlr). + # purrr 1.0.2 * Fixed valgrind issue. @@ -744,7 +748,7 @@ accessor(x[[1]])$foo to the equivalent pluck: ``` -x %>% pluck(1, accessor, "foo") +x |> pluck(1, accessor, "foo") ``` @@ -968,7 +972,7 @@ This is a compatibility release with dplyr 0.6.0. * `set_names()` is a snake-case alternative to `setNames()` with stricter equality checking, and more convenient defaults for pipes: - `x %>% set_names()` is equivalent to `setNames(x, x)` (#119). + `x |> set_names()` is equivalent to `setNames(x, x)` (#119). ## Row based functionals @@ -980,7 +984,7 @@ functions. * `map()` now always returns a list. Data frame support has been moved to `map_df()` and `dmap()`. The latter supports sliced data frames as a shortcut for the combination of `by_slice()` and `dmap()`: - `x %>% by_slice(dmap, fun, .collate = "rows")`. The conditional + `x |> by_slice(dmap, fun, .collate = "rows")`. The conditional variants `dmap_at()` and `dmap_if()` also support sliced data frames and will recycle scalar results to the slice size. diff --git a/R/deprec-when.R b/R/deprec-when.R index 677c3ca7..a1fb6f04 100644 --- a/R/deprec-when.R +++ b/R/deprec-when.R @@ -36,7 +36,7 @@ #' #' @keywords internal #' @examples -#' 1:10 %>% +#' 1:10 |> #' when( #' sum(.) <= 50 ~ sum(.), #' sum(.) <= 100 ~ sum(.)/2, diff --git a/R/imap.R b/R/imap.R index fa1d9e52..ec1b01ff 100644 --- a/R/imap.R +++ b/R/imap.R @@ -56,6 +56,13 @@ imap_dbl <- function(.x, .f, ...) { map2_dbl(.x, vec_index(.x), .f, ...) } +#' @rdname imap +#' @export +imap_vec <- function(.x, .f, ...) { + .f <- as_mapper(.f, ...) + map2_vec(.x, vec_index(.x), .f, ...) +} + #' @export #' @rdname imap diff --git a/R/keep.R b/R/keep.R index ab217f59..e1df34cd 100644 --- a/R/keep.R +++ b/R/keep.R @@ -68,12 +68,12 @@ compact <- function(.x, .p = identity) { #' @export #' @examples #' x <- c(a = 1, b = 2, cat = 10, dog = 15, elephant = 5, e = 10) -#' x %>% keep_at(letters) -#' x %>% discard_at(letters) +#' x |> keep_at(letters) +#' x |> discard_at(letters) #' #' # Can also use a function -#' x %>% keep_at(~ nchar(.x) == 3) -#' x %>% discard_at(~ nchar(.x) == 3) +#' x |> keep_at(~ nchar(.x) == 3) +#' x |> discard_at(~ nchar(.x) == 3) keep_at <- function(x, at) { where <- where_at(x, at, user_env = caller_env()) x[where] diff --git a/R/list-transpose.R b/R/list-transpose.R index 9e76f033..692e9ad0 100644 --- a/R/list-transpose.R +++ b/R/list-transpose.R @@ -14,9 +14,9 @@ #' @param x A list of vectors to transpose. #' @param template A "template" that describes the output list. Can either be #' a character vector (where elements are extracted by name), or an integer -#' vector (where elements are extracted by position). Defaults to the names -#' of the first element of `x`, or if they're not present, the integer -#' indices. +#' vector (where elements are extracted by position). Defaults to the union +#' of the names of the elements of `x`, or if they're not present, the +#' union of the integer indices. #' @param simplify Should the result be [simplified][list_simplify]? #' * `TRUE`: simplify or die trying. #' * `NA`: simplify if possible. @@ -80,8 +80,19 @@ list_transpose <- function(x, if (length(x) == 0) { template <- integer() - } else { - template <- template %||% vec_index(x[[1]]) + } else if (is.null(template)) { + indexes <- map(x, vec_index) + call <- current_env() + withCallingHandlers( + template <- reduce(indexes, vec_set_union), + vctrs_error_ptype2 = function(e) { + cli::cli_abort( + "Can't combine named and unnamed vectors.", + arg = template, + call = call + ) + } + ) } if (!is.character(template) && !is.numeric(template)) { diff --git a/README.Rmd b/README.Rmd index 1f11d9c0..dc6bc5e5 100644 --- a/README.Rmd +++ b/README.Rmd @@ -52,7 +52,7 @@ library(purrr) mtcars |> split(mtcars$cyl) |> # from base R map(\(df) lm(mpg ~ wt, data = df)) |> - map(summary) %>% + map(summary) |> map_dbl("r.squared") ``` diff --git a/README.md b/README.md index 7809a9e8..ed4d3cc8 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ library(purrr) mtcars |> split(mtcars$cyl) |> # from base R map(\(df) lm(mpg ~ wt, data = df)) |> - map(summary) %>% + map(summary) |> map_dbl("r.squared") #> 4 6 8 #> 0.5086326 0.4645102 0.4229655 diff --git a/man/imap.Rd b/man/imap.Rd index ae081b01..b453d245 100644 --- a/man/imap.Rd +++ b/man/imap.Rd @@ -6,6 +6,7 @@ \alias{imap_chr} \alias{imap_int} \alias{imap_dbl} +\alias{imap_vec} \alias{iwalk} \title{Apply a function to each element of a vector, and its index} \usage{ @@ -19,6 +20,8 @@ imap_int(.x, .f, ...) imap_dbl(.x, .f, ...) +imap_vec(.x, .f, ...) + iwalk(.x, .f, ...) } \arguments{ diff --git a/man/keep_at.Rd b/man/keep_at.Rd index feff8223..3f025a98 100644 --- a/man/keep_at.Rd +++ b/man/keep_at.Rd @@ -25,12 +25,12 @@ Keep/discard elements based on their name/position } \examples{ x <- c(a = 1, b = 2, cat = 10, dog = 15, elephant = 5, e = 10) -x \%>\% keep_at(letters) -x \%>\% discard_at(letters) +x |> keep_at(letters) +x |> discard_at(letters) # Can also use a function -x \%>\% keep_at(~ nchar(.x) == 3) -x \%>\% discard_at(~ nchar(.x) == 3) +x |> keep_at(~ nchar(.x) == 3) +x |> discard_at(~ nchar(.x) == 3) } \seealso{ \code{\link[=keep]{keep()}}/\code{\link[=discard]{discard()}} to keep/discard elements by value. diff --git a/man/list_transpose.Rd b/man/list_transpose.Rd index 0dc8aabc..1e86d708 100644 --- a/man/list_transpose.Rd +++ b/man/list_transpose.Rd @@ -20,9 +20,9 @@ list_transpose( \item{template}{A "template" that describes the output list. Can either be a character vector (where elements are extracted by name), or an integer -vector (where elements are extracted by position). Defaults to the names -of the first element of \code{x}, or if they're not present, the integer -indices.} +vector (where elements are extracted by position). Defaults to the union +of the names of the elements of \code{x}, or if they're not present, the +union of the integer indices.} \item{simplify}{Should the result be \link[=list_simplify]{simplified}? \itemize{ diff --git a/man/when.Rd b/man/when.Rd index ff3c85a5..d7815456 100644 --- a/man/when.Rd +++ b/man/when.Rd @@ -37,7 +37,7 @@ valid match/condition is found the action is executed and the result of the action is returned. } \examples{ -1:10 \%>\% +1:10 |> when( sum(.) <= 50 ~ sum(.), sum(.) <= 100 ~ sum(.)/2, diff --git a/purrr.Rproj b/purrr.Rproj index 69b10ac6..cba1b6b7 100644 --- a/purrr.Rproj +++ b/purrr.Rproj @@ -19,5 +19,3 @@ BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source PackageRoxygenize: rd,collate,namespace - -UseNativePipeOperator: Yes diff --git a/tests/testthat/_snaps/list-transpose.md b/tests/testthat/_snaps/list-transpose.md index df1f84a8..b956aaad 100644 --- a/tests/testthat/_snaps/list-transpose.md +++ b/tests/testthat/_snaps/list-transpose.md @@ -64,3 +64,11 @@ Error in `list_transpose()`: ! `template` must be a character or numeric vector, not a function. +# fail mixing named and unnamed vectors + + Code + test_list_transpose() + Condition + Error in `list_transpose()`: + ! Can't combine named and unnamed vectors. + diff --git a/tests/testthat/test-imap.R b/tests/testthat/test-imap.R index 79314d7e..bd1536e1 100644 --- a/tests/testthat/test-imap.R +++ b/tests/testthat/test-imap.R @@ -13,6 +13,7 @@ test_that("atomic vector imap works", { expect_length(imap_chr(x, paste), 3) expect_equal(imap_int(x, ~ .x + as.integer(.y)), x * 2) expect_equal(imap_dbl(x, ~ .x + as.numeric(.y)), x * 2) + expect_equal(imap_vec(x, ~ .x + as.numeric(.y)), x * 2) }) test_that("iwalk returns invisibly", { diff --git a/tests/testthat/test-list-transpose.R b/tests/testthat/test-list-transpose.R index 724fbab7..dffb9a7e 100644 --- a/tests/testthat/test-list-transpose.R +++ b/tests/testthat/test-list-transpose.R @@ -13,7 +13,7 @@ test_that("can use character template", { # Default: expect_equal( list_transpose(x, default = NA), - list(a = c(1, NA), b = c(2, 3)) + list(a = c(1, NA), b = c(2, 3), c = c(NA, 4)) ) # Change order @@ -131,8 +131,13 @@ test_that("validates inputs", { }) }) -test_that("works on data frames", { - expect_no_error( - list_transpose(mtcars) - ) +test_that("fail mixing named and unnamed vectors", { + test_list_transpose <- function() { + x <- list(list(a = 1, b = 2), list(a = 3, b = 4)) + list_transpose(list(x = list(a = 1, b = 2), y = list(3, 4))) + } + expect_snapshot(error = TRUE, { + test_list_transpose() + }) }) + diff --git a/vignettes/base.Rmd b/vignettes/base.Rmd index 3b37d290..9f13787c 100644 --- a/vignettes/base.Rmd +++ b/vignettes/base.Rmd @@ -281,9 +281,9 @@ The pipe is particularly compelling when working with longer transformations. For example, the following code splits `mtcars` up by `cyl`, fits a linear model, extracts the coefficients, and extracts the first one (the intercept). ```{r, eval = modern_r} -mtcars %>% - split(mtcars$cyl) %>% - map(\(df) lm(mpg ~ wt, data = df)) %>% - map(coef) %>% +mtcars |> + split(mtcars$cyl) |> + map(\(df) lm(mpg ~ wt, data = df))|> + map(coef) |> map_dbl(1) ``` diff --git a/vignettes/other-langs.Rmd b/vignettes/other-langs.Rmd index dc1df51c..dd51b534 100644 --- a/vignettes/other-langs.Rmd +++ b/vignettes/other-langs.Rmd @@ -23,7 +23,7 @@ purrr draws inspiration from many related tools: However, the goal of purrr is not to try and simulate a purer functional programming language in R; we don't want to implement a second-class version of Haskell in R. The goal is to give you similar expressiveness to an FP language, while allowing you to write code that looks and works like R: -* Instead of point free (tacit) style, we use the pipe, `%>%`, to write code +* Instead of point free (tacit) style, we use the pipe, `|>`, to write code that can be read from left to right. * Instead of currying, we use `...` to pass in extra arguments.