I know how partial function works. However, when I look at the source code of partial(), I don't quite understand how it works. Could anyone tell me how each line works?
> partial
function (`_f`, ..., .env = parent.frame(), .lazy = TRUE)
{
stopifnot(is.function(`_f`))
if (.lazy) {
fcall <- substitute(`_f`(...))
}
else {
fcall <- make_call(substitute(`_f`), .args = list(...))
}
fcall[[length(fcall) + 1]] <- quote(...)
args <- list(... = quote(expr = ))
make_function(args, fcall, .env)
}
<environment: namespace:pryr>
What is `_f`? Why using substitute() in this way
substitute(`_f`(...))
What is this?
quote(expr = )
Why is the following line necessary?
fcall[[length(fcall) + 1]] <- quote(...)
Some operations in R don't look like function calls, but they are. For example, 3+4 can also be written as `+`(3,4). In this case, since the partial function works by pre-filling in arguments, the "substitute" here works by substituting any variables found in the environment into "...".
quote() works by capturing the action
Seems like the fcall line is used to maintain what was originally placed into the partial call
Related
Sometimes in R a function wants a string or sometimes it wants an object.
For example, rm(x); and rm("x"); work the same.
NOTE: In this example x or "x" is NOT a function. I generically call it an OBJECT. In the example below, I am referring to functions as FN.STR or FN.OBJ, but the QUESTION is looking for a general OBJECT MANIPULATOR. Given a thing, determine if it is a string or object, and return a string/object as requested by the Function. A function then serves as a general API to access R objects.
> rm
function (..., list = character(), pos = -1, envir = as.environment(pos),
inherits = FALSE)
{
dots <- match.call(expand.dots = FALSE)$...
if (length(dots) && !all(vapply(dots, function(x) is.symbol(x) ||
is.character(x), NA, USE.NAMES = FALSE)))
stop("... must contain names or character strings")
names <- vapply(dots, as.character, "")
if (length(names) == 0L)
names <- character()
list <- .Primitive("c")(list, names)
.Internal(remove(list, envir, inherits))
}
<bytecode: 0x00000136a0f22d80>
<environment: namespace:base>
This is useful on this function because the USER doesn't have to remember: do I need the string or object. In fact, you can mix: x=1; y=2; rm(x,"y"); The ... dots have been expended to make this happen... Ideally, it would be nice to save the ... dots for passing parameters through to other functions or for lazy loading like sum allows: sum(1,2,3) == sum(c(1,2,3)). [Or maybe ..1 could be reimagined to allow for multiple dots: ...a, ...b, {ldots}, ...z where the name/order of the ...{letter} would allow lots of match.call magic. Regardless, even this magic is happening in the function, not as a standalone VARIADIC external magicFunction]
Objective
Anyway, I am trying to write a few variadic functions that allow the USER to input either the FUN as fn.str or as fn.obj. At the GLOBAL scope, the following will work:
str.fromObjectName = function(objname)
{
res = deparse(substitute(objname));
res = str.replace('"', "", res);
return(res);
}
WHERE str.replace is a extension of gsub, so for simplicity, let's say:
str.replace = function(search, replace, str)
{
gsub(search, replace, str, fixed=TRUE);
}
So if it is an object, I get the string.name of it (whether or not it actually exists, no error thrown). If it is a string, it has an extra " wrapping it, so the str.replace takes care of that.
As a procedural function, I would like to be able to access this in OTHER functions. But with the nature of the R stack (or my lack of understanding), I can't seem to get it to work. I found a solution in some baseR code that I have applied to use the ... dots to TRAP the potential object. But it has some limitations: I have to use the ... dots so I can't use them for other purposes, and if I call a function from a function the evaluation of the original function name gets lost, so I apply the character.only=FALSE when I call the function INTERNALLY to the other function where at some point the FN.OBJ was converted to FN.STR
So if I review the base packages with character.only I believe the help or library uses it to trap the pkg as a string or object. Maybe the solution is trivial which I am missing, but as I review the base code, it seems like it may be a challenge. It appears the function doesn't know what to do automatically without the character.only flag.
> library
function (package, help, pos = 2, lib.loc = NULL, character.only = FALSE,
...
if (!character.only)
package <- as.character(substitute(package))
...
else if (!missing(help)) {
if (!character.only)
help <- as.character(substitute(help))
...
else invisible(.packages())
}
<bytecode: 0x0000013699060b10>
<environment: namespace:base>
An example
Here is a preamble of one function:
function.info = function(..., character.only=FALSE)
{
if(character.only)
{
fn.str = unlist(list(...));
} else {
fn.str = str.fromObjectName(...);
}
}
NOTE: the ... passthrough allows the GLOBAL function to correctly scope.
This allows the function str "sum" or the function object sum to be inputed into the function (making life BETTER for the user). In the spirit of DRY and VARIADIC programming, it would be nice if I could do this as one external function to function.info ... and allow multiple fn objects to be passed in as parameters that are either the str "sum" or the object sum which INTERNALLY for most purposes I just want the resulting str.
Question
Given a function, how to allow a user to pass multiple FUN elements as either obj/string (mix allowed) using an external function (DRY = don't repeat yourself). In the example, I am referring to FUN.OBJ, but the goal would be to return anything that could be called an OBJ in R, not just a function.
magicFunction = function(FUN.OBJ.OR.STR, return="STR")
{
# do something here ... FUN.OBJ could be any R.OBJ
FUN.AS.STR;
# or if return = "OBJ"
FUN.AS.OBJ
}
v.math = function(data=c("#abcdef","#123456"), FUN, param="hi", FUN.pre="hex2dec", FUN.post=dec2hex)
{
# takes input [whether a str/obj] and returns a string/object.
fn.str = magicFunction(FUN);
fn.pre = magicFunction(FUN.pre);
fn.post = magicFunction(FUN.post);
# get to the main event
}
In the above, "hex2dec" is a string (as in "foo") and dec2hex is an object (as in bar): both referring to functions [something akin to match.fun(base::sum) or match.fun("base::sum"); I think currently it only searches the TOP of the stack.]. I can call my function str.fromObjectName on them in the GLOBAL sphere and get what I want, but when placed inside another function, it will return something else. I did a bunch of sys.call VOODOO that I didn't understand fully, and it would allow it to work at one-level deep of function calls (I guess because of the nature of the call stack). And it would only work on have one FUN to evaluate in the v.math where I have 3 functions to evaluate.
Here's a wrapper around match.fun to allow for the user to include :: in a string argument:
as_fun = function(x) {
if(is.character(x) && grepl("::", x)) return(eval(parse(text = x)))
match.fun(x)
}
as_fun(base::sum)
# function (..., na.rm = FALSE) .Primitive("sum")
as_fun("base::sum")
# function (..., na.rm = FALSE) .Primitive("sum")
as_fun(sum)
# function (..., na.rm = FALSE) .Primitive("sum")
as_fun("sum")
# function (..., na.rm = FALSE) .Primitive("sum")
I am using a package that has 2 functions which ultimately look like the following:
pkgFun1 <- function(group) {
call <- match.call()
pkgFun2(call)
}
pkgFun2 <- function(call) {
eval(call$group)
}
If I just call pkgFun1(group = 2), it works fine. But I want to call it from a function:
myFun <- function(x) {
pkgFun1(group = x)
}
myFun(x = 2)
## Error in eval(call$group) : object 'x' not found
Is there any way to avoid this error, if I can't modify the package functions, but only myFun?
There are similar questions, such as Issue with match.call or Non-standard evaluation in a user-defined function with lapply or with in R, but my particular issue is that I can't modify the part of code containing the eval call.
It's pkgFun2 that is wrong, so I think you're out of luck without some weird contortions. It needs to pass the appropriate environment to eval(); if you can't modify it, then you can't fix it.
This hack might appear to work, but in real life it doesn't:
pkgFun1 <- function(group) {
call <- match.call()
f <- pkgFun2
environment(f) <- parent.frame()
f(call)
}
With this, you're calling a copy of pkgFun2 modified so its environment is appropriate to evaluate the call. It works in the test case, but will cause you untold grief in the future, because everything that is not local in pkgFun2 will be searched for in the wrong place. For example,
myFun <- function(x) {
eval <- function(...) print("Gotcha!")
pkgFun1(group = x)
}
myFun(x = 2)
# [1] "Gotcha!"
Best is to fix pkgFun2. Here's one fix:
pkgFun1 <- function(group) {
call <- match.call()
pkgFun2(call, parent.frame())
}
pkgFun2 <- function(call, envir) {
eval(call$group, envir = envir)
}
Edited to add: Actually, there is another hack that is not so weird that should work with your original pkgFun1 and pkgFun2. If you force the evaluation of x to happen in myFun so that pkgFun1 never sees the expression x, it should work. For example,
myFun <- function(x) {
do.call("pkgFun1", list(group = x))
}
If you do this, then after myFun(2), the pkgFun1 variable call will be pkgFun1(group = 2) and you won't get the error about x.
I'm trying to use the curve3d function in the emdbook-package to create a contour plot of a function defined locally inside another function as shown in the following minimal example:
library(emdbook)
testcurve3d <- function(a) {
fn <- function(x,y) {
x*y*a
}
curve3d(fn(x,y))
}
Unexpectedly, this generates the error
> testcurve3d(2)
Error in fn(x, y) : could not find function "fn"
whereas the same idea works fine with the more basic curve function of the base-package:
testcurve <- function(a) {
fn <- function(x) {
x*a
}
curve(a*x)
}
testcurve(2)
The question is how curve3d can be rewritten such that it behaves as expected.
You can temporarily attach the function environment to the search path to get it to work:
testcurve3d <- function(a) {
fn <- function(x,y) {
x*y*a
}
e <- environment()
attach(e)
curve3d(fn(x,y))
detach(e)
}
Analysis
The problem comes from this line in curve3d:
eval(expr, envir = env, enclos = parent.frame(2))
At this point, we appear to be 10 frames deep, and fn is defined in parent.frame(8). So you can edit the line in curve3d to use that, but I'm not sure how robust this is. Perhaps parent.frame(sys.nframe()-2) might be more robust, but as ?sys.parent warns there can be some strange things going on:
Strictly, sys.parent and parent.frame refer to the context of the
parent interpreted function. So internal functions (which may or may
not set contexts and so may or may not appear on the call stack) may
not be counted, and S3 methods can also do surprising things.
Beware of the effect of lazy evaluation: these two functions look at
the call stack at the time they are evaluated, not at the time they
are called. Passing calls to them as function arguments is unlikely to
be a good idea.
The eval - parse solution bypasses some worries about variable scope. This passes the value of both the variable and function directly as opposed to passing the variable or function names.
library(emdbook)
testcurve3d <- function(a) {
fn <- eval(parse(text = paste0(
"function(x, y) {",
"x*y*", a,
"}"
)))
eval(parse(text = paste0(
"curve3d(", deparse(fn)[3], ")"
)))
}
testcurve3d(2)
I have found other solution that I do not like very much, but maybe it will help you.
You can create the function fn how a call object and eval this in curve3d:
fn <- quote((function(x, y) {x*y*a})(x, y))
eval(call("curve3d", fn))
Inside of the other function, the continuous problem exists, a must be in the global environment, but it is can fix with substitute.
Example:
testcurve3d <- function(a) {
fn <- substitute((function(x, y) {
c <- cos(a*pi*x)
s <- sin(a*pi*y/3)
return(c + s)
})(x, y), list(a = a))
eval(call("curve3d", fn, zlab = "fn"))
}
par(mfrow = c(1, 2))
testcurve3d(2)
testcurve3d(5)
I'm trying to read a function call as a string and evaluate this function within another function. I'm using eval(parse(text = )) to evaluate the string. The function I'm calling in the string doesn't seem to have access to the environment in which it is nested. In the code below, my "isgreater" function finds the object y, defined in the global environment, but can't find the object x, defined within the function. Does anybody know why, and how to get around this? I have already tried adding the argument envir = .GlobalEnv to both of my evals, to no avail.
str <- "isgreater(y)"
isgreater <- function(y) {
return(eval(y > x))
}
y <- 4
test <- function() {
x <- 3
return(eval(parse(text = str)))
}
test()
Error:
Error in eval(y > x) : object 'x' not found
Thanks to #MrFlick and #r2evans for their useful and thought-provoking comments. As far as a solution, I've found that this code works. x must be passed into the function and cannot be a default value. In the code below, my function generates a list of results with the x variable being changed within the function. If anyone knows why this is, I would love to know.
str <- "isgreater(y, x)"
isgreater <- function(y, x) {
return(eval(y > x))
}
y <- 50
test <- function() {
list <- list()
for(i in 1:100) {
x <- i
bool <- eval(parse(text = str))
list <- append(list, bool)
}
return(list)
}
test()
After considering the points made by #r2evans, I have elected to change my approach to the problem so that I do not arrive at this string-parsing step. Thanks a lot, everyone.
I offer the following code, not as a solution, but rather as an insight into how R "works". The code does things that are quite dangerous and should only be examined for its demonstration of how to assert a value for x. Unfortunately, that assertion does destroy the x-value of 3 inside the isgreater-function:
str <- "isgreater(y)"
isgreater <- function(y) {
return(eval( y > x ))
}
y <- 4
test <- function() {
environment(isgreater)$x <- 5
return(eval(parse(text = str) ))
}
test()
#[1] FALSE
The environment<- function is used in the R6 programming paradigm. Take a look at ?R6 if you are interested in working with a more object-oriented set of structures and syntax. (I will note that when I first ran your code, there was an object named x in my workspace and some of my efforts were able to succeed to the extent of not throwing an error, but they were finding that length-10000 vector and filling up my console with logical results until I escaped the console. Yet another argument for passing both x and y to isgreater.)
Is it possible in R to run some code when calling a missing (yet undefined) function or when evaluating an inexistent symbol?
Or: is there any way to load a library in such a situation?
In the end, I would like to have something like this:
autoload.table <- list(foo = source("foo.R"), bar = library("bar"))
foo()
#=> load "foo.R" and evaluate `foo()`
edit:
Building on the solution by #Miff, I came up with this function, which avoids the string mangling:
tAutoload <- function (name, expr) {
cl <- as.list(match.call())
sname <- as.character(cl$name)
if (!exists(sname)) {
assign(sname,
eval(substitute(function (...) {
rm(name)
expr
name(...)
})), envir = .GlobalEnv)
}
}
This can be used as follows:
tAutoload(foo, source("foo.R"))
tAutoload(bar, library("bar"))
Upon first invocation, e.g., foo() will remove itself and then execute the assigned action.
I'm not sure how generally applicable this code is - I think it may not be robust to a different types of argument matching in foo and bar, but how about something like this:
at <- list(foo = 'source("foo.R")', qplot = 'library(ggplot2)') #too lazy to type autoloader.tablF
for (i in 1:length(at))
assign(names(at)[i], eval(parse(text=paste0("function(...){ rm(",names(at)[i],",envir=.GlobalEnv);",at[[i]],"; ",names(at)[i],"(...) }")),envir=.GlobalEnv))
What does that mess do? For each element in the at list, create a function in the global environment, which deletes itself, runs the code from at[[i]], then runs the function again, with the arguments originally used, which should now call the new version loaded. So foo now has the value:
function(...){ rm(foo,envir=.GlobalEnv);source("foo.R"); foo(...) }
Example:
> foo
function(...){ rm(foo,envir=.GlobalEnv);source("foo.R"); foo(...) }
> foo(1)
fooing 1
> foo
function(x) cat("fooing", x, "\n") #Now imported from foo.R
or for qplot:
> qplot
function(...){ rm(qplot,envir=.GlobalEnv);library(ggplot2); qplot(...) }
> qplot(diamonds$cut, diamonds$carat) #produces a plot
> qplot #now prints definition from ggplot2
Create a blank function!
foo <- function(){ }