Problem: Can the contents of 2 functions be merged into a single function using R?
A function consists of 3 things:
- formal arguments
- body
- environment
The body of a function can be
- fetched using
body()
- set using
body<-()
With these functions for manipulating bodies, can the contents of two functions be merged into a single function using R?
And can the resulting function’s body look neat?
Edit:
- 2018-03-18 The use of
[[-1]]
to remove the call to{
from the function bodies wasn’t really a general solution. Besides being hard to interpret, it failed when the function body was more complex than a single command. Switched to usingrlang::call_args()
instead. Thanks to hadley for pointing out the problem here. - 2018-03-18 When constructing a call using
as.call()
, the first item in the list argument is a function. This may be specified as either the actual function, or the name/symbol for that function. It turns out that using the symbol for{
rather thanget('{')
results in a tidier representation. Thanks to hadley for pointing out the problem here.
My Functions
The goal is to merge the following 2 functions, s()
and t()
into a single function called newf()
s <- function() {print("hello")}
t <- function() {
print("there")
print("#rstats")
}
The expected body of the combined function after the above 2 functions are merged:
newf <- function()
{
print("hello")
print("there")
print("#rstats")
}
Body hacking 1: Basic way to set a new body
With the body<-()
function, you can insert the body/content of a function after it’s been created.
newf <- function() {}
body(newf) <- quote(print("hello"))
newf <- function ()
print("hello")
Body hacking 2: Merge the 2 function bodies together
Merge the 2 bodies into a single call
to {
.
newf <- function() {}
body(newf) <- as.call(list(get('{'), body(s), body(t)))
newf <- function ()
.Primitive("{")({
print("hello")
}, {
print("there")
print("#rstats")
})
The newf()
function looks a bit ugly, but it works!
This looks much better if I build the as.call
with the function defined by the symbol for {
rather than
pulling in the actual function.
newf <- function() {}
body(newf) <- as.call(list(as.name('{'), body(s), body(t)))
newf <- function ()
{
{
print("hello")
}
{
print("there")
print("#rstats")
}
}
Body hacking 3 - use rlang
!
Use rlang::call2
newf <- function() {}
body(newf) <- rlang::call2('{', body(s), body(t))
newf <- function ()
{
{
print("hello")
}
{
print("there")
print("#rstats")
}
}
This construction is a little simpler to read.
Body hacking 4: use rlang::call_args
Strip the outer {
call from each body before merging into a new call. Do this
using rlang::call_args()
to just retain the arguments to the outer {
.
newf <- function() {}
body(newf) <- as.call(c(
list(as.name('{')),
rlang::call_args(body(s)),
rlang::call_args(body(t))
))
newf <- function ()
{
print("hello")
print("there")
print("#rstats")
}
Success!
Conclusion
- Yes, you can edit the content of functions.
- Yes, you can merge the contents of 2 functions.