Search code examples
rrlang

rlang: using function argument in default assignment function


Using rlang, I'd like to have a function that works both when directly called and when passed arguments as part of constructing another function argument by default, e.g.:

refdf = data.frame(x=1:100, y=runif(100,-1,1))

test.helper <- function(z, df) {
  qz <- enquo(z)
  range(eval_tidy(qz, df))
}
test.helper(y, refdf) # works

test.main <- function(z, df, def = test.helper(z, df)) {
  print(def)
}
test.main(y, refdf)
# doesn't work:  Error in eval_tidy(qz, df) : object 'y' not found 

If instead, I do

refdf = data.frame(x=1:100, y=runif(100,-1,1))

test.helper <- function(z, df) {
  qz <- as_quosure(z)
  range(eval_tidy(qz, df))
}
test.helper(y, refdf)
# doesn't work: Error in is_quosure(x) : object 'y' not found

test.main <- function(z, df, def = test.helper(enquo(z), df)) {
  print(def)
}
test.main(y, refdf)
# now works 

I feel like I'm missing something about what gets quoted when; is there an alternative syntax I can use to make both work? I know I could define a separate test.helper_quo or some such, but I'd really like to use the test.helper in the signature (as an extra hint to users about what functions are available).


Solution

  • This should work

    library(rlang)
    
    test.helper <- function(z, df) {
      qz <- enquo(z)
      range(eval_tidy(qz, df))
    }
    test.helper(y, refdf) # works
    
    test.main <- function(z, df, def = test.helper(!!enquo(z), df)) {
      print(def)
    }
    test.main(y, refdf) # works
    
    # or with rlang >= 0.4.0
    test.main <- function(z, df, def = test.helper({{z}}, df)) {
      print(def)
    }
    test.main(y, refdf) # works
    

    Note that in def, we need to capture the quosure passed as z and then expand that into the the call the test.helper so the it's own enquo will be able to see the original symbol.