This blog has relocated to https://coolbutuseless.github.ioand associated packages are now hosted at https://github.com/coolbutuseless.

29 April 2018

mikefc

Problem: Can the contents of 2 functions be merged into a single function using R?

A function consists of 3 things:

  1. formal arguments
  2. body
  3. 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 using rlang::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 than get('{') 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.