diff --git a/NEWS.md b/NEWS.md index 207011e8..fdfb8998 100644 --- a/NEWS.md +++ b/NEWS.md @@ -139,6 +139,10 @@ of `[<-`. `modify.default()` is thus a shorthand for `x[] <- map(x, f)`. * `transpose()` now matches by name if available (#164). You can override the default choice with the new `.names` argument. +* The function argument of `detect()` and `detect_index()` have been + renamed from `.p` to `.f`. This is because they have mapper + semantics rather than predicate semantics. + # purrr 0.2.2.1 diff --git a/R/find-position.R b/R/find-position.R index 8726cc0e..6ae2e292 100644 --- a/R/find-position.R +++ b/R/find-position.R @@ -18,22 +18,42 @@ #' #' 3:10 %>% detect(is_even, .right = TRUE) #' 3:10 %>% detect_index(is_even, .right = TRUE) -detect <- function(.x, .p, ..., .right = FALSE) { - .p <- as_mapper(.p, ...) +#' +#' +#' # Since `.f` is passed to as_mapper(), you can supply a +#' # lambda-formula or a pluck object: +#' x <- list( +#' list(1, foo = FALSE), +#' list(2, foo = TRUE), +#' list(3, foo = TRUE) +#' ) +#' +#' detect(x, "foo") +#' detect_index(x, "foo") +detect <- function(.x, .f, ..., .right = FALSE, .p) { + if (!missing(.p)) { + warn("`.p` has been renamed to `.f`", "purrr_2.2.3") + .f <- .p + } + .f <- as_mapper(.f, ...) for (i in index(.x, .right)) { - if (is_true(.p(.x[[i]], ...))) return(.x[[i]]) + if (is_true(.f(.x[[i]], ...))) return(.x[[i]]) } NULL } #' @export #' @rdname detect -detect_index <- function(.x, .p, ..., .right = FALSE) { - .p <- as_mapper(.p, ...) +detect_index <- function(.x, .f, ..., .right = FALSE, .p) { + if (!missing(.p)) { + warn("`.p` has been renamed to `.f`", "purrr_2.2.3") + .f <- .p + } + .f <- as_mapper(.f, ...) for (i in index(.x, .right)) { - if (is_true(.p(.x[[i]], ...))) return(i) + if (is_true(.f(.x[[i]], ...))) return(i) } 0L } diff --git a/man/detect.Rd b/man/detect.Rd index 935c1546..32a6552e 100644 --- a/man/detect.Rd +++ b/man/detect.Rd @@ -5,25 +5,46 @@ \alias{detect_index} \title{Find the value or position of the first match.} \usage{ -detect(.x, .p, ..., .right = FALSE) +detect(.x, .f, ..., .right = FALSE, .p) -detect_index(.x, .p, ..., .right = FALSE) +detect_index(.x, .f, ..., .right = FALSE, .p) } \arguments{ \item{.x}{A list or atomic vector.} -\item{.p}{A single predicate function, a formula describing such a -predicate function, or a logical vector of the same length as \code{.x}. -Alternatively, if the elements of \code{.x} are themselves lists of -objects, a string indicating the name of a logical element in the -inner lists. Only those elements where \code{.p} evaluates to -\code{TRUE} will be modified.} +\item{.f}{A function, formula, or atomic vector. + +If a \strong{function}, it is used as is. + +If a \strong{formula}, e.g. \code{~ .x + 2}, it is converted to a function. There +are three ways to refer to the arguments: +\itemize{ +\item For a single argument function, use \code{.} +\item For a two argument function, use \code{.x} and \code{.y} +\item For more arguments, use \code{..1}, \code{..2}, \code{..3} etc +} + +This syntax allows you to create very compact anonymous functions. + +If \strong{character vector}, \strong{numeric vector}, or \strong{list}, it +is converted to an extractor function. Character vectors index by name +and numeric vectors index by position; use a list to index by position +and name at different levels. Within a list, wrap strings in \code{get_attr()} +to extract named attributes. If a component is not present, the value of +\code{.default} will be returned.} \item{...}{Additional arguments passed on to \code{.f}.} \item{.right}{If \code{FALSE}, the default, starts at the beginning of the vector and move towards the end; if \code{TRUE}, starts at the end of the vector and moves towards the beginning.} + +\item{.p}{A single predicate function, a formula describing such a +predicate function, or a logical vector of the same length as \code{.x}. +Alternatively, if the elements of \code{.x} are themselves lists of +objects, a string indicating the name of a logical element in the +inner lists. Only those elements where \code{.p} evaluates to +\code{TRUE} will be modified.} } \value{ \code{detect} the value of the first item that matches the @@ -42,4 +63,16 @@ is_even <- function(x) x \%\% 2 == 0 3:10 \%>\% detect(is_even, .right = TRUE) 3:10 \%>\% detect_index(is_even, .right = TRUE) + + +# Since `.f` is passed to as_mapper(), you can supply a +# lambda-formula or a pluck object: +x <- list( + list(1, foo = FALSE), + list(2, foo = TRUE), + list(3, foo = TRUE) +) + +detect(x, "foo") +detect_index(x, "foo") } diff --git a/tests/testthat/test-find-position.R b/tests/testthat/test-find-position.R index 75a7c3fb..5d29b49d 100644 --- a/tests/testthat/test-find-position.R +++ b/tests/testthat/test-find-position.R @@ -22,3 +22,13 @@ test_that("has_element checks whether a list contains an object", { expect_true(has_element(list(1, 2), 1)) expect_false(has_element(list(1, 2), 3)) }) + +test_that("detect functions still work with `.p`", { + is_even <- function(x) x %% 2 == 0 + expect_warning(regex = "renamed to `.f`", + expect_identical(detect(1:3, .p = is_even), 2L) + ) + expect_warning(regex = "renamed to `.f`", + expect_identical(detect_index(1:3, .p = is_even), 2L) + ) +})