4

I have a function f <- function(x){x}. I want to insert the line x <- 2*x into f such that it ends up as

function(x){
    x <- 2*x
    x
}

I understand that I should be using body(), but I have thus far only figured out how to replace the entire body, which is not practical for my true purposes.

MrFlick
  • 195,160
  • 17
  • 277
  • 295
BioBroo
  • 613
  • 1
  • 7
  • 21
  • @ZheyuanLi Li It is not clear to me how I could use edit to do this... – BioBroo Aug 03 '16 at 00:52
  • @ZheyuanLi - since there is a `\`body<-\`` function, there's probably a way to do this programmatically. – thelatemail Aug 03 '16 at 00:54
  • @ZheyuanLi, I don't think commands like `fix` or `edit` will work for me because I'm trying to insert lines into _any_ function, rather than one specific function. – BioBroo Aug 03 '16 at 01:03
  • I did it here: https://github.com/data-steve/useResearch/blob/master/R/pingr.R – Tyler Rinker Aug 03 '16 at 01:05
  • @TylerRinker, so the trick is that I can modify lines in the body like a list. That helped! I'm not sure what is the proper way to deal with the question now. It was answered in a comment... – BioBroo Aug 03 '16 at 01:23

4 Answers4

5

Here's another way (using magrittr to streamline things)

f <- function(x){x}
f(2)
# [1] 2
# library(magrittr)
body(f) <- body(f) %>% as.list %>% append(quote(x<-2*x), 1) %>% as.call
f(2)
# [1] 4

Or even simply

body(f) %<>% as.list %>% append(quote(x<-2*x), 1) %>% as.call %>% as.expression

but I feel like I may be missing an en even simpler way

You could write a more traditional function without magrittr as well...

funins <- function(f, expr = expression(x<-2*x), after=1) {
    body(f)<-as.call(append(as.list(body(f)), expr, after=after))
    f
}

Which you can use to insert any expression

f <- function(x){x}
g <- funins(f, expression(print("hi"), x<-3*x))
f(2)
# [1] 2
g(2)
# [1] "hi"
# [1] 6
MrFlick
  • 195,160
  • 17
  • 277
  • 295
3

Here is my comment as an answer. THe if else protects in case it's a single line fuunction:

f <- function(x){x}


fun <- f

bod <- body(fun)
if (trimws(as.character(bod[[1]])) == "{"){
    body(fun)[[2]] <- quote(x <- 2*x)
    if (length(bod) > 1) body(fun)[3:(1+length(bod))] <- bod[-1]
} else {
    body(fun)[[1]] <- as.name('{')
    body(fun)[[2]] <- quote(x <- 2*x)
    body(fun)[[3]] <- bod
}
fun
fun(3)
Tyler Rinker
  • 108,132
  • 65
  • 322
  • 519
1
f <- function(x) { x }
bdy <- deparse(body(f))
body(f) <- as.expression(parse(text=paste0(c(bdy[1], "x <- 2*x", bdy[2:3]), collapse="\n")))

Knowing what you're really trying to do wld be gd. Monkey patching functions like this sounds like a bad idea.

hrbrmstr
  • 77,368
  • 11
  • 139
  • 205
  • The small task I'm struggling with is to add in a specific line of code, as the first line of code, into an arbitrary function. The reason I'm doing this is that there is no function parMapply for parallel processing, so I've had to alter user-defined functions to make what I have fit parApply, which is cumbersome and promotes bugs. So I'm writing a little wrapper for parApply that requires me to solve the problem I posed. – BioBroo Aug 03 '16 at 01:11
  • 1
    I don't suppose `parallel::clusterMap` does what you want from `parMapply`, does it? – Jonathan Carroll Aug 03 '16 at 01:28
  • As far as I understand, `clusterMap` only works on Linux and Mac operating systems. I'm using Windows. – BioBroo Aug 03 '16 at 01:49
  • 1
    I *think* that's only true for the `mc*` functions which rely on a `fork` - the `par*` and `cluster*` functions derive from `snow` and work on Windows (IIRC). – Jonathan Carroll Aug 03 '16 at 01:59
  • @JonathanCarroll As it turns out, my understanding is not that far. After doing some more reading, it looks like cluster* functions do not use forking. I didn't find that written explicitly, but it appears so. – BioBroo Aug 03 '16 at 12:16
  • Hopefully that solves your problem then. I won't add it as an answer as it doesn't answer your original question, but feel free to put an upvote on the comment. – Jonathan Carroll Aug 03 '16 at 12:18
0

Building on hrbrmstr's answer, here is an easy-to-use wrapper, without using external packages.

The function used to patch other functions.

patch.function <- function(fun, patch, position = 1) {
    # Deparse the body.
    fun.body <- deparse(body(fun))

    # Append the patch to function body where intended.
    patched.fun.body <- paste0(
        c(fun.body[1:position], patch, fun.body[(position + 1):length(fun.body)]),
        collapse = "\n"
    )

    # Parse and treat as an expression.
    expr <- as.expression(parse(text = patched.fun.body))

    return(expr)
}

Let's say we want to patch the following dummy function.

dummy.fun <- function() {
    print("- a line A of the function body.")
    print("- a line B of the function body.")
    print("- a line C of the function body.")
    print("- a line D of the function body.")
    print("- a line E of the function body.")
    print("- a line F of the function body.")
    print("- a line G of the function body.")
    print("- a line H of the function body.")
}

We can use it as:

body(dummy.fun) <- patch.function(dummy.fun, "print('Before the A line of the body.')")
dummy.fun()

# Output:
# [1] "Before the A line of the body."
# [1] "- a line A of the function body."
# [1] "- a line B of the function body."
# [1] "- a line C of the function body."
# [1] "- a line D of the function body."
# [1] "- a line E of the function body."
# [1] "- a line F of the function body."
# [1] "- a line G of the function body."
# [1] "- a line H of the function body."


body(dummy.fun) <- patch.function(dummy.fun, "print('Before the D line of the body.')", position = 5)
dummy.fun()

# Output:
# [1] "Before the A line of the body."
# [1] "- a line A of the function body."
# [1] "- a line B of the function body."
# [1] "- a line C of the function body."
# [1] "Before the D line of the body."
# [1] "- a line D of the function body."
# [1] "- a line E of the function body."
# [1] "- a line F of the function body."
# [1] "- a line G of the function body."
# [1] "- a line H of the function body."

Mihai
  • 2,807
  • 4
  • 28
  • 53