I am trying to define options programmatically by using a string vector as shown below. However, the option does not get defined and it returns a NULL value. Are there any best practices or functions for this?
f <- "z"
options(f = TRUE)
getOption("z")
# returns NULL
According to the docs:
Options can also be passed by giving a single unnamed argument which is a named list
So you can do
f <- list(z = TRUE)
options(f)
getOption("z")
#> [1] TRUE
Or, if you want to be able to use the input format in your question, you can use the following function:
prog_options <- function(...)
{
mc <- as.list(match.call()[-1])
names(mc) <-
sapply(names(mc), function(x) eval(as.name(x), envir = parent.frame()))
options(mc)
}
Which allows the following:
f <- "z"
g <- "y"
prog_options(f = TRUE, g = "Yes")
getOption("z")
#> [1] TRUE
getOption("y")
#> [1] "Yes"
Related
Given a regular R function f, I'd like to be able to create a new function f_debug that acts just like f, but lets me keep track of all the assignments to function-local variables that happened inside it.
For example:
f <- function(x, y) {
z <- x + y
df <- data.frame(z=z)
df
}
# This function doesn't work as intended - would like it to (in the case of `f` above)
# write out a list containing `z` and `df` to an RDS file
capturing <- function(func) {
e <- new.env()
altered <- function(...) {
parent <- parent.frame()
e <- something...(func, environment(), parent, etc., etc.)
result <- func(...)
saveRDS(as.list(e), 'foo.rds')
result
}
environment(func) <- e
altered
}
f_debug <- capturing(f)
I'm not sure whether my knowledge gap to do this is large or small, anyone have a solution?
Solution 1: Steal the function's code
Here's a solution which doesn't return a new function which captures intermediate calculations, but rather calls the given function's code internally. There's some limitations, such as it probably only works with named arguments. Instead of storing the intermediate calculations as an RDS, it attaches them as an attribute.
capturing <- function(fun, ...) {
fun <- match.fun(fun)
code <- body(fun)
parent <- environment(fun)
env <- new.env(parent = parent)
for (val in names(list(...))) {
env[[val]] <- list(...)[[val]]
}
result <- eval(code, envir = env, enclos = parent.frame())
attr(result, "intermediate") <- env
result
}
my_add <- function(x, y) {
z <- x+y
u <- x-y
w <- x*y
x + y
}
intermediates <- function(x) {
attr(x, "intermediate", exact = TRUE)
}
value <- capturing(my_add, x = 1, y = 7)
ls(envir = intermediates(value))
#> [1] "u" "w" "x" "y" "z"
intermediates(value)$x
#> [1] 1
# Created on 2022-02-08 by the reprex package (v2.0.1)
Solution 2: Modify the function's code
One weakness of this solution is that if the chosen function features a call to on.exit(add=FALSE), some additional work needs to be done to modify the function so the internal environment is captured. However, it does work when the function accepts ... arguments.
my_add <- function(x, y) {
z <- x+y
u <- x-y
w <- x*y
x + y
}
insert_capture <- function(code) {
# `<<-` assigns into the global environment if no variable of the given name is found
# while traveling up to the global environment. If you need this assignment to go elsewhere,
# I'd recommend passing in `assign()`. Of course, you could also modify the `on.exit()`
# to use saveRDS.
parse(text=append(deparse(code),
"on.exit(._last_capture <<- environment(), add = TRUE)",
after = 1L))
}
capturing2 <- function(fun) {
fun <- match.fun(fun)
code <- insert_capture(body(fun))
body(fun) <- code
fun
}
my_add2 <- capturing2(my_add)
my_add2(1, 7)
#> [1] 8
ls(envir = ._last_capture)
#> [1] "u" "w" "x" "y" "z"
._last_capture$u
#> [1] -6
Created on 2022-02-08 by the reprex package (v2.0.1)
What you are describing is already implemented in base R with utils::dump.frames, in an even more sophisticated way. It saves the frame (environment) associated with each call in the call stack to an object of class "dump.frames", which you can explore retroactively with utils::debugger as if you had actually run your code under a debugger.
capturing <- function(func, ...) {
cc <- as.call(c(quote(utils::dump.frames), list(...)))
cc <- call("on.exit", cc, add = TRUE)
body(func) <- call("{", cc, body(func))
func
}
capturing injects the call on.exit(utils::dump.frames(...), add = TRUE) into the body of func and returns the modified function.
Here, ... is a list of arguments to dump.frames:
dumpto, a character string giving the name to be used for the "dump.frames" object
to.file, a logical flag indicating whether the "dump.frames" object should be assigned in the global environment or save-ed to paste0(dumpto, ".rda") in the current working directory
include.GlobalEnv, a logical flag indicating whether the global environment should be saved as well
A quick example, which you should try yourself:
tmp <- tempfile()
dir.create(tmp)
cwd <- setwd(tmp)
f <- function(x, y) {
z <- x + y
z + 1
}
g <- capturing(f, dumpto = "zzz", to.file = TRUE)
h <- function(a, b) {
d <- g(a, b)
d + 1
}
h12 <- h(1, 2)
load("zzz.rda")
zzz
## $`h(1, 2)`
## <environment: 0x14c16cb58>
##
## $`#2: g(a, b)`
## <environment: 0x14c16ca40>
##
## attr(,"error.message")
## [1] ""
## attr(,"class")
## [1] "dump.frames"
ls(zzz[[1L]])
## [1] "a" "b"
ls(zzz[[2L]])
## [1] "z" "x" "y"
utils::debugger(zzz)
## Message: Available environments had calls:
## 1: h(1, 2)
## 2: #2: g(a, b)
##
## Enter an environment number, or 0 to exit
## Selection: 2
## Browsing in the environment with call:
## #2: g(a, b)
## Called from: debugger.look(ind)
## Browse[1]> ls()
## [1] "x" "y" "z"
## Browse[1]> x == 1 && y == 2 && z == x + y
## [1] TRUE
## Browse[1]> Q
setwd(cwd)
unlink(tmp, recursive = TRUE)
See ?browser if you are unfamiliar with R's environment browser.
My capturing function has the limitation that on.exit calls in the body of func must also use add = TRUE. If you have written func yourself, then it is not much of a limitation at all, and passing add = TRUE is a good habit anyway.
Ultimately, there is no completely safe way to inject code into functions, but, in an interactive setting, I would say that this level of "unsafety" is fine.
I'd like to get argument names from function call:
testFun <- function(x = 1:20, z = list(a = 1, b = 2)) x %>% sin %>% sum
getArgNames <- function(value) {
# function should return all arguments names - in this case c("x", "z")
}
arg.names <- getArgNames(testFun())
And it is important to not to evaluate function before getting argument names. Any ideas?
Using the same formalArgs suggested by #Akrun (and also in the almost duplicate Get the argument names of an R function):
getArgNames <- function(value) formalArgs(deparse(substitute(value)[[1]]))
substitute(value) quotes the input, to prevent immediate evaluation, [[1]] retrieves the function from the parsed input, deparse turns it into character (since formalArgs can take the function name as character).
getArgNames(testFun())
#[1] "x" "z"
We can use formalArgs
formalArgs(testFun)
#[1] "x" "z"
If we need to pass the parameter as executable function
library(rlang)
getArgNames <- function(value) {
v1 <- enquo(value)
args <- formalArgs(get(gsub("[()]", "", quo_name(v1))))
list(args, value)
}
getArgNames(testFun())
#[[1]]
#[1] "x" "z"
#[[2]]
#[1] 0.9982219
I have the following code:
fn <- 'George'
mn <- 'Walker'
ln <- 'Bush'
f <- function(...) { print(list(...)) }
When I call it, it produces the following output:
f(fn,mn,ln)
[[1]]
[1] "George"
[[2]]
[1] "Walker"
[[3]]
[1] "Bush"
Suppose I wanted something similar to this (note the parameter names):
fn:George
mn:Walker
ln:Bush
Question: I know how to get the VALUES of the arguments inside a function. How do I get the NAMES of the arguments inside the function?
Thanks, CC.
You may use
f <- function(...) {
nm1 <- as.list(match.call()[-1])
val <- list(...)
cat(paste(nm1, val, sep=":", collapse="\n"),'\n') }
f(fn,mn,ln)
#fn:George
#mn:Walker
#ln:Bush
What I'm trying to do is trivial but I've not found a clear solution to it:
For instance, I have the following function:
sample.function <- function(a, b, named="test") {
...
}
I wish I could inspect the function and obtain the arguments (maybe as an R list), given ret is the returned value of the desired function, fhe following assertions should be all True
ret <- magicfunction(sample.function)
ret[[1]] == "a"
ret[[2]] == "b"
ret$named == "test"
can it be done?
Here are a few things you can look at, inside or outside of the function.
> f <- function(FUN = sum, na.rm = FALSE) {
c(formals(f), args(f), match.fun(FUN))
}
> f()
$FUN
sum
$na.rm
[1] FALSE
[[3]]
function (FUN = sum, na.rm = FALSE)
NULL
[[4]]
function (..., na.rm = FALSE) .Primitive("sum")
This will work if the function encloses its body with brace brackets (which nearly all functions do). It gives a list whose names are the argument names and whose values are the defaults:
sample.function <- function(a, b, named="test") {} # test function
L <- as.list(formals(sample.function))); L
## $a
##
## $b
##
## $named
## [1] "test"
This is slightly longer but works even for functions whose bodies are not surrounded by brace brackets:
head(as.list(args(sample.function)), -1)
# same output
head(as.list(args(sin)), -1) # sin has no {}
## $x
Returning to the first example, to examine the default values for missing:
sapply(L, identical, formals(function(x) {})$x)
## a b named
## TRUE TRUE FALSE
Revised
I am trying to create a simple reference class in R. Here is my code (R beginner):
MyClass <- setRefClass("MyClass",
fields = list(a = "numeric",
b = "numeric"),
methods = list(
initialize <- function(){
print("Initializing")
a <<- 1
b <<- 2
},
printValues <- function(){
print(a)
print(b)
}
)
)
a <- MyClass$new()
a$printValues()
This produces the following error for the last line, a$printValues:
Error in envRefInferField(x, what, getClass(class(x)), selfEnv) :
"printValues" is not a valid field or method name for reference class “MyClass”
Also, the initializer method is not being called ?
Can someone point me to where the issue lies here ? Many thanks in advance.
The methods argument to setRefClass needs to be a named list. The problem is you are using the assign operator <- instead of = when defining your list. See the difference between
list(a = 1, b = 2)
# $a
# [1] 1
#
# $b
# [1] 2
which returns a named list and
list(a <- 1, b <- 2)
# [[1]]
# [1] 1
#
# [[2]]
# [1] 2
which creates a and b in your environment and returns an unnamed list.
So when passing your methods, you need to use =:
methods = list(initialize = function [...],
printValues = function [...]