Search code examples
rsubstitution

Passing unevaluated expression to substitute + eval combo


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?


Solution

  • 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.