diff --git a/NEWS.md b/NEWS.md index a2282eee..2083fe06 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,9 @@ no longer think they are the right approach to solving this problem. See #768 for more information. +* The `lift_*` family of functions has been deprecated. We no longer believe + these to be a good fit for purrr because rely on a style of function + manipulation that is very uncommon in R code (#871). ## Features and fixes diff --git a/R/composition.R b/R/composition.R index 6ff3e6fb..0eb12e11 100644 --- a/R/composition.R +++ b/R/composition.R @@ -1,5 +1,8 @@ #' Lift the domain of a function #' +#' @description +#' `r lifecycle::badge("deprecated")` +#' #' `lift_xy()` is a composition helper. It helps you compose #' functions by lifting their domain from a kind of input to another #' kind. The domain can be changed from and to a list (l), a vector @@ -12,6 +15,10 @@ #' functional tools. Since this is such a common function, #' `lift()` is provided as an alias for that operation. #' +#' We have deprecated these functions because we no longer believe "lifting" +#' to be a mainstream operation, and we are striving to reduce purrr to its +#' most useful core. +#' #' @inheritParams as_vector #' @param ..f A function to lift. #' @param ... Default arguments for `..f`. These will be @@ -41,36 +48,26 @@ NULL #' #' x <- list(x = c(1:100, NA, 1000), na.rm = TRUE, trim = 0.9) #' lift_dl(mean)(x) -#' -#' # Or in a pipe: -#' mean %>% lift_dl() %>% invoke(x) -#' #' # You can also use the lift() alias for this common operation: #' lift(mean)(x) +#' # now: +#' exec(mean, !!!x) #' #' # Default arguments can also be specified directly in lift_dl() #' list(c(1:100, NA, 1000)) %>% lift_dl(mean, na.rm = TRUE)() +#' # now: +#' mean(c(1:100, NA, 1000), na.rm = TRUE) #' #' # lift_dl() and lift_ld() are inverse of each other. #' # Here we transform sum() so that it takes a list #' fun <- sum %>% lift_dl() #' fun(list(3, NA, 4, na.rm = TRUE)) -#' -#' # Now we transform it back to a variadic function -#' fun2 <- fun %>% lift_ld() -#' fun2(3, NA, 4, na.rm = TRUE) -#' -#' # It can sometimes be useful to make sure the lifted function's -#' # signature has no named parameters, as would be the case for a -#' # function taking only dots. The lifted function will take a list -#' # or vector but will not match its arguments to the names of the -#' # input. For instance, if you give a data frame as input to your -#' # lifted function, the names of the columns are probably not -#' # related to the function signature and should be discarded. -#' lifted_identical <- lift_dl(identical, .unnamed = TRUE) -#' mtcars[c(1, 1)] %>% lifted_identical() -#' mtcars[c(1, 2)] %>% lifted_identical() +#' # now: +#' fun <- function(x) exec("sum", !!!x) +#' exec(sum, 3, NA, 4, na.rm = TRUE) lift <- function(..f, ..., .unnamed = FALSE) { + lifecycle::deprecate_warn("0.4.0", "lift()") + force(..f) defaults <- list(...) function(.x = list(), ...) { @@ -88,6 +85,8 @@ lift_dl <- lift #' @rdname lift #' @export lift_dv <- function(..f, ..., .unnamed = FALSE) { + lifecycle::deprecate_warn("0.4.0", "lift_dv()") + force(..f) defaults <- list(...) @@ -111,21 +110,17 @@ lift_dv <- function(..f, ..., .unnamed = FALSE) { #' type by supplying `.type`. #' @export #' @examples -#' # -#' -#' #' ### Lifting from c(...) to list(...) or ... #' #' # In other situations we need the vector-valued function to take a #' # variable number of arguments as with pmap(). This is a job for #' # lift_vd(): -#' pmap(mtcars, lift_vd(mean)) -#' -#' # lift_vd() will collect the arguments and concatenate them to a -#' # vector before passing them to ..f. You can add a check to assert -#' # the type of vector you expect: -#' lift_vd(tolower, .type = character(1))("this", "is", "ok") +#' pmap_dbl(mtcars, lift_vd(mean)) +#' # now +#' pmap_dbl(mtcars, ~ mean(c(...))) lift_vl <- function(..f, ..., .type) { + lifecycle::deprecate_warn("0.4.0", "lift_vl()") + force(..f) defaults <- list(...) if (missing(.type)) .type <- NULL @@ -139,6 +134,8 @@ lift_vl <- function(..f, ..., .type) { #' @rdname lift #' @export lift_vd <- function(..f, ..., .type) { + lifecycle::deprecate_warn("0.4.0", "lift_vd()") + force(..f) defaults <- list(...) if (missing(.type)) .type <- NULL @@ -162,43 +159,20 @@ lift_vd <- function(..f, ..., .type) { #' #' @export #' @examples -#' # -#' -#' #' ### Lifting from list(...) to c(...) or ... #' -#' # cross() normally takes a list of elements and returns their -#' # cartesian product. By lifting it you can supply the arguments as -#' # if it was a function taking dots: -#' cross_dots <- lift_ld(cross) -#' out1 <- cross(list(a = 1:2, b = c("a", "b", "c"))) -#' out2 <- cross_dots(a = 1:2, b = c("a", "b", "c")) -#' identical(out1, out2) -#' #' # This kind of lifting is sometimes needed for function #' # composition. An example would be to use pmap() with a function #' # that takes a list. In the following, we use some() on each row of #' # a data frame to check they each contain at least one element #' # satisfying a condition: -#' mtcars %>% pmap(lift_ld(some, partial(`<`, 200))) -#' -#' # Default arguments for ..f can be specified in the call to -#' # lift_ld() -#' lift_ld(cross, .filter = `==`)(1:3, 1:3) %>% str() +#' mtcars %>% pmap_lgl(lift_ld(some, partial(`<`, 200))) +#' # now +#' mtcars %>% pmap_lgl(~ any(c(...) > 200)) #' -#' -#' # Here is another function taking a list and that we can update to -#' # take a vector: -#' glue <- function(l) { -#' if (!is.list(l)) stop("not a list") -#' l %>% invoke(paste, .) -#' } -#' -#' \dontrun{ -#' letters %>% glue() # fails because glue() expects a list} -#' -#' letters %>% lift_lv(glue)() # succeeds lift_ld <- function(..f, ...) { + lifecycle::deprecate_warn("0.4.0", "lift_ld()") + force(..f) defaults <- list(...) function(...) { @@ -209,6 +183,8 @@ lift_ld <- function(..f, ...) { #' @rdname lift #' @export lift_lv <- function(..f, ...) { + lifecycle::deprecate_warn("0.4.0", "lift_lv()") + force(..f) defaults <- list(...) function(.x, ...) { diff --git a/R/cross.R b/R/cross.R index 9ce9e9a7..af21cccb 100644 --- a/R/cross.R +++ b/R/cross.R @@ -19,7 +19,7 @@ #' ) #' #' # With deprecated `cross()` -#' data %>% cross() %>% map_chr(lift(paste)) +#' data %>% cross() %>% map_chr(~ paste(..., collapse = " ")) #' #' # With `expand_grid()` #' tidyr::expand_grid(!!!data) %>% pmap_chr(paste) diff --git a/_pkgdown.yml b/_pkgdown.yml index 5d619523..422fed55 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -95,7 +95,6 @@ reference: contents: - faq-adverbs-export - compose - - lift - negate - partial - safely diff --git a/man/cross.Rd b/man/cross.Rd index a5e8dd34..87595a59 100644 --- a/man/cross.Rd +++ b/man/cross.Rd @@ -52,9 +52,10 @@ Here is an example of equivalent usages for \code{cross()} and ) # With deprecated `cross()` -data \%>\% cross() \%>\% map_chr(lift(paste)) -#> [1] "John! Hello." "Jane! Hello." "John! Bonjour." "Jane! Bonjour." -#> [5] "John... Hello." "Jane... Hello." "John... Bonjour." "Jane... Bonjour." +data \%>\% cross() \%>\% map_chr(~ paste(..., collapse = " ")) +#> [1] "John Hello. ! " "Jane Hello. ! " "John Bonjour. ! " +#> [4] "Jane Bonjour. ! " "John Hello. ... " "Jane Hello. ... " +#> [7] "John Bonjour. ... " "Jane Bonjour. ... " # With `expand_grid()` tidyr::expand_grid(!!!data) \%>\% pmap_chr(paste) diff --git a/man/lift.Rd b/man/lift.Rd index 99c0f9e0..0b10403d 100644 --- a/man/lift.Rd +++ b/man/lift.Rd @@ -44,18 +44,23 @@ input vectors. The latter can be any of the types returned by A function. } \description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + \code{lift_xy()} is a composition helper. It helps you compose functions by lifting their domain from a kind of input to another kind. The domain can be changed from and to a list (l), a vector (v) and dots (d). For example, \code{lift_ld(fun)} transforms a function taking a list to a function taking dots. -} -\details{ + The most important of those helpers is probably \code{lift_dl()} because it allows you to transform a regular function to one that takes a list. This is often essential for composition with purrr functional tools. Since this is such a common function, \code{lift()} is provided as an alias for that operation. + +We have deprecated these functions because we no longer believe "lifting" +to be a mainstream operation, and we are striving to reduce purrr to its +most useful core. } \section{from ... to \code{list(...)} or \code{c(...)}}{ @@ -97,85 +102,42 @@ with \code{.type}. x <- list(x = c(1:100, NA, 1000), na.rm = TRUE, trim = 0.9) lift_dl(mean)(x) - -# Or in a pipe: -mean \%>\% lift_dl() \%>\% invoke(x) - # You can also use the lift() alias for this common operation: lift(mean)(x) +# now: +exec(mean, !!!x) # Default arguments can also be specified directly in lift_dl() list(c(1:100, NA, 1000)) \%>\% lift_dl(mean, na.rm = TRUE)() +# now: +mean(c(1:100, NA, 1000), na.rm = TRUE) # lift_dl() and lift_ld() are inverse of each other. # Here we transform sum() so that it takes a list fun <- sum \%>\% lift_dl() fun(list(3, NA, 4, na.rm = TRUE)) - -# Now we transform it back to a variadic function -fun2 <- fun \%>\% lift_ld() -fun2(3, NA, 4, na.rm = TRUE) - -# It can sometimes be useful to make sure the lifted function's -# signature has no named parameters, as would be the case for a -# function taking only dots. The lifted function will take a list -# or vector but will not match its arguments to the names of the -# input. For instance, if you give a data frame as input to your -# lifted function, the names of the columns are probably not -# related to the function signature and should be discarded. -lifted_identical <- lift_dl(identical, .unnamed = TRUE) -mtcars[c(1, 1)] \%>\% lifted_identical() -mtcars[c(1, 2)] \%>\% lifted_identical() -# - - +# now: +fun <- function(x) exec("sum", !!!x) +exec(sum, 3, NA, 4, na.rm = TRUE) ### Lifting from c(...) to list(...) or ... # In other situations we need the vector-valued function to take a # variable number of arguments as with pmap(). This is a job for # lift_vd(): -pmap(mtcars, lift_vd(mean)) - -# lift_vd() will collect the arguments and concatenate them to a -# vector before passing them to ..f. You can add a check to assert -# the type of vector you expect: -lift_vd(tolower, .type = character(1))("this", "is", "ok") -# - - +pmap_dbl(mtcars, lift_vd(mean)) +# now +pmap_dbl(mtcars, ~ mean(c(...))) ### Lifting from list(...) to c(...) or ... -# cross() normally takes a list of elements and returns their -# cartesian product. By lifting it you can supply the arguments as -# if it was a function taking dots: -cross_dots <- lift_ld(cross) -out1 <- cross(list(a = 1:2, b = c("a", "b", "c"))) -out2 <- cross_dots(a = 1:2, b = c("a", "b", "c")) -identical(out1, out2) - # This kind of lifting is sometimes needed for function # composition. An example would be to use pmap() with a function # that takes a list. In the following, we use some() on each row of # a data frame to check they each contain at least one element # satisfying a condition: -mtcars \%>\% pmap(lift_ld(some, partial(`<`, 200))) - -# Default arguments for ..f can be specified in the call to -# lift_ld() -lift_ld(cross, .filter = `==`)(1:3, 1:3) \%>\% str() - - -# Here is another function taking a list and that we can update to -# take a vector: -glue <- function(l) { - if (!is.list(l)) stop("not a list") - l \%>\% invoke(paste, .) -} - -\dontrun{ -letters \%>\% glue() # fails because glue() expects a list} +mtcars \%>\% pmap_lgl(lift_ld(some, partial(`<`, 200))) +# now +mtcars \%>\% pmap_lgl(~ any(c(...) > 200)) -letters \%>\% lift_lv(glue)() # succeeds } \seealso{ \code{\link[=invoke]{invoke()}} diff --git a/tests/testthat/_snaps/composition.md b/tests/testthat/_snaps/composition.md new file mode 100644 index 00000000..619bacaf --- /dev/null +++ b/tests/testthat/_snaps/composition.md @@ -0,0 +1,33 @@ +# lift_* is deprecated + + Code + . <- lift(mean) + Condition + Warning: + `lift()` was deprecated in purrr 0.4.0. + Code + . <- lift_dv(mean) + Condition + Warning: + `lift_dv()` was deprecated in purrr 0.4.0. + Code + . <- lift_vl(mean) + Condition + Warning: + `lift_vl()` was deprecated in purrr 0.4.0. + Code + . <- lift_vd(mean) + Condition + Warning: + `lift_vd()` was deprecated in purrr 0.4.0. + Code + . <- lift_ld(mean) + Condition + Warning: + `lift_ld()` was deprecated in purrr 0.4.0. + Code + . <- lift_lv(mean) + Condition + Warning: + `lift_lv()` was deprecated in purrr 0.4.0. + diff --git a/tests/testthat/test-composition.R b/tests/testthat/test-composition.R index 57412bb9..b59cbd86 100644 --- a/tests/testthat/test-composition.R +++ b/tests/testthat/test-composition.R @@ -1,4 +1,17 @@ +test_that("lift_* is deprecated", { + expect_snapshot({ + . <- lift(mean) + . <- lift_dv(mean) + . <- lift_vl(mean) + . <- lift_vd(mean) + . <- lift_ld(mean) + . <- lift_lv(mean) + }) +}) + test_that("lift_dl and lift_ld are inverses of each other", { + options(lifecycle_verbosity = "quiet") + expect_identical( sum %>% lift_dl(.unnamed = TRUE) %>% @@ -11,18 +24,22 @@ test_that("lift_dl and lift_ld are inverses of each other", { }) test_that("lift_dv is from ... to c(...)", { + options(lifecycle_verbosity = "quiet") expect_equal(lift_dv(range, .unnamed = TRUE)(1:10), c(1, 10)) }) test_that("lift_vd is from c(...) to ...", { + options(lifecycle_verbosity = "quiet") expect_equal(lift_vd(mean)(1, 2), 1.5) }) test_that("lift_vl is from c(...) to list(...)", { + options(lifecycle_verbosity = "quiet") expect_equal(lift_vl(mean)(list(1, 2)), 1.5) }) test_that("lift_lv is from list(...) to c(...)", { + options(lifecycle_verbosity = "quiet") glue <- function(l) { if (!is.list(l)) stop("not a list") l %>% do.call(paste, .)