I'm trying to extract arguments passed to ... from within an internal function to perform validity check. Since the only purpose of the function is to check ellipsis, I'd like the function to have no parameter and capture the ellipsis from the parent function internally.
Here's a simple example of what I'd like to do:
check_dots <- function() {
# capture ... arguments here
if (rlang::dots_n(...) == 1L && ... == "foo") {
stop()
}
}
(function(...) {
check_dots()
"success"
})("foo", "bar")
I've tried using formals(fun = rlang::caller_fn()) to extract ... arguments without success.
The following, using base R, does what you want:
check_dots = function () {
call = match.call(definition = sys.function(-1L), call = sys.call(-1L), expand.dots = FALSE)
if (length(call$...) == 1L && call$...[[1L]] == 'foo') stop('error')
}
‘rlang’ has caller_call as an rough equivalent of match.call, but it’s missing an option to prevent expanding dots, so I don’t know how to do the same as above using ‘rlang’.
Related
I want to use quoted arguments in my function and I would like to allow the user to specify that they don't want to use the argument by setting it to NULL. However, rlang::ensym throws an error when it receives a NULL argument. Here is my code:
f <- function(var){
rlang::ensym(var)
return(var + 2)
}
# This works
variable = 2
f(variable)
# This throws an error
f(NULL)
The error message is:
Error: Only strings can be converted to symbols
I already tried adding an if-clause with is.null(var) before the expression with rlang::ensym, but of course, this doesn't work as the variable is not yet quoted at this time.
How can I check that the supplied quoted variable is NULL in order to handle it differently?
If you need to allow for NULL, it's more robust to use quosures first. Then you can inspect the quosure to see what's inside. For example
f <- function(var){
var <- rlang::enquo(var)
if (rlang::quo_is_null(var)) {
var <- NULL
} else if (rlang::quo_is_symbol(var)) {
var <- rlang::get_expr(var)
} else {
stop(paste("Expected symbol but found", class(rlang::get_expr(var))))
}
return(var)
}
And that returns
f(variable)
# variable
f(NULL)
# NULL
f(x+1)
# Error in f(x + 1) : Expected symbol but found call
Or you can use whatever logic is appropriate for your actual requirements.
Background
I have a simple function called TBT. This function has a single argument called x. A user can provide any type rdistribution_name() (e.g., rnorm(), rf(), rt(), rbinom() etc.) existing in R for argument x, EXCEPT ONE: "rcauchy()".
Question
I was wondering how R could recognize that a user has provided an rcauchy() as the input for x, and when this is the case, then R issues a warning message?
Here is my R code with no success:
TBT = function(x) {
if( x == rcauchy(...) ) { warning("\n\tThis type of distribution is not supported.") }
}
TBT( x = rcauchy(1e4) )
Error in TBT(rcauchy(10000)) : '...' used in an incorrect context
If you are expeciting them do call to random function when they call your function, you could so
TBT <- function(x) {
xcall <- match.call()$x
if (class(xcall)=="call" && xcall[[1]]=="rcauchy") {
warning("\n\tThis type of distribution is not supported.")
}
}
TBT( x = rcauchy(1e4) )
But this would not catch cases like
x <- rcauchy(1e4)
TBT( x )
R can't track where the data in the x variable came from
How do I list all functions of a certain R file doing something like
list = list.all.functions(file.name, alphabetical = TRUE, ...)
where list is a string vector containing the names of the functions in file.name?
The solution of How to list all the functions and their arguments in an R file? gives no output for me (since I am not interested in arguments I opened a new question).
EDIT
File allometry.R starts with
#==========================================================================================#
#==========================================================================================#
# Standing volume of a tree. #
#------------------------------------------------------------------------------------------#
dbh2vol <<- function(hgt,dbh,ipft){
vol = pft$b1Vol[ipft] * hgt * dbh ^ pft$b2Vol[ipft]
return(vol)
}#end function dbh2ca
#==========================================================================================#
#==========================================================================================#
My main looks like
rm(list=ls())
here = "/directory/of/allometry.R/"
setwd(here)
is_function = function (expr) {
if (! is_assign(expr))
return(FALSE)
value = expr[[3]]
is.call(value) && as.character(value[[1]]) == 'function'
}
function_name = function (expr)
as.character(expr[[2]])
is_assign = function (expr)
is.call(expr) && as.character(expr[[1]]) %in% c('=', '<-', 'assign')
file_parsed = parse("allometry.R")
functions = Filter(is_function, file_parsed)
function_names = unlist(Map(function_name, functions))
Probably too late to join the party, but better late than never.
There is a package called NCmisc which has a function to list all functions in a file and returns a list where the names of the components are the names of the packages they belong to. If there are any functions in the global environment, they will be under the .GobalEnv list component. Simply load all packages the file uses and then run the following:
all.functions <- list.functions.in.file(
filename = "/path/to/file/my_file.R")
Consider a code snippet as follow:
f = function(y) function() y()
f(version)()
Error in f(version)() : could not find function "y"
P.s. It seems that the closure mechanism is quite different from C# Lambda. (?)
Q: How can I capture a function in the closure?
--EDIT--
Scenario: Actually, I would like to write a function factory, and I don't want to add parameter to the nested function.
Like this:
theme_factory = function(theme_fun)
{
function(device)
{
if (!is.onMac()) # Not Mac
{
(device == "RStudioGD") %?% theme_fun(): theme_fun(base_family="Heiti")
}
else
{
theme_fun(base_family="STHeiti")
}
}
}
And I defined two customized theme function for ggplot
theme_bw_rmd = theme_factory(theme_bw)
theme_grey_rmd = theme_factory(theme_grey)
Then I use them like:
function(device)
ggplot(data) + geom_point() something like that + theme_bw_rmd(device)
Thanks.
So the problem is with passing parameter? What about something like this:
alwaysaddone <- function(f) function(...) f(...)+1
biggersum <- alwaysaddone(sum)
sum(1:3)
# 6
biggersum(1:3)
# 7
You can use ... to "pass-through" any parameters you like.
Use eval(func, envir = list(... captured parameters)) or substitute(func, envir) to eval the captured function in a specific environment.
S4 classes allow you to define validity checks using validObject() or setValidity(). However, this does not appear to work for ReferenceClasses.
I have tried adding assert_that() or if (badness) stop(message) clauses to the $initialize() method of a ReferenceClass. However, when I simulate loading the package (using devtools::load_all()), it must try to create some prototype class because the initialize method executes and fails (because no fields have been set).
What am I doing wrong?
Implement a validity method on the reference class
A = setRefClass("A", fields=list(x="numeric", y="numeric"))
setValidity("A", function(object) {
if (length(object$x) != length(object$y)) {
"x, y lengths differ"
} else NULL
})
and invoke the validity method explicitly
> validObject(A())
[1] TRUE
> validObject(A(x=1:5, y=5:1))
[1] TRUE
> validObject(A(x=1:5, y=5:4))
Error in validObject(A(x = 1:5, y = 5:4)) :
invalid class "A" object: x, y lengths differ
Unfortunately, setValidity() would need to be called explicitly as the penultimate line of an initialize method or constructor.
Ok so you can do this in initialize. It should have the form:
initialize = function (...) {
if (nargs()) return ()
# Capture arguments in list
args <- list(...)
# If the field name is passed to the initialize function
# then check whether it is valid and assign it. Otherwise
# assign a zero length value (character if field_name has
# that type)
if (!is.null(args$field_name)) {
assert_that(check_field_name(args$field_name))
field_name <<- field_name
} else {
field_name <<- character()
}
# Make sure you callSuper as this will then assign other
# fields included in ... that weren't already specially
# processed like `field_name`
callSuper(...)
}
This is based on the strategy set out in the lme4 package.