I am not entirely sure I understand what substitute does, although I've used in it code before. Today I encountered in shiny::exprToFunction the following lines of code:
function (expr, env = parent.frame(2), quoted = FALSE, caller_offset = 1)
{
expr_sub <- eval(substitute(substitute(expr)),
...
}
Can someone please explain why nested substitute is used here? A easy to run example would really help.
Take a look at
a<-function(aa) {
b(aa)
}
b<-function(bb) {
z(bb)
}
z<-function(zz) {
print(substitute(zz))
print(substitute(substitute(zz)))
print(eval(substitute(substitute(zz)), parent.frame()))
}
q<-5
a(q)
# bb
# substitute(bb)
# aa
The first/inner substitute grabs the name/symbol that was passed to the called function. The second/outer substitute() simply wraps a substitute() command around that discovered name/symbol. Then that substitute() is evaluated in the parent environment where it came from.
The method of using substitute to capture variable names only works when parameters are still promises; that is, they have not yet been evaluated.
Related
I read some post where Hadley made a joke about a self destructing function. I thought this would be relatively simple to implement but turns out it's not.
Here is an attempt to write a function named self_delete that is a quine and attempts to self destruct after printing its body. The idea was to search for the function's name in .GlobalEnv and delete it but that doesn't work. I would like to understand why this is the case.
self_delete<- function(){
print(body(self_delete))
rm(list=lsf.str(pattern="self_delete"))
}
Calling the above prints the following as expected but does not delete itself from .Globalenv, what am I missing? I did try with rm and ls too with no luck
self_delete()
{
print(body(self_delete))
rm(list = lsf.str(pattern = "self_delete"))
}
You forgot to set the envir argument to rm(), so it's trying to delete self_delete from the calling frame, not from globalenv().
This works:
self_delete <- function(){
print(body(self_delete))
rm("self_delete", envir = globalenv())
}
I have a package which provides a script and some functions. Within the script I assign a variable which will be used by the function. This works if the function gets executed within the script but might fail if I just call the function since the variable doesn't exist.
If I use devtools::check() I get warnings, that the variable within the function isn't defined. How can I handle this properly?
Edit
I am thinking about to use get() within the function to assign the variable within the function to get rid of this warnings. So the question is, is myp2 the correct way of doing something like this? Maybe some trycatch to handle errors?
ab <- c(1,2,3)
myp1 <- function() {
print(ab)
return(1)
}
myp2 <- function() {
ab <- get('ab')
print(ab)
return(1)
}
myp1()
myp2()
You could do something like
if(!exists("your variable")){
stop("You have not defined your variable")}
This would check to see if what you are looking for exists. A better practice would be to define the variable in the function and have the default value be the name of the thing for which you are looking.
myp <- function(x) {
print(x)
return(1)
}
ab <- c(1,2,3)
myp(x = ab)
If possible, it would be also better to substitute the script with a function.
I'm working in a call stack of variable depth that looks like
TopLevelFunction
-> <SomeOtherFunction(s), 1 or more>
-> AssignmentFunction
Now, my goal is to assign a variable created in AssignmentFunction, to the environment of TopLevelFunction. I know I can extract the stack with sys.calls, so my current approach is
# get the call stack and search for TopLevelFunction
depth <- which(stringr::str_detect(as.character(sys.calls()), "TopLevelFunction"))
# assign in TopLevelFunction's environment
assign(varName, varValue, envir = sys.frame(depth))
I'm more or less fine with that, though I am not sure if that's a good idea to convert call objects to character vectors. Is that approach error-prone? More generally, how would you search for a specific parent environment, knowing only the name of the function?
A fn like this
get_toplevel_env <- function(env) {
if (identical(parent.env(env), globalenv())) {
env
} else {
get_toplevel_env(parent.env(env))
}
}
And use it within any level of your nested-functions like this?
get_toplevel_env(as.environment(-1))
I'm not sure if I understood correctly what you want to do, but, woulnd't it work to use parent.env(as.environment(-1))?
In this example it seems to work.
fn1 <- function() {
fn1.1 <- function(){
assign("parentvar", "PARENT",
envir = parent.env(as.environment(-1)))
}
fn1.1()
print(parentvar)
}
fn1()
Maybe other possibility is to use <<-, which assigns in the global environment, I think. But maybe that's not what you want.
Is there an R function that lists all the functions in an R script file along with their arguments?
i.e. an output of the form:
func1(var1, var2)
func2(var4, var10)
.
.
.
func10(varA, varB)
Using [sys.]source has the very undesirable side-effect of executing the source inside the file. At the worst this has security problems, but even “benign” code may simply have unintended side-effects when executed. At best it just takes unnecessary time (and potentially a lot).
It’s actually unnecessary to execute the code, though: it is enough to parse it, and then do some syntactical analysis.
The actual code is trivial:
file_parsed = parse(filename)
functions = Filter(is_function, file_parsed)
function_names = unlist(Map(function_name, functions))
And there you go, function_names contains a vector of function names. Extending this to also list the function arguments is left as an exercise to the reader. Hint: there are two approaches. One is to eval the function definition (now that we know it’s a function definition, this is safe); the other is to “cheat” and just get the list of arguments to the function call.
The implementation of the functions used above is also not particularly hard. There’s probably even something already in R core packages (‘utils’ has a lot of stuff) but since I’m not very familiar with this, I’ve just written them myself:
is_function = function (expr) {
if (! is_assign(expr)) return(FALSE)
value = expr[[3L]]
is.call(value) && as.character(value[[1L]]) == 'function'
}
function_name = function (expr) {
as.character(expr[[2L]])
}
is_assign = function (expr) {
is.call(expr) && as.character(expr[[1L]]) %in% c('=', '<-', 'assign')
}
This correctly recognises function declarations of the forms
f = function (…) …
f <- function (…) …
assign('f', function (…) …)
It won’t work for more complex code, since assignments can be arbitrarily complex and in general are only resolvable by actually executing the code. However, the three forms above probably account for ≫ 99% of all named function definitions in practice.
UPDATE: Please refer to the answer by #Konrad Rudolph instead
You can create a new environment, source your file in that environment and then list the functions in it using lsf.str() e.g.
test.env <- new.env()
sys.source("myfile.R", envir = test.env)
lsf.str(envir=test.env)
rm(test.env)
or if you want to wrap it as a function:
listFunctions <- function(filename) {
temp.env <- new.env()
sys.source(filename, envir = temp.env)
functions <- lsf.str(envir=temp.env)
rm(temp.env)
return(functions)
}
I went through the R documentation and I could not find a clue for this. So the problem is that it seems that l$sigm2 is also assigned when l$sigm2_prior is assigned. Does R behave like this because their similar names? Is there a way go around it?
Function call:
l$sigm2 is not assigned if I change l$sigm2_prior 's name to l$prior.
lik_gaussian(lik=lik[[1]],sigm2_prior=pn[[1]], debug=TRUE);
function:
lik_gaussian <-function(...){
l <- list(...);
inputarray <- NULL;
if(!(length(l$lik)==0)){
inputarray <- c(l$lik);
}
if(!(length(l$sigm2)==0)){
inputarray <- c(inputarray, l$sigm2);
}
if(!(length(l$sigm2_prior)==0)){
inputarray <- c(inputarray,l$sigm2_prior);
}
print(inputarray);
return(inputarray)
}
Thanks in advance.
The '$' operator uses partial matching. That is, if you evaluate l$sigm2 it will actually match to l$sigm2_prior as well. To use exact matching you need to use '[[' or and the name of the object as a string:
l[['sigm2_prior']]
'[[' differs from '$' in that it has the exact argument set to TRUE by default. Se also:
?'$'