4

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?

Roland
  • 127,288
  • 10
  • 191
  • 288
PatrickT
  • 10,037
  • 9
  • 76
  • 111

1 Answers1

9

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.

Community
  • 1
  • 1
PatrickT
  • 10,037
  • 9
  • 76
  • 111