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
Related
This isn't really a httr2 specific problem though it is easy to illustrate this way. If I have a param that is being to a function that I want to lapply on and that function and the componets of ... need to named, how do I.... do that? I want the function to take the argument name (i.e. param below) use that are the dots name with the values of the vector being lapply over.
library(httr2)
req <- request("http://example.com")
param <- c("foo", "bar")
## hard code param (this is what i am hoping to generate)
lapply(param, \(x) req_url_query(req, param = x))
#> [[1]]
#> <httr2_request>
#> GET http://example.com?param=foo
#> Body: empty
#>
#> [[2]]
#> <httr2_request>
#> GET http://example.com?param=bar
#> Body: empty
## want the ... to dynamically named
my_func <- function(req, ...) {
lapply(..., \(x) req_url_query(req, ...))
}
other_param <- c("x", "y")
my_func(req, other_param)
#> Error in `modify_list()`:
#> ! All components of ... must be named
This looks like it works (edited from comment below):
my_func <- function(req, ...) {
dots <- list(...)
dots_chr <- unlist(dots)
function_string <- paste0("lapply(dots_chr, \\(x) req_url_query(req, ", names(dots), "= x))")
eval(parse(text = function_string))
}
which returns:
$pizza1
<httr2_request>
GET http://example.com?pizza=is_great
Body: empty
$pizza2
<httr2_request>
GET http://example.com?pizza=is_healthy
Body: empty
Here's a version that uses the base do.call function to build the call to the function with the parameter name you want
my_func <- function(req, ...) {
mc <- as.list(match.call()[-(1:2)])
stopifnot(length(mc)==1)
pname <- deparse1(mc[[1]])
if(!is.null(names(mc))) {
pname[names(mc)!=""] <- names(mc)[[names(mc)!=""]]
}
lapply(list(...)[[1]], \(x) do.call("req_url_query", setNames(list(quote(req), x), c("", pname))))
}
It handles all cases like
other_param <- c("x", "y")
my_func(req, other_param)
my_func(req, other_param=1:2)
my_func(req, other_param=other_param)
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 want to define (<- and access the name of the left hand side argument :
*<- functions use internally an intermediate '*tmp*' variable. Is it still possible to get the name of x ?
`(<-` <- function(x,value){
print(deparse(substitute(value)))
print(deparse(substitute(x)))
print(match.call())
value
}
foo <- 0
(foo) <- 3
# [1] "3"
# [1] "*tmp*"
# `(<-`(x = `*tmp*`, value = 3)# [1] "3"
I want to get "foo" from inside the function.
I tried to hack it by using tracemem, i.e. calling sapply(ls(envir = parent.frame()),tracemem) and tracemem(x) inside of the functions but the address of foo, *temp* and x are all different.
I hacked it, though I didn't understand everything that I did.
I noticed pryr::address was giving a different kind of results than tracemem and tried it (I had to dig into the code to use pryr:::address2 because pryr::address doesn't have an environment argument).
Then I noticed that mixing the results from tracemem on x and pryr:::address2 on the rest of the objects there was a match (after basic reformatting) :
`(<-` <- function(x,value){
pf <- parent.frame()
all_addresses <- sapply(ls(pf), pryr:::address2, pf)
all_addresses <- all_addresses[names(all_addresses) != "*tmp*"]
all_addresses_short <- gsub("(^|<)[0x]*(.*?)(>|$)","\\2",all_addresses)
x_address <- tracemem(x)
x_address_short <- tolower(gsub("(^|<)[0x]*(.*?)(>|$)","\\2",x_address))
ind <- match(x_address_short, all_addresses_short)
x_name <- names(all_addresses)[ind]
message("all_addresses, using pryr::address2")
print(all_addresses)
print(all_addresses_short)
message("x_address, using tracemem")
print(x_address)
print(x_address_short)
message("x_name, matching substrings")
print(x_name)
value
}
The regex used in gsub calls tries to account for the address formats we get with different systems, I'm not 100% sure that it's general.
output:
foo <- 1
bar <- 2
(foo) <- foo
# all_addresses, using pryr::address2
# (<- bar foo
# "0x1433df50" "0x14937678" "0x14937708"
# (<- bar foo
# "1433df50" "14937678" "14937708"
# x_address, using tracemem
# [1] "<0000000014937708>"
# [1] "14937708"
# x_name, matching substrings
# [1] "foo"
It breaks if x is not a variable name, for example:
foo <- iris
(foo$species) <- 3
We could assume that if the address isn't found x is a list item, and then lookup its address among the addresses of the items of all the lists we have in the parent.frame (recursively), but I think that's enough ugly hacks for today.
1) If you are willing to change it so that the call is:
fooify[foo] <- 99
then we can do it like this where foo need not exist beforehand:
fooify <- structure(NA, class = "fooify")
"[<-.fooify" <- function(x, var, value) {
print(deparse(substitute(var)))
eval.parent(substitute(var <- value))
x
}
# test
if (exists("foo")) rm(foo)
fooify[foo] <- 99
## [1] "foo" <-- this comes from the print statement
foo
## [1] 99
2) := If using := is ok then:
`:=` <- function(lhs, rhs) {
print(deparse(substitute(lhs)))
eval.parent(substitute(lhs <- rhs))
}
# test
if (exists("foo")) rm(foo)
foo := 99
## [1] foo <-- this comes from print statement
foo
## [1] 99
I would like use a function that uses the standard deparse(substitute(x)) trick within lapply. Unfortunately I just get the argument of the loop back. Here's my completely useless reproducible example:
# some test data
a <- 5
b <- 6
li <- list(a1=a,b2=b)
# my test function
tf <- function(obj){
nm <- deparse(substitute(obj))
res <- list(myName=nm)
res
}
tf(a)
#returns
$myName
[1] "a"
which is fine. If I use lapply I either get [[1L]] or the x argument of an anonymous function.
lapply(li,function(x) tf(x))
# returns
$a1
$a1$myName
[1] "x"
$b2
$b2$myName
[1] "x"
Is there any way to obtain the following?
$a1
$a1$myName
[1] "a1"
$b2
$b2$myName
[1] "b1"
If there's anything more general on deparse(substitute(x)) and lapply I'd also eager to know.
EDIT:
The problem as opposed to using an anonymous function that accepts multiple arguments and can thus use the name of the object and the object itself does not work because, the tf function will only accept one argument. So this does not work here...
A possible solution :
lapply(li, function(x) {
call1 <- sys.call(1)
call1[[1]] <- as.name("names")
call1 <- call1[1:2]
nm <- eval(call1)
y <- deparse(substitute(x))
y <- gsub("\\D", "", y)
y <- as.numeric(y)
list(myname=nm[y])
})
I'm trying to get the names of arguments in the global environment within a function. I know I can use substitute to get the name of named arguments, but I would like to be able to do the same thing with ... arguments. I kinda got it to work for the first element of ... but can't figure out how to do it for the rest of the elements. Any idea how to get this working as intended.
foo <- function(a,...)
{
print(substitute(a))
print(eval(enquote(substitute(...))))
print(sapply(list(...),function(x) eval(enquote(substitute(x)),env=.GlobalEnv)))
}
x <- 1
y <- 2
z <- 3
foo(x,y,z)
x
y
[[1]]
X[[1L]]
[[2]]
X[[2L]]
The canonical idiom here is deparse(substitute(foo)), but the ... needs slightly different processing. Here is a modification that does what you want:
foo <- function(a, ...) {
arg <- deparse(substitute(a))
dots <- substitute(list(...))[-1]
c(arg, sapply(dots, deparse))
}
x <- 1
y <- 2
z <- 3
> foo(x,y,z)
[1] "x" "y" "z"
I would go with
foo <- function(a, ...) {
print( n <- sapply(as.list(substitute(list(...)))[-1L], deparse) )
n
}
Then
foo(x,y,z)
# [1] "y" "z"
Related question was previously on StackOverflow:
How to use R's ellipsis feature when writing your own function? Worth reading.
Second solution, using match.call
foo <- function(a, ...) {
sapply(match.call(expand.dots=TRUE)[-1], deparse)
}