-
Notifications
You must be signed in to change notification settings - Fork 292
Rewrite every(), some(), and none() in C
#1169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
2dcb5f5
b32a384
3feea66
9e4aab5
dd6a89a
d54dfac
d38eacf
e72b2f2
bdde5ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,7 +60,6 @@ as_predicate <- function( | |
| .fn, | ||
| ..., | ||
| .mapper, | ||
| .allow_na = FALSE, | ||
| .purrr_error_call = caller_env(), | ||
| .purrr_error_arg = caller_arg(.fn) | ||
| ) { | ||
|
|
@@ -75,10 +74,6 @@ as_predicate <- function( | |
| out <- .fn(...) | ||
|
|
||
| if (!is_bool(out)) { | ||
| if (is_na(out) && .allow_na) { | ||
| # Always return a logical NA | ||
| return(NA) | ||
| } | ||
|
Comment on lines
-78
to
-81
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've removed this because no one else uses it This was a fairly weird bit of purrr. I've changed this in the C code to strictly require a scalar logical vector, and added tests about this. This is a breaking change, but hopefully a very minor one. |
||
| cli::cli_abort( | ||
| "{.fn { .purrr_error_arg }} must return a single `TRUE` or `FALSE`, not {.obj_type_friendly {out}}.", | ||
| arg = .purrr_error_arg, | ||
|
|
@@ -115,8 +110,9 @@ vctrs_list_compat <- function( | |
| } | ||
|
|
||
| # When we want to use vctrs, but treat lists like purrr does | ||
| # Treat data frames and S3 scalar lists like bare lists. | ||
| # But ensure rcrd vctrs retain their class. | ||
| # | ||
| # Treats data frames and S3 scalar lists like bare lists. | ||
| # But ensures rcrd vctrs retain their class. | ||
| vctrs_vec_compat <- function(x, user_env) { | ||
| if (inherits(x, "by")) { | ||
| class(x) <- NULL | ||
|
|
@@ -127,7 +123,7 @@ vctrs_vec_compat <- function(x, user_env) { | |
| } else if (is.pairlist(x)) { | ||
| lifecycle::deprecate_soft( | ||
| when = "1.0.0", | ||
| what = I("Use of pairlists in map functions"), | ||
| what = I("Use of pairlists in purrr functions"), | ||
| details = "Please coerce explicitly with `as.list()`", | ||
| user_env = user_env | ||
| ) | ||
|
|
@@ -138,7 +134,7 @@ vctrs_vec_compat <- function(x, user_env) { | |
| } else if (is_call(x) || is.expression(x)) { | ||
| lifecycle::deprecate_soft( | ||
| when = "1.0.0", | ||
| what = I("Use of calls and pairlists in map functions"), | ||
| what = I("Use of calls and expressions in purrr functions"), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe We now use |
||
| details = "Please coerce explicitly with `as.list()`", | ||
| user_env = user_env | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| #define R_NO_REMAP | ||
| #include <R.h> | ||
| #include <Rinternals.h> | ||
| #include <stdbool.h> | ||
|
|
||
| #include "conditions.h" | ||
|
|
||
| /** | ||
| * Is `x` a scalar logical? | ||
| * | ||
| * Notably we bypass the class and any attributes, i.e. `structure(TRUE, foo = | ||
| * "bar", class = "my-class")` does count for these purrr functions for | ||
| * historical reasons. We also ignore any R level `length()` method, but that | ||
| * would be incredibly rare to see here. | ||
| */ | ||
| static inline | ||
| bool is_scalar_logicalish(SEXP x) { | ||
| return TYPEOF(x) == LGLSXP && Rf_xlength(x) == 1; | ||
| } | ||
|
|
||
| /** | ||
| * C loop for `every()`, `some()`, and `none()` | ||
| * | ||
| * Uses `vctrs_vec_compat()` at the R level so that we can use `vec_size()` to | ||
| * compute `n`, while also using `[[` to extract elements, which is consistent | ||
| * with `map()`. | ||
| */ | ||
| static | ||
| SEXP satisfies_predicate( | ||
| SEXP env, | ||
| SEXP ffi_n, | ||
| SEXP ffi_i, | ||
| int initial, | ||
| int early_stop | ||
| ) { | ||
| const int n = INTEGER_ELT(ffi_n, 0); | ||
| int* p_i = INTEGER(ffi_i); | ||
|
|
||
| static SEXP call = NULL; | ||
| if (call == NULL) { | ||
| SEXP x_sym = Rf_install(".x"); | ||
| SEXP p_sym = Rf_install(".p"); | ||
| SEXP i_sym = Rf_install("i"); | ||
|
|
||
| // Constructs a call of the form .p(.x[[i]], ...) | ||
| SEXP x_i_sym = PROTECT(Rf_lang3(R_Bracket2Symbol, x_sym, i_sym)); | ||
|
|
||
| call = Rf_lang3(p_sym, x_i_sym, R_DotsSymbol); | ||
| R_PreserveObject(call); | ||
|
|
||
| UNPROTECT(1); | ||
| } | ||
|
|
||
| // Number of arguments within `call` to force. | ||
| // Same as `map()`. | ||
| const int force = 1; | ||
|
|
||
| int out = initial; | ||
|
|
||
| for (int i = 0; i < n; ++i) { | ||
| *p_i = i + 1; | ||
|
|
||
| if (i % 1024 == 0) { | ||
| R_CheckUserInterrupt(); | ||
| } | ||
|
|
||
| SEXP ffi_elt = PROTECT(R_forceAndCall(call, force, env)); | ||
DavisVaughan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (!is_scalar_logicalish(ffi_elt)) { | ||
| // We don't pass `.purrr_error_call` through `.Call()` so we can avoid | ||
| // evaluating it when it isn't needed, so we have to retrieve it when | ||
| // required. | ||
| SEXP error_call = PROTECT(Rf_eval(Rf_install(".purrr_error_call"), env)); | ||
|
|
||
| r_abort_call( | ||
| error_call, | ||
| "`.p()` must return a single `TRUE`, `FALSE`, or `NA`, not %s.", | ||
| rlang_obj_type_friendly_full(ffi_elt, true, false) | ||
| ); | ||
| } | ||
|
|
||
| const int elt = LOGICAL_ELT(ffi_elt, 0); | ||
| UNPROTECT(1); | ||
|
|
||
| if (elt == early_stop) { | ||
| // Early exit | ||
| out = !initial; | ||
| break; | ||
| } | ||
|
|
||
| if (elt == NA_LOGICAL) { | ||
| // Propagate `NA`, but keep going | ||
| out = NA_LOGICAL; | ||
| } | ||
| } | ||
|
|
||
| *p_i = 0; | ||
|
|
||
| return Rf_ScalarLogical(out); | ||
| } | ||
|
|
||
| SEXP every_impl(SEXP ffi_env, SEXP ffi_n, SEXP ffi_i) { | ||
DavisVaughan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return satisfies_predicate(ffi_env, ffi_n, ffi_i, 1, 0); | ||
| } | ||
| SEXP some_impl(SEXP ffi_env, SEXP ffi_n, SEXP ffi_i) { | ||
| return satisfies_predicate(ffi_env, ffi_n, ffi_i, 0, 1); | ||
| } | ||
| SEXP none_impl(SEXP ffi_env, SEXP ffi_n, SEXP ffi_i) { | ||
| return satisfies_predicate(ffi_env, ffi_n, ffi_i, 1, 1); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,123 @@ | ||
| # every() requires logical value | ||
| # every(), some(), and none() require logical scalar predicate results | ||
|
|
||
| Code | ||
| every(list(1:3), identity) | ||
| every(list(1), function(x) 1) | ||
| Condition | ||
| Error in `every()`: | ||
| ! `.p()` must return a single `TRUE` or `FALSE`, not an integer vector. | ||
| ! `.p()` must return a single `TRUE`, `FALSE`, or `NA`, not the number 1. | ||
|
|
||
| --- | ||
|
|
||
| Code | ||
| every(list(function() NULL), identity) | ||
| some(list(1), function(x) 1) | ||
| Condition | ||
| Error in `some()`: | ||
| ! `.p()` must return a single `TRUE`, `FALSE`, or `NA`, not the number 1. | ||
|
|
||
| --- | ||
|
|
||
| Code | ||
| none(list(1), function(x) 1) | ||
| Condition | ||
| Error in `none()`: | ||
| ! `.p()` must return a single `TRUE`, `FALSE`, or `NA`, not the number 1. | ||
|
|
||
| --- | ||
|
|
||
| Code | ||
| every(list(1), function(x) NA_integer_) | ||
| Condition | ||
| Error in `every()`: | ||
| ! `.p()` must return a single `TRUE` or `FALSE`, not a function. | ||
| ! `.p()` must return a single `TRUE`, `FALSE`, or `NA`, not an integer `NA`. | ||
|
|
||
| --- | ||
|
|
||
| Code | ||
| some(list(1), function(x) NA_integer_) | ||
| Condition | ||
| Error in `some()`: | ||
| ! `.p()` must return a single `TRUE`, `FALSE`, or `NA`, not an integer `NA`. | ||
|
|
||
| --- | ||
|
|
||
| Code | ||
| none(list(1), function(x) NA_integer_) | ||
| Condition | ||
| Error in `none()`: | ||
| ! `.p()` must return a single `TRUE`, `FALSE`, or `NA`, not an integer `NA`. | ||
|
|
||
| --- | ||
|
|
||
| Code | ||
| every(list(1), function(x) c(TRUE, FALSE)) | ||
| Condition | ||
| Error in `every()`: | ||
| ! `.p()` must return a single `TRUE`, `FALSE`, or `NA`, not a logical vector. | ||
|
|
||
| --- | ||
|
|
||
| Code | ||
| some(list(1), function(x) c(TRUE, FALSE)) | ||
| Condition | ||
| Error in `some()`: | ||
| ! `.p()` must return a single `TRUE`, `FALSE`, or `NA`, not a logical vector. | ||
|
|
||
| --- | ||
|
|
||
| Code | ||
| none(list(1), function(x) c(TRUE, FALSE)) | ||
| Condition | ||
| Error in `none()`: | ||
| ! `.p()` must return a single `TRUE`, `FALSE`, or `NA`, not a logical vector. | ||
|
|
||
| # every(), some(), and none() require vector `.x` | ||
|
|
||
| Code | ||
| every(function() 1, identity) | ||
| Condition | ||
| Error in `every()`: | ||
| ! `.x` must be a vector, not a function. | ||
|
|
||
| --- | ||
|
|
||
| Code | ||
| some(function() 1, identity) | ||
| Condition | ||
| Error in `some()`: | ||
| ! `.x` must be a vector, not a function. | ||
|
|
||
| --- | ||
|
|
||
| Code | ||
| none(function() 1, identity) | ||
| Condition | ||
| Error in `none()`: | ||
| ! `.x` must be a vector, not a function. | ||
|
|
||
| # pairlists, expressions, and calls are deprecated but work | ||
|
|
||
| Code | ||
| out <- every(expression(1, 2), is.double) | ||
| Condition | ||
| Warning: | ||
| Use of calls and expressions in purrr functions was deprecated in purrr 1.0.0. | ||
| i Please coerce explicitly with `as.list()` | ||
|
|
||
| --- | ||
|
|
||
| Code | ||
| out <- every(pairlist(1, 2), is.double) | ||
| Condition | ||
| Warning: | ||
| Use of pairlists in purrr functions was deprecated in purrr 1.0.0. | ||
| i Please coerce explicitly with `as.list()` | ||
|
|
||
| --- | ||
|
|
||
| Code | ||
| x <- every(quote(f(a, b)), is.name) | ||
| Condition | ||
| Warning: | ||
| Use of calls and expressions in purrr functions was deprecated in purrr 1.0.0. | ||
| i Please coerce explicitly with `as.list()` | ||
|
|
Uh oh!
There was an error while loading. Please reload this page.