base R substitute names of the arguments to function call
Asked Answered
A

1

6

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.

Approver answered 8/3, 2020 at 12:53 Comment(2)
Does str2lang(paste("", "list", " (", "a_name", " = ", "50L", ")")) give you what you want?Merbromin
@Merbromin str2lang is just special version of parse, so not any better than example 3.Approver
T
7

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]]))
Transpose answered 8/3, 2020 at 13:9 Comment(3)
Thanks for elaborating. Agree, it does work by replacing the name of the element. I was hoping there is a more neat way in base R api, but I don't think there is. It is not easy to scale this approach for complex expression substitution. Will mark as accepted if no better answer will appear.Approver
I have modified the answer slightly so that if there are multiple arguments in the call and the second component of params is a vector of names it still works. Also reduced it to a single line. We show at the end that for the question's example it gives the identical answer.Transpose
Thank you. Complexity also comes when trying to substitute nested named elements my_fun(my_name1 = f(my_name2 = my_value)), but this was not part of the question obviously. I pinged you on github so you can know the use case.Approver

© 2022 - 2024 — McMap. All rights reserved.