Sorry for my poor English but I can not think of a title that could concisely describe my problem, which is a little bit complicated than the title suggests.
Here is what I'd like to achieve:
In the global environment, one can get the name of a variable, say xyz, by calling deparse(substitute(xyz)). I need to use this at several places in my code so I decided to make it a function: getVarName <- function(x){deparse(substitute(x))}.
Then I write some function myfunc in which I need to call getVarName:
myfunc <- function(x){cat(getVarName(x))}
Now here is the problem: when I call myfunc(y), instead of printing out y, it still prints out x. I suspect it has something to do with the environment in which substitute() does the trick, but got no luck in that direction.
What do I have to do to make it right?
P.S. It'll be nice if some could edit the title with a better description of this question, thank you!
From what I saw while testing your code, it appears that deparse(substitute(x)) will only print out the name of the variable which was immediately below it in the call stack. In your example:
getVarName <- function(x){ deparse(substitute(x)) }
myfunc <- function(x){ cat(getVarName(x)) }
myfunc(y)
The call to getVarName() is processing a variable from myfunc() which was called x. In effect, the variable y which you passed is not part of the call stack anymore.
Solution:
Just use deparse(substitute(x)) directly in the function where you want to print the name of the variable. It's concise, and you could justify not having a helper function as easily as having one.
It is typically the kind of functional programmming problem where you can use a decorator:
decorator = function(f)
{
function(...)
{
print(as.list(match.call()[-1]))
f(...)
}
}
foo = function(x,y,z=2) paste0(x,y,z)
superFoo = decorator(foo)
Results:
> xx=34
> superFoo('bigwhale',xx)
[[1]]
[1] "bigwhale"
[[2]]
xx
[1] "bigwhale342"
Related
I have made a pretty big package in R and I have collected a lot of smaller scripts in it. Later on it turned out that I may need to configure it a bit more than what I have made. Here is an example:
analyse <- function(param1,param2,param3,...){
function1 <- function(param1,param2,param3){...}
function2 <- function(param1,param2,param3){...}
function3 <- function(param1,param2,param3){...}
}
As you can see, this function have 3 function inside, and all of them have their parameters. When I call it:
analyse(param1=value1, param2=value2, param3=value3)
My question is, that is it possible to pass parameters to the inner functions as well? And what if the inner functions share a similar name parameter. For example function1 and 2 have the parameter name maxLength and I would like function1 to be maxLength=2 and function2 to be maxLength=10.
I would imagine it like this when calling:
analyse(param1=value1, param2=value2, param3=value3, paramToInnerFunct1=value1, paramToInnerFunct3=value1)
In my case if I give a plus parameter what would go to an inner function to the calling nothing happens.
Is there a way to deal with my problem?
Thank you in advance!
the simple solution is to just add the additional parameters like this:
analyse <- function(param1,param2,param3,
paramToInnerFunct1,paramToInnerFunct3,...){
function1 <- function(param1,param2,param3,paramToInnerFunct1){...}
function2 <- function(param1,param2,param3){...}
function3 <- function(param1,param2,param3,paramToInnerFunct3){...}
}
Edit:
maybe it works without named arguments (...) in your function 'analyse', but you would need the arguments in your 'function1'... though like this:
analyse <- function(param1,param2,param3,...){
function1 <- function(param1,param2,param3,paramToInnerFunct1){...}
function2 <- function(param1,param2,param3){...}
function3 <- function(param1,param2,param3,paramToInnerFunct3){...}
}
Another idea:
if you define the additional arguments like 'paramToInnerFunct1' in your global environment, you still can use them within the 'function1' like:
paramToInnerFunct1 <- "whatever object goes here"
function1 <- function(param1,param2,param3){
# use paramToInnerFunct1 from your global environment here
}
Hope that helps,
Samuel
I created a function to convert a function name to string. Version 1 func_to_string1 works well, but version 2 func_to_string2 doesn't work.
func_to_string1 <- function(fun){
print(rlang::as_string(rlang::enexpr(fun)))
}
func_to_string2 <- function(fun){
is.function(fun)
print(rlang::as_string(rlang::enexpr(fun)))
}
func_to_string1 works:
> func_to_string1(sum)
[1] "sum"
func_to_string2 doesn't work.
> func_to_string2(sum)
Error: Can't convert a primitive function to a string
Call `rlang::last_error()` to see a backtrace
My guess is that by calling the fun before converting it to a string, it gets evaluated inside function and hence throw the error message. But why does this happen since I didn't do any assignments?
My questions are why does it happen and is there a better way to convert function name to string?
Any help is appreciated, thanks!
This isn't a complete answer, but I don't think it fits in a comment.
R has a mechanism called pass-by-promise,
whereby a function's formal arguments are lazy objects (promises) that only get evaluated when they are used.
Even if you didn't perform any assignment,
the call to is.function uses the argument,
so the promise is "replaced" by the result of evaluating it.
Nevertheless, in my opinion, this seems like an inconsistency in rlang*,
especially given cory's answer,
which implies that R can still find the promise object even after a given parameter has been used;
the mechanism to do so might not be part of R's public API though.
*EDIT: see coments.
Regardless, you could treat enexpr/enquo/ensym like base::missing,
in the sense that you should only use them with parameters you haven't used at all in the function's body.
Maybe use this instead?
func_to_string2 <- function(fun){
is.function(fun)
deparse(substitute(fun))
#print(rlang::as_string(rlang::enexpr(fun)))
}
> func_to_string2(sum)
[1] "sum"
This question brings up an interesting point on lazy evaluations.
R arguments are lazily evaluated, meaning the arguments are not evaluated until its required.
This is best understood in the Advanced R book which has the following example,
f <- function(x) {
10
}
f(stop("This is an error!"))
the result is 10, which is surprising because x is never called and hence never evaluated. We can force x to be evaluated by using force()
f <- function(x) {
force(x)
10
}
f(stop("This is an error!"))
This behaves as expected. In fact we dont even need force() (Although it is good to be explicit).
f <- function(x) {
x
10
}
f(stop("This is an error!"))
This what is happening with your call here. The function sum which is a symbol initially is being evaluated with no arguments when is.function() is being called. In fact, even this will fail.
func_to_string2 <- function(fun){
fun
print(rlang::as_string(rlang::ensym(fun)))
}
Overall, I think its best to use enexpr() at the very beginning of the function.
Source:
http://adv-r.had.co.nz/Functions.html
I am trying to write a very basic IF statement in R and am stuck. I thought I'd find someone with the same problem, but I cant. Im sorry if this has been solved before.
I want to check if a variable/object has been assigned, IF TRUE I want to execute a function that is part of a R-package. First I wrote
FileAssignment <- function(x){
if(exists("x")==TRUE){
print("yes!")
x <- parse.vdjtools(x)
} else { print("Nope!")}
}
I assign a filename as x
FILENAME <- "FILENAME.txt"
I run the function
FileAssignment(FILENAME)
I use print("yes!") and print("Nope!") to check if the IF-Statement works, and it does. However, the parse.vdjtools(x) part is not assigned. Now I tested the same IF-statement outside of the function:
if(exists("FILENAME1")==TRUE){
FILENAME1 <- parse.vdjtools(FILENAME1)
}
This works. I read here that it might be because the function uses {} and the if-statement does too. So I should remove the brackets from the if-statement.
FileAssignment <- function(x){
if(exists("x")==TRUE)
x <- parse.vdjtools(x)
else { print("Nope!")
}
Did not work either.
I thought it might be related to the specific parse.vdjtools(x) function, so I just tried assigning a normal value to x with x <- 20. Also did not work inside the function, however, it does outside.
I dont really know what you are trying to acheive, but I wpuld say that the use of exists in this context is wrong. There is no way that the x cannot exist inside the function. See this example
# All this does is report if x exists
f <- function(x){
if(exists("x"))
cat("Found x!", fill = TRUE)
}
f()
f("a")
f(iris)
# All will be found!
Investigate file.exists instead? This is vectorised, so a vector of files can be investigated at the same time.
The question that you are asking is less trivial than you seem to believe. There are two points that should be addressed to obtain the desired behavior, and especially the first one is somewhat tricky:
As pointed out by #NJBurgo and #KonradRudolph the variable x will always exist within the function since it is an argument of the function. In your case the function exists() should therefore not check whether the variable x is defined. Instead, it should be used to verify whether a variable with a name corresponding to the character string stored in x exists.
This is achieved by using a combination of deparse() and
substitute():
if (exists(deparse(substitute(x)))) { …
Since x is defined only within the scope of the function, the superassignment operator <<- would be required to make a value assigned to x visible outside the function, as suggested by #thothai. However, functions should not have such side effects. Problems with this kind of programming include possible conflicts with another variable named x that could be defined in a different context outside the function body, as well as a lack of clarity concerning the operations performed by the function.
A better way is to return the value instead of assigning it to a variable.
Combining these two aspects, the function could be rewritten like this:
FileAssignment <- function(x){
if (exists(deparse(substitute(x)))) {
print("yes!")
return(parse.vdjtools(x))
} else {
print("Nope!")
return(NULL)}
}
In this version of the function, the scope of x is limited to the function body and the function has no side effects. The return value of FileAssignment(a) is either parse.vdjtools(a) or NULL, depending on whether a exists or not.
Outside the function, this value can be assigned to x with
x <- FileAssignment(a)
Can you write a function that prints out its own name?
(without hard-coding it in, obviously)
You sure can.
fun <- function(x, y, z) deparse(match.call()[[1]])
fun(1,2,3)
# [1] "fun"
You can, but just in case it's because you want to call the function recursively see ?Recall which is robust to name changes and avoids the need to otherwise process to get the name.
Recall package:base R Documentation
Recursive Calling
Description:
‘Recall’ is used as a placeholder for the name of the function in
which it is called. It allows the definition of recursive
functions which still work after being renamed, see example below.
As you've seen in the other great answers here, the answer seems to be "yes"...
However, the correct answer is actually "yes, but not always". What you can get is actually the name (or expression!) that was used to call the function.
First, using sys.call is probably the most direct way of finding the name, but then you need to coerce it into a string. deparse is more robust for that.
myfunc <- function(x, y=42) deparse(sys.call()[[1]])
myfunc (3) # "myfunc"
...but you can call a function in many ways:
lapply(1:2, myfunc) # "FUN"
Map(myfunc, 1:2) # (the whole function definition!)
x<-myfunc; x(3) # "x"
get("myfunc")(3) # "get(\"myfunc\")"
The basic issue is that a function doesn't have a name - it's just that you typically assign the function to a variable name. Not that you have to - you can have anonymous functions - or assign many variable names to the same function (the x case above).
How can I ensure that when a function is called it is not allowed to grab variables from the global environment?
I would like the following code to give me an error. The reason is because I might have mistyped z (I wanted to type y).
z <- 10
temp <- function(x,y) {
y <- y + 2
return(x+z)
}
> temp(2,1)
[1] 12
I'm guessing the answer has to do with environments, but I haven't understood those yet.
Is there a way to make my desired behavior default (e.g. by setting an option)?
> library(codetools)
> checkUsage(temp)
<anonymous>: no visible binding for global variable 'z'
The function doesn't change, so no need to check it each time it's used. findGlobals is more general, and a little more cryptic. Something like
Filter(Negate(is.null), eapply(.GlobalEnv, function(elt) {
if (is.function(elt))
findGlobals(elt)
}))
could visit all functions in an environment, but if there are several functions then maybe it's time to think about writing a package (it's not that hard).
environment(temp) = baseenv()
See also http://cran.r-project.org/doc/manuals/R-lang.html#Scope-of-variables and ?environment.
environment(fun) = parent.env(environment(fun))
(I'm using 'fun' in place of your function name 'temp' for clarity)
This will remove the "workspace" environment (.GlobalEnv) from the search path and leave everything else (eg all packages).