I am trying to pass an expression that should be evaluated with respect to the data frame, essentially mirroring the subset()
. The issue is, I need to be able to have it inside other functions.
This essentially means preserving the expression and not evaluating it until the time is right (i.e., the subset-like function is called).
The way I was able to finally succeed is by passing env
with a default value sys.frame(sys.nframe())
which should evaluate to the function's frame. The disadvantage is that this needs to be parameter of every outer function to preserve the nesting property:
f1 = function(x, expr, env=sys.frame(sys.nframe())){
expr = substitute(expr, env)
eval(expr, x)
}
f2 = function(x, expr, env=sys.frame(sys.nframe())){
f1(x, expr, env)
}
f3 = function(x, expr, env=sys.frame(sys.nframe())){
f2(x, expr, env)
}
f4 = function(x, expr){
f2(x, expr) # no env passing
}
m = head(mtcars, 2) # less typing
f1(m, mpg > 20) # TRUE TRUE -- expected
f2(m, mpg > 20) # TRUE TRUE -- yaay
f3(m, mpg > 20) # TRUE TRUE -- yaay
f4(m, mpg > 20) # expr -- naay :(
Can I rewrite f1
so it would automatically unwind the call-stack and find the correct env
to substitute the passed expression?
You could build the calls with do.call()
. For example
f1 = function(x, expr){
expr = substitute(expr)
eval(expr, x)
}
f2 = function(x, expr){
do.call("f1", list(x, substitute(expr)))
}
f3 = function(x, expr){
do.call("f2", list(x, substitute(expr)))
}
f1(m, mpg > 20)
# [1] TRUE TRUE
f2(m, mpg > 20)
# [1] TRUE TRUE
f3(m, mpg > 20)
# [1] TRUE TRUE
But in general I would strongly recommend against this. Normally you would have a user facing function that will deal with the non-standard evaulation and then you would just pass along a quoted language string or, in the cause of the rlang/tidyverse world, a quosure that you can evaluate later. Relying on nested non-standard evaluation calls usually just leads to more headaches when trying to programmatically use the functions.