Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return a function's code

Tags:

parsing

r

shiny

This would seem to be an elementary question, but I can't seem to find an answer on stackoverflow.

How can I obtain the following effect:

f <- function(x = 1){x^2}
miracle(f)
[1]  "x^2"

The context is a shiny app (package by RStudio) in which I have a textInput() function to which I supply an initial value x^2. While this works:

textInput(inputId = "inFun", label = h4("Enter a function:"), value = "x^2")

this doesn't:

textInput(inputId = "inFun", label = h4("Enter a function:"), value = f)

It appears that I need something like "x^2" on the rhs of value.

Below is a representative sample of several variations I have tried:

eval(parse(text = f))
Error in as.character(x) : 
  cannot coerce type 'closure' to vector of type 'character'

f(x = "x")
Error in x^2 : non-numeric argument to binary operator

`f`
function(x){x^2}

f(x = `x`)
Error in f(x = x) : object 'x' not found

Is there a built-in function for this?

like image 802
PatrickT Avatar asked Jan 12 '23 11:01

PatrickT


1 Answers

I'd like to answer my own question, based on Roman Luštrik's comment, to invite suggestions for improvements rather than raising my meagre tally of "points".

Roman suggested the function body(), which I had never heard of. Here is what body() does to f:

f <- function(x = 1){x^2}

> body(f)
{
    x^2
}

The curly brackets were unwanted, so I searched a little further. I managed to get rid of the curly brackets with this:

> gsub(' {2,}','',deparse(body(f))[2])
[1] "x^2"

The above, therefore, answers my own question. But is there a more elegant and shorter way?

Following Roman's suggestion to use body(), I came across this outstanding answer by joran, hadley, and several others, which provided me with a template:

How to create an R function programmatically?

There it explains how to create a function programmatically from an argument list, a body and an environment. I therefore decided to construct my function f with these 3 primitives and to call the body from inside shiny's textInput.

So I put this in my global.R file (the small-cap g is shorthand for global)

# Convenience function
make.function <- function(args = alist(a = 1, b = 2), body = quote(a + b), 
                          env = parent.frame()) {
  subs <- list(args = as.pairlist(args), body = body)
  eval(substitute(`function`(args, body), subs), env)
}
gArg <- alist(a = 1, b = 2)
gBody <- quote(a + b)
gFun <- make.function(gArg, gBody)

Then in my server.R file, I have:

textInput(inputId = "inFun", label = h4("1. Enter a function:"), 
          value = deparse(body(gFun)))

And it works!

I was planning to write value = gBody or something to that effect, but my first success came with deparse(body(gFun)), so that's what I'm using now.

The use of make.function to produce a 'static' function in global.R is of course overkill, but I'm using make.function elsewhere inside server.R to process the user-supplied arguments and body to create new functions and plot them, so it's a very useful function to have.

Thanks Roman: if you write your own answer I'll accept yours.

like image 114
PatrickT Avatar answered Jan 19 '23 11:01

PatrickT