r-lib / rlang Goto Github PK
View Code? Open in Web Editor NEWLow-level API for programming with R
Home Page: https://rlang.r-lib.org
License: Other
Low-level API for programming with R
Home Page: https://rlang.r-lib.org
License: Other
While this code works fine:
# library(tidyr)
library(rlang)
test <- function(df, ...) {
group_by <- quos(...)
print(group_by)
}
test(1,x=e,a=s)
# $x
# <quosure: global>
# ~e
#
# $a
# <quosure: global>
# ~s
#
# attr(,"class")
# [1] "quosures"
Unquoting library(tidyr)
(i.e. loading it first) gives me the following error:
Error in .Call(replace_na, x, y) :
first argument must be a string (of length 1) or native symbol reference
My session info:
R version 3.3.1 (2016-06-21)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: OS X 10.9.5 (Mavericks)
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] rlang_0.1 tidyr_0.6.2
loaded via a namespace (and not attached):
[1] assertthat_0.1 magrittr_1.5 tools_3.3.1 tibble_1.2 Rcpp_0.12.8
Now that quosures have an S3 class, we'd guard all unquoted bare formulas.
It would be useful to explore how much extra work this would be. If it's not too much, it'd be worth it.
Since they're called primarily for their side-effects
I have skimmed the dplyr/tidyeval/rlang documentation and tutorials and I don't remember or see how to convert a string to a quosure easily. What I want to do (and I think it is an important use case) is take the name of a column as a string from some external source (say from colnames(), or from the yarn-control block of an R-markdown document) and then use that string as a variable name. It looks like to do that you have to promote the string up to a quosure- and that is the part I don't know how to do in pure tidyeval idiom. I've tried things like quo()
, but I am missing something.
Below is a specific example with a work-around that shows the effect I want. The only question is how does one produce the variable varQ from the value stored in varName (again, assuming the value stored is a string and not known to the programmer)?
# devtools::install_github("tidyverse/dplyr")
library("dplyr")
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
packageVersion("dplyr")
## [1] '0.5.0.9004'
library("wrapr")
# imagine this string comes from somewhere else
varName <- 'disp'
# The following does not currently work as
# rlang/tidyeval/dplyr is expecting a
# quousure to represent the variable name,
# not a string.
mtcars %>%
select(!!varName)
## Error: `"disp"` must resolve to integer column positions, not string
# How does one idiomatically
# create a quosure varQ that refers
# to the name stored in varName?
# Here is a work-around using wrapr
# to show the desired effect:
stringToQuoser <- function(varName) {
wrapr::let(c(VARNAME = varName), quo(VARNAME))
}
varQ <- stringToQuoser(varName)
# the code we want to run:
mtcars %>%
select(!!varQ) %>%
head()
## disp
## Mazda RX4 160
## Mazda RX4 Wag 160
## Datsun 710 108
## Hornet 4 Drive 258
## Hornet Sportabout 360
## Valiant 225
library(rlang)
eval_tidy(quo(~x), environment())
#> Error: Use env_names() for environments.
In the v0.1 package source on CRAN, the vignette is indexed as "Specifying fonts." The offending line is no. 6 in the file tidy-evaluation.Rmd (which I can't find in GitHub master):
%\VignetteIndexEntry{Specifying fonts}
I presume that should be Tidy evaluation
?
Similar to #31, there is not a C API for messages, would be nice to have one.
reduce_common <- function(x, msg = "Objects must be identical",
operator = identical) {
reduce(x, function(.x, .y) {
if (!operator(.x, .y)) {
stop(msg, call. = FALSE)
}
.y
})
}
reduce_common_lgl <- function(.x, operator = identical) {
safe_reduce_common <- safely(reduce_common)
reduced <- safe_reduce_common(.x, operator = operator)
is.null(reduced$error)
}
#' Companion to identical()
equal <- function(x, y) {
isTRUE(all.equal(x, y))
}
all_equal <- function(x) {
reduce_common_lgl(x, operator = equal)
}
all_identical <- function(x) {
reduce_common_lgl(x)
}
list(1, 1, 1.0001) %>% all_equal()
#> [1] FALSE
list(1, 1, 1.00000000001) %>% all_equal()
#> [1] TRUE
list(1, 1, 1.00000000001) %>% all_identical()
#> [1] FALSE
But with a for
based implementation. Original issue: tidyverse/purrr#234
is_call <- function(x, name = NULL, n = NULL) {
is.call(x) &&
(is.null(name) || identical(x[[1]], as.name(name))) &&
(is.null(n) || length(x) == n + 1L)
}
is_unary_call <- function(x, name = NULL) {
is_call(x, name, n = 1L)
}
is_binary_call <- function(x, name = NULL) {
is_call(x, name, n = 2L)
}
!!! ...
as !!! quosures(...)
Sometimes when signalling an error, it's useful to use the call one level up the stack (i.e. in check()
style functions). Maybe as well as being an logical, call()
could also be an integer with the same interpretation as in call_stack()
etc?
Would be useful to have a function that asserts that ...
is empty - this is useful in S3 methods since generics should have ...
and methods need someway to warn the user about misspelled arguments.
Error message should match base R:
> f(asdf = 10)
Error in f(asdf = 10) : unused argument (asdf = 10)
> f(asdf = 10, yz = 1)
Error in f(asdf = 10, yz = 1) : unused arguments (asdf = 10, yz = 1)
And return NA
if it detects splicing?
quo <- rlang::quosure
quos <- rlang::dots_quosures
enquo <- rlang::catch_quosure
More generally, we'd have one-sided quosures and two-sided formulas.
This makes sense because in mathematics formulas specify a relationship, so have two sides.
This would be helpful to have specific terms because with tidyeval we treat one-sided quosures and two-sided formulas very differently.
For situations like this:
my_mutate <- function(df, expr) {
expr <- catch_quosure(expr)
mean_name <- paste0("mean_", quo_text(expr))
sum_name <- paste0("sum_", quo_text(expr))
summarise(df,
!!mean_name := mean(!!expr),
!!sum_name := sum(!!expr)
)
}
my_mutate(df, a)
Neither quo_text()
not quo_label()
are quite right here. quo_label()
without the backticks might be ok.
lang_
function should take lang
as first argument, not call
.
eval_tidy()
and variants should take quo
, not f
.
env_bind()
, env_define()
and env_assign()
should be merged into one function now that we have name-unquoting and list-splicing syntax for dots.
For tidyverse/tibble#250, we want to throw a descriptive error if a matrix is passed, but:
rlang::friendly_type(rlang::type_of(diag(5)))
#> [1] "a double vector"
Any suggestions?
CC @hadley.
Is there a cleaner way to do this?
fun1 <- function(...) rlang::dots_quos(...)
fun1(iris, mtcars) %>%
sapply(function(x) x %>%
deparse() %>%
gsub("~", "", .))
# "iris" "mtcars"
library(rlang)
#>
#> Attaching package: 'rlang'
#> The following object is masked from 'package:graphics':
#>
#> symbols
is_integer("hello world")
#> [1] TRUE
Discovered via a very puzzling result from purrr::modify_if(is_integer, ...)
.
There are quite a few algorithms where you want to skip 1 and only start comparing at element 2.
It would be useful to have a seq()
variant to facilitate this, maybe one that only counts upwards?
eval_tidy()
doesn't seem to be working in the current version. Using rlang v0.0.0.9019 I get the following:
> eval_tidy(~ 1 + 2 + 3)
~ 1 + 2 + 3
> eval_tidy(~ cyl, mtcars)
~cyl
rlang::as_closure(`[[`)
#> function ()
#> NULL[[]]
#> <environment: base>
Use case: map(some_list,
[[, some_index)
. (By now I know I can omit the `[[`
, but I'm still not sure the behavior shown above is correct.)
e.g. as_character()
should handle factors.
Also we need to distinguish between implicit and explicit coercions. Currently as_character()
etc are used for implicit coercions as well. But we'd want explicit coercion support serialisation of e.g. numeric vectors, but that should be forbidden in implicit coercions.
e.g.
expr <- quote(base::list(x, y))
is_call(expr, quote(base::list))
So as_symbol()
doesn't have to return namespaced symbols (which are actually calls).
character()
sep
or collapse
Naming suggestion: glue()
And provide [.quosures
and is_quosures
. This will be useful for providing error messages (particularly in the scenario where you need to pass ...
to two different functions and want to have separate arguments)
Then dplyr::vars()
would just call this function.
When passed a formula, eval_tidy
just returns the formula and environment instead of actually evaluating:
library(rlang)
str(eval_tidy(~ mean(mpg), mtcars))
#> Class 'formula' language ~mean(mpg)
#> ..- attr(*, ".Environment")=<environment: 0x7fd501bd2630>
str(eval_tidy(quo( mean(mpg) ), mtcars))
#> num 20.1
(When run manually, the environment is R_GlobalEnv
; the pointer is a result of reprex::reprex
/knitr evaluation.)
This is currently breaking the purrr devel version's list_update
and update_list
. On the CRAN version, formulas in update_list
evaluate:
# example from devel ?purrr::list_update
library(purrr)
x <- list(x = 1:10, y = 4, z = list(a = 1, b = 2))
update_list(x, z = ~ x + y) %>% str()
#> List of 3
#> $ x: int [1:10] 1 2 3 4 5 6 7 8 9 10
#> $ y: num 4
#> $ z: num [1:10] 5 6 7 8 9 10 11 12 13 14
but on the rlang-powered devel version,
list_update(x, z = ~ x + y) %>% str()
#> List of 3
#> $ x: int [1:10] 1 2 3 4 5 6 7 8 9 10
#> $ y: num 4
#> $ z:Class 'formula' language ~x + y
#> .. ..- attr(*, ".Environment")=<environment: 0x7fd390d0a030>
update_list(x, z = ~ x + y) %>% str()
#> List of 3
#> $ x: int [1:10] 1 2 3 4 5 6 7 8 9 10
#> $ y: num 4
#> $ z:Class 'formula' language ~x + y
#> .. ..- attr(*, ".Environment")=<environment: 0x7fd390d0a030>
list_update(x, z = rlang::quo(x + y)) %>% str()
#> List of 3
#> $ x: int [1:10] 1 2 3 4 5 6 7 8 9 10
#> $ y: num 4
#> $ z: num [1:10] 5 6 7 8 9 10 11 12 13 14
I can file the issue there if you like, but eval_tidy
is clearly the underlying culprit.
There isn't currently a way to signal a custom condition from C, it would be nice to define functions in rlang to do so. If they are header only and included in inst/include
they can be used from other packages by LinkingTo: rlang
.
This is a minor, pedantic issue, but shouldn't is_formulaish()
be named is_formulaic()
? (Or is there a good reason for the neologism, which I've overlooked?)
Is this intended?
rlang::as_list(letters[1:3])
#> [[1]]
#> [1] "a"
#>
#> [[2]]
#> [1] "b"
#>
#> [[3]]
#> [1] "c"
rlang::as_list("a")
#> Error: Can't convert a string to a list
would this lead to clearer code than a series of if ()
and abort()
?
abort_if(
!is_scalar_list(x) := "must be a scalar list",
!inherits(x, "foo") := "'x' must be a 'foo'",
!inherits(y, "bar") := "`y` must be a `bar`"
)
with warn_if()
and inform_if()
variants. The error message would be passed directly to cnd_abort()
so could be typed conditions as well.
Is this intended?
rlang::as_list(factor(c(a = "a", b = "b")))
#> $a
#> [1] 1
#>
#> $b
#> [1] 2
I was looking for a base substitute which behaves interestingly, too.
as.list(factor(c(a = 1, b = 2)))
#> [[1]]
#> a
#> 1
#> Levels: 1 2
#>
#> [[2]]
#> b
#> 2
#> Levels: 1 2
Hello,
I'm trying to write a simple companion to dplyr's mutate called mutateX
which would allow me to substitute X for column names in mutate; i.e.,
mutate(dft, some_var = some_var + 1)
could be written as mutateX(dft, some_var = X + 1)
.
I'm playing around with the dplyr 0.6.0 RC and rlang to figure out how to go about that. I wrote a quick and dirty implementation that works unless hybrid evaluation is needed, and I'm not sure what the issue is here.
The quick function and my notes:
mutateX <- function(.data, ...) {
modQuos <- rlang::quos(...)
nonMod <- rlang::quos(...)
for (qs in seq_along(modQuos)) {
modQuos[[qs]][[2]] <- do.call(
'substitute',
list(modQuos[[qs]][[2]], list(X=as.name(names(modQuos)[[qs]])))
)
}
# This will not work:
dplyr::mutate(.data, !!!modQuos)
# This works, where we just pass nonMod. But why? The objects look the same.
# dplyr::mutate(.data, !!!nonMod)
}
dft <- data.frame(a = 1:10, b = 101:110)
var <- quo(a)
# This will work
mutate(a, c = (!!var) + 1)
# This will not.
mutateX(a, c = (!!var) + 1)
# If no unquoting is used, then it _does_ work.
mutateX(a, b = X + 1)
mutateX(a, b = b + 1)
e.g.
quo(UQ()(x, y))
Hi all,
I am (perhaps too early) trying to test-drive the new NSE DSL (and very much enjoying it).
I noticed that the .data
pronoun was reintroduced in #85. I also remember there being an .env
pronoun in a previous vignette.
Has the .env
pronoun been removed from the "philosophy", or it is a matter of being patient for it to be reintroduced here?
Thanks!
These vignettes appear to be missing:
vignette("conditions")
vignette("stack")
Should these point to a URL or the vignette on tidy eval? (Or are they WIP?)
I don't like struct()
because in lisps and in C, structs are for data structures with fast member lookup. In R, attributes are stored in a linked list so that doesn't seem fitting. Should this be called sexp()
?
Rename is_parsable_literal()
to is_syntactic_literal()
so there is a connection with what we call "syntactic names".
Apologies for the terrible subject. I have almost no internet, so can't do further research. This is a dplyr error but I suspect the problem is rlang (?).
I noticed this while trying to write an example for someone trying to do map_*()
inside filter()
. I'm not exactly sure what the problem is but with dev (purrr + dplyr), no go. With CRAN (purrr + dplyr), all is well. It seems like something that should work, so I'm assuming this is a bug not a feature.
CRAN purrr and dplyr
library(tidyverse)
iris_tibble <- as_tibble(iris)
map_lgl(iris_tibble$Species, ~ grepl("^v", .x))
#> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [12] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [23] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [34] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [45] FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE
#> [56] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [67] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [78] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [89] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [100] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [111] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [122] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [133] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [144] TRUE TRUE TRUE TRUE TRUE TRUE TRUE
iris_tibble %>%
filter(map_lgl(Species, ~ grepl("^v", .x)))
#> # A tibble: 100 × 5
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> <dbl> <dbl> <dbl> <dbl> <fctr>
#> 1 7.0 3.2 4.7 1.4 versicolor
#> 2 6.4 3.2 4.5 1.5 versicolor
#> 3 6.9 3.1 4.9 1.5 versicolor
#> 4 5.5 2.3 4.0 1.3 versicolor
#> 5 6.5 2.8 4.6 1.5 versicolor
#> 6 5.7 2.8 4.5 1.3 versicolor
#> 7 6.3 3.3 4.7 1.6 versicolor
#> 8 4.9 2.4 3.3 1.0 versicolor
#> 9 6.6 2.9 4.6 1.3 versicolor
#> 10 5.2 2.7 3.9 1.4 versicolor
#> # ... with 90 more rows
Dev purrr and dplyr
library(tidyverse)
iris_tibble <- as_tibble(iris)
map_lgl(iris_tibble$Species, ~ grepl("^v", .x))
#> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [12] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [23] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [34] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [45] FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE
#> [56] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [67] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [78] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [89] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [100] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [111] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [122] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [133] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [144] TRUE TRUE TRUE TRUE TRUE TRUE TRUE
iris_tibble %>%
filter(map_lgl(Species, ~ grepl("^v", .x)))
#> Error in filter_impl(.data, dots): object '.x' not found
I don't know if this is possible, but it would be really useful to have something like return()
that could be called from top-level code being executed by source()
i.e. in examples instead of
if (requireNamespace("RSQLite", quietly = TRUE)) {
...
}
I'd like to be able to do
if (!requireNamespace("RSQLite", quietly = TRUE))
end()
...
Which we'd use in base-type conversions.
Related: our own C-level conversion functions, so we can avoid a copy when coercing S3 classes wrapping a vector (changing an attribute implies a copy).
Add an argument to env_get()
that accepts a predicate function and applies it to a variable when it finds one. If the predicate returns FALSE
, we continue the lookup in the parents.
Would be a nice alternatve to the mode
argument of get()
. May have to be written in C.
When I try to install I get the following compilation error. R 3.3.3 on linux. Is this working for others? If so what am I doing wrong?
devtools::install_github("hadley/rlang")
...
Installation failed: NULL : Command failed:
'/usr/lib64/R/bin/R' CMD INSTALL '/tmp/RtmpDdgMtG/devtools50b16b455b4f/hadley-rlang-82336f5'
* installing to library ‘/my/r/library/3.3’
* installing *source* package ‘rlang’ ...
** libs
In file included from export.c:5:
export.h:10: error: redefinition of typedef ‘DL_FUNC’
/usr/include/R/R_ext/Rdynload.h:36: note: previous declaration of ‘DL_FUNC’ was here
make: *** [export.o] Error 1
ERROR: compilation failed for package ‘rlang’
rlang::as_function(list())
#> Error: Cannot convert objects of type `list` to `function`
Types should not be quoted: tidyverse/dplyr#2448 (comment).
env_get()
, env_set()
, env_has()
, env_unset()
I'm trying to update my package srvyr which has used tidyeval's NSE framework and fit it into the survey package.
I've traced a bug in my code to the behavior of quo
on integers. Instead of returning the environment where it is called, it returns an empty environment. Is this the intended behavior?
library(rlang)
`%>%` <- magrittr::`%>%`
# Both formulas and quo work with variable names
model.frame(~mpg, data = mtcars) %>% head()
#> mpg
#> Mazda RX4 21.0
#> Mazda RX4 Wag 21.0
#> Datsun 710 22.8
#> Hornet 4 Drive 21.4
#> Hornet Sportabout 18.7
#> Valiant 18.1
model.frame(quo(mpg), data = mtcars) %>% head()
#> mpg
#> Mazda RX4 21.0
#> Mazda RX4 Wag 21.0
#> Datsun 710 22.8
#> Hornet 4 Drive 21.4
#> Hornet Sportabout 18.7
#> Valiant 18.1
# But quo returns a quosure with an empty environment when it gets an integer
# while using a formula keeps the environment where it is called
model.frame(~1, data = mtcars) %>% head()
#> data frame with 0 columns and 6 rows
model.frame(quo(1), data = mtcars) %>% head()
#> Error in eval(expr, envir, enclos): could not find function "list"
str(~1)
#> Class 'formula' language ~1
#> ..- attr(*, ".Environment")=<environment: 0x7f877bf970e8>
str(quo(1))
#> Classes 'quosure', 'formula' language ~1
#> ..- attr(*, ".Environment")=<environment: R_EmptyEnv>
FWIW, the reason I am getting this error is because the survey package uses ~1 as an indicator that each observation is an ID. It's not a particularly widespread syntactic style within the package, and could be worked around.
# From survey::svydesign
library(survey)
svydesign(id=~1,strata=~stype, weights=~pw, data=apistrat, fpc=~fpc)
#> Stratified Independent Sampling design
#> svydesign(id = ~1, strata = ~stype, weights = ~pw, data = apistrat,
#> fpc = ~fpc)
I think I could just use quo_name()
to check whether it is 1 and handle as a special case. However, srvyr's code got pretty complex because I was working around something that turned out to be a bug in lazyeval, so wanted to avoid making that mistake again.
Thank you!
See if we can work around this:
dots_values(!! quote(foobar))
#> Error in eval_bare(dot$expr, dot$env) (from expr.R#203) : object 'foobar' not found
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.