return function argument as string

Hi all! I'm writing a function that takes some data x and a function aggr_func and I would like to return a list with the aggregated data & the function passed in aggr_func. rlang::expr_text() and rlang::expr(), but while this works outside of the function context it does not work with an argument! Here's a reprex:

library(rlang)
#> Warning: package 'rlang' was built under R version 3.5.3

# expected behavior
expr_text(expr(mean))
#> [1] "mean"
test_func <- function(x, aggr_func) {
    list(
        x = aggr_func(x),
        aggr_func = expr_text(expr(aggr_func))
    )
}

# returns name of argument ("aggr_func") instead of "mean"
test_func(1:10, mean)
#> $x
#> [1] 5.5
#> 
#> $aggr_func
#> [1] "aggr_func"

Created on 2019-05-16 by the reprex package (v0.2.1)

Using rlang::!! doesn't help entirely--it returns what's getting applied to the data and not the text from the argument:

library(rlang)
#> Warning: package 'rlang' was built under R version 3.5.3
test_func <- function(x, aggr_func) {
    list(
        x = aggr_func(x),
        aggr_func = expr_text(expr(!!aggr_func))
    )
}
test_func(1:10, mean)
#> $x
#> [1] 5.5
#> 
#> $aggr_func
#> [1] "function (x, ...) \nUseMethod(\"mean\")"

Created on 2019-05-16 by the reprex package (v0.2.1)

2 Likes

Inside the function, use enexpr() instead of expr(). Whereas expr() captures your code, enexpr() captures the code of the user of your function.

Also I would use rlang::as_label() instead of expr_text() since the latter is for multi-lines strings. The former always returns a single-line string, so is appropriate when you need to turn something into a descriptive label that can be used, for example, for plot axes.

5 Likes

Solved, c/o @hadley:

library(rlang)
#> Warning: package 'rlang' was built under R version 3.5.3
test_func <- function(x, aggr_func) {
    func <- enexpr(aggr_func)
    list(
        x = aggr_func(x),
        aggr_func = func
    )
}
test_func(1:10, mean)
#> $x
#> [1] 5.5
#> 
#> $aggr_func
#> mean

Created on 2019-05-16 by the reprex package (v0.2.1)

thanks! hadley beat you to it on twitter but the other advice is also helpful! new to rlang and it comes up when i least expect it...

I think the response by @lionel better answers your OP, as his suggestion returns the bare function name as a string in the list.

Something like this:

test_func <- function(x, aggr_func) {
  
  capture_exp <- enexpr(aggr_func)
  
  list(
    x = aggr_func(x),
    aggr_func = as_label(capture_exp)
  )
}

test_func(1:10, mean)

thanks for pointing out the difference! now that i see i could do either, i'm not sure which is preferred...presumably there are memory savings to just storing the string? i don't know that i would ever use the function by pulling it from this data but it might be worthwhile to have...

Surprisingly, storing the string seems to be more costly than storing the expression. However, in practice the difference is probably trivial (112 B vs 56 B).

pryr::object_size(expr(mean)) #56 B
pryr::object_size("mean") #112 B

You might also ask yourself what you would want this to return:

test_fun(function(x) sd(x) / mean(x))

Sometimes generalising the problem can make it easy to see what you really want.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.