Search code examples
rmetaprogrammingrlang

base R substitute names of the arguments to function call


The ultimate goal in the question is to construct the following unevaluated call using 's computing on the language, where list, a_name and 50L are provided from parameters.

list(a_name = 50L)

Which internally looks like

str(quote(list(a_name = 50L)))
# language list(a_name = 50L)

str(as.list(quote(list(a_name = 50L))))
#List of 2
# $       : symbol list
# $ a_name: int 50

I will put my variables in a list so the further code will be cleaner.

params = list(my_fun = as.name("list"), my_name = "a_name", my_value = 50L)

# What I tried so far?

# 1. The first thing that one would try

substitute(my_fun(my_name = my_value),
           params)
#list(my_name = 50L) ## `my_name` was not substituted!

# 2. Workaround get the same output, but only after `setNames` call evaluation, so doesn't really answer the question about constructing specific call

substitute(setNames(my_fun(my_value), my_name), ## alternatively could be `structure(..., names=my_name)`
           params)
#setNames(list(50L), "a_name")

# 3. Another workaround, not really computing on the language but parsing, and integer L suffix is gone!

with(expr = parse(text=paste0(my_fun, "(", my_name, " = ", my_value, ")"))[[1L]],
     data = params)
#list(a_name = 50)

# 4. Working example using rlang

with(expr = rlang::call2(my_fun, !!my_name := my_value),
     data = params)
#list(a_name = 50L)

Is there any way in base to construct required call? Basically to get exactly same output as rlang way but using base .

Note that this Question is not a duplicate of this which was strictly asking for rlang solution. This Question asks for a way to achieve it using base . If there is no way to achieve it, I would like to know that as well. Thank you.


Solution

  • Assuming that the question is how to produce a call object whose function is the first argument of params, whose single argument name is the value of the second component of params and whose argument value is the third component of params then c2 is that call object. We verify that the cakl object produced is identical to the rlang call object shown in the question.

    c2 <- as.call(setNames(params, c("", "", params2$my_name))[-2])
    
    # verify that c2 equals the output of the rlang example in question
    c1 <- with(expr = rlang::call2(my_fun, !!my_name := my_value), data = params)
    
    identical(c1, c2)
    ## [1] TRUE
    

    This generalizes so that if the call has more arguments so that params has length n+2 and the second component of params is a vector of names whose length is n then it still works.

    If you have control over the input I think it would make more sense to define the input list as having one component for the function name and one component for each argument with the names of the components being the argument names (as opposed to a separate argument to hold the names). In that case we could simply write the following. Actually what the above does is to transform params into that form and then use as.call so we might as well provide that form from the start if possible.

    as.call(params2)  # params2[[1]] is func name, params2[-1] is named args
    

    Note

    To execute the call just use:

    eval(c2)
    

    or we could use do.call:

    do.call(as.character(params[[1]]), setNames(tail(params, -2), params[[2]]))