Home > Back-end >  How do I turn a vector of character strings (each giving commands) into a function in R?
How do I turn a vector of character strings (each giving commands) into a function in R?

Time:05-11

I am trying to build a function that creates and outputs a new function (which I will call the output-function for convenience) from its inputs. I am able to generate the lines of code I want for the output-function as character strings and I have put this into a character vector that goes through the commands I want in the body of the function. However, I'm having trouble turning this into a function to give as the output.

I have observed that I can extract the body of a function using the body command and I can turn this into a character vector. For example, here is what you get when you apply this to the matrix function:

body(matrix)

{
    if (is.object(data) || !is.atomic(data)) 
        data <- as.vector(data)
    .Internal(matrix(data, nrow, ncol, byrow, dimnames, missing(nrow), 
        missing(ncol)))
}

COMMANDS <- as.character(body(matrix))
COMMANDS

[1] "{"                                                                                 
[2] "if (is.object(data) || !is.atomic(data)) data <- as.vector(data)"                  
[3] ".Internal(matrix(data, nrow, ncol, byrow, dimnames, missing(nrow), missing(ncol)))"

However, I don't know how to reverse this process --- i.e., if I have constructed a character vector containing my commands, how do I create a function with this set of commands as the body?

CodePudding user response:

A function can be created from a list using as.function. The list has to include a single unnamed element of type "language" that acts as the function's body, with any named items in the list being interpreted as the function's arguments.

To create the function body from a string, we can convert the string to a language object using str2lang.

This means that we can easily write a function that creates functions from strings like this:

make_func <- function(..., body) {
  as.function(c(..., str2lang(body)))
}

so, for example:

hello <- make_func(body = "print('hello world')")

hello()
#> [1] "hello world"

Or, if our function needs arguments:

add_one <- make_func(x = 1, body = "{ x <- x   1; return(x) }")

add_one(22)
#> [1] 23

As others have pointed out in the comments though, it's rarely necessary to go through the steps of converting to character, since we can use language objects directly.

For example, to recreate matrix, we could do:

my_matrix <- as.function(c(formals(matrix), body(matrix)))
my_matrix(1:9, ncol = 3)
#>      [,1] [,2] [,3]
#> [1,]    1    4    7
#> [2,]    2    5    8
#> [3,]    3    6    9

If, for some reason, you have a character vector representing several lines of code, you can simply paste them together with a newline character. Note that you will need to ensure that the result is wrapped in curly braces for it to be parsed by str2lang. So in your example, we can do:

my_mat <- make_func(data = NA, ncol = 1, nrow = 1, byrow = FALSE, 
                    dimnames = list(NULL),
                    body = paste0(paste(code_lines, collapse = '\n'), '}'))

my_mat(1:9, ncol = 3)
#>      [,1] [,2] [,3]
#> [1,]    1    4    7
#> [2,]    2    5    8
#> [3,]    3    6    9

CodePudding user response:

For completeness I am going to add another answer to draw together the required material into a single function generate_function to generate the output function. The function takes a set of arguments and a (character) vector of commands and generates the output-function that implements these commands.

generate_function <- function(..., commands, envir = parent.frame(), 
                              openbracket = TRUE, closebracket = FALSE) { 
  body <- paste(commands, collapse = '\n')
  if (!openbracket)  { body <- paste(c('{', body), collapse = '\n') }
  if (!closebracket) { body <- paste(c(body, '}'), collapse = '\n') }
  as.function(c(..., str2lang(body)), envir = envir) }

We can apply this function to recover the matrix function used as the example in the question. To do this, we list the same arguments as in the function and use its commands as inputs.

COMMANDS <- as.character(body(matrix))
FUNCTION <- generate_function(data = NA, nrow = 1, ncol = 1, byrow = FALSE, 
                              dimnames = list(NULL), commands = COMMANDS)

We can now confirm that the formals and body of the newly created function matches the matrix function. (The environments for the functions are different because the new function exists in the parent environment we are working in, just as we would want.)

identical(body(FUNCTION), body(matrix))
[1] TRUE
identical(formals(FUNCTION), formals(matrix))
[1] TRUE
  • Related