Reputation: 17843
How can a function inspect and modify the arguments of a call that it received as argument?
Application: A user feeds a call to function a
as an argument to function b
, but they forget to specify one of the required arguments of a
. How can function b
detect the problem and fix it?
In this minimal example, function a
requires two arguments:
a <- function(arg1, arg2) {
return(arg1 + arg2)
}
Function b
accepts a call and an argument. The commented lines indicate what I need to do:
b <- function(CALL, arg3) {
# 1. check if `arg2` is missing from CALL
# 2. if `arg2` is missing, plug `arg3` in its place
# 3. return evaluated call
CALL
}
Expected behavior:
b(CALL = a(arg1 = 1, arg2 = 2), arg3 = 3)
> 3
b(CALL = a(arg1 = 1), arg3 = 3)
> 4
The second call currently fails because the user forgot to specify the required arg2
argument. How can function b
fix this mistake automatically?
Can I exploit lazy evaluation to modify the call to a
before it is evaluated? I looked into rlang::modify_call
but couldn't figure it out.
Upvotes: 4
Views: 91
Reputation: 57696
I don't see why fancy language manipulation is needed. The problem is what to do when a
, which requires 2 arguments, is supplied only 1. Wrapping it with b
, which has a default value for the 2nd argument, solves this.
b <- function(arg1, arg2=42)
{
a(arg1, arg2)
}
b(1)
# [1] 43
b(1, 2)
# [1] 3
Upvotes: 0
Reputation: 206566
Here's a method that would work
b <- function(CALL, arg3) {
scall <- substitute(CALL)
stopifnot(is.call(scall)) #check that it's a call
lcall <- as.list(scall)
if (!"arg2" %in% names(lcall)) {
lcall <- c(lcall, list(arg2 = arg3))
}
eval.parent(as.call(lcall))
}
We use substitute()
to grab the unevaluated version the CALL parameter. We convert it to a list so we can modify it. Then we append to the list another list with the parameter name/value we want. Finally, we turn the list back into a call and then evaluate that call in the environment of the caller rather than in the function body itself.
If you wanted to use rlang::modify_call
and other rlang
functions you could use
b <- function(CALL, arg3) {
scall <- rlang::enquo(CALL)
stopifnot(rlang::quo_is_call(scall))
if (!"arg2" %in% names(rlang::quo_get_expr(scall))) {
scall <- rlang::call_modify(scall, arg2=arg3)
}
rlang::eval_tidy(scall, env = rlang::caller_env())
}
Upvotes: 2