1

I'm trying to instrument some functions in R, by replacing them with my own versions which record some information and then call the original versions. My trouble is, how do I construct the call such that it exactly replicates the original arguments.

First, we'll set up an environment

   env <- new.env()

One way I could do this is by getting the call and modifying it. However, I don't know how to do this with a primitive function.

## Option 1: get the call and modify it
env$`[` <- function(x, i, j, ...) {
    my.call <- match.call()
    ## This isn't allowed.
    my.call[[1]] <- as.name(.Primitive("["))

    record.some.things.about(i, j)

    eval(my.call, envir = parent.frame())
}

Alternatively, I could just get the arguments and use do.call. However, I haven't worked out how to extract the arguments correctly.

## Option 2: do a call with the arguments
env$`[` <- function(x, i, j, ...) {
    ## This outputs a list, so if 'i' is missing, then 'j' ends up in the 'i' position.
    ## If I could get an alist here instead, I could keep 'j' in the right position.
    my.args <- match.call()[-1]

    record.some.things.about(i, j)

    do.call(
        .Primitive("["),
        my.args,
        envir = parent.frame()
    )
}

Then, if we've done things right, we can eval some expression which uses [ inside the enviroment we've constructed, and our instrumented function will be called instead:

## Should return data.frame(b = c(4, 5, 6))
evalq(
    data.frame(a = c(1, 2, 3), b = c(4, 5, 6))[, "b"],
    envir = env
)

Can anyone help/advise?

GlennS
  • 5,251
  • 6
  • 28
  • 31
  • 1
    Possibly relevant: http://stackoverflow.com/questions/23124096/r-decorator-to-change-both-input-and-output – nrussell Mar 02 '16 at 12:58
  • What I tend to do to fix some defaults is something along the following lines: Say I want `table` to always include missing values, then in the beginning of my script, I use `table <- function(x, ...) base:::table(x, ..., useNA="ifany")`. Whatever you want to record about `i` and `j`, you could probably construct something along similar lines. – coffeinjunky Mar 02 '16 at 13:14
  • 1
    Can you explain more plainly what you want to achieve? If I understand correctly, you either want to write a method for `[` or want to substitute `[` with your own function. However, your function name is confusing me. – Roland Mar 02 '16 at 13:30
  • Now you have muddled this even more. You assign a function to an environment. Why? – Roland Mar 02 '16 at 13:36
  • I would really advise you to follow a different path. Create an S3 class and define a method for `[`. – Roland Mar 02 '16 at 13:40
  • Roland: I don't understand why S3 would help? This doesn't have anything to do with object orientation or method dispatch as far as I can tell. I want to intercept *all uses* of [, not just those for some particular object. – GlennS Mar 02 '16 at 13:43

2 Answers2

4

Use trace:

trace(`[.data.frame`, quote({print("hello!")}), at = 1) 
iris[1,]
#Tracing `[.data.frame`(iris, 1, ) step 1 
#[1] "hello!"
#  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#1          5.1         3.5          1.4         0.2  setosa
Roland
  • 127,288
  • 10
  • 191
  • 288
0

Can't you just capture everything for original function args with triple dots arg, that gets passed on to the original function?

sum <- function(..., na.rm = TRUE) {
  print("my sum") # here is where you can "record some info"
  base::sum(..., na.rm = na.rm) # then call original function w/ ...
}

base::sum(5,5, NA)
##[1] NA

# your function
sum(5,5, NA)
##[1] "my sum"
##[1] 10