0

I have below function. I cannot alter the function in any way except the first block of code in the function.
In this simple example I want to display apply some function on returning object.
The point is the name of variable returned by function may vary and I'm not able to guess it.
Obviously I also cannot wrap the f function into { x <- f(); myfun(x); x }.
The below .Last.value in my on.exit call represents the value to be returned by f function.

f <- function(param){
  # the only code I know - start
  on.exit(if("character" %in% class(.Last.value)) message(print(.Last.value)) else message(class(.Last.value)))
  # the only code I know - end

  # real processing of f()

  a <- "aaa"
  "somethiiiing"
  if(param==1L) return(a)

  b <- 5L
  "somethiiiing"
  if(param==2L) return(b)

  "somethiiiing"
  return(32)
}
f(1L)
# function
# [1] "aaa"
f(2L)
# aaa
# [1] 5
f(3L)
# integer
# [1] 32

Above code with .Last.value seems to be working with lag (so in fact not working) and also the .Last.value is probably not the way to go as I want to use the value few times like if(fun0(x)) fun1(x) else fun2(x), and because returned value might be a big object, copy it on the side is also bad approach.
Any way to use on.exit or any other function which can help me to run my function on the f function results without knowing result variable name?

jangorecki
  • 16,384
  • 4
  • 79
  • 160
  • Why can you not wrap your function to grab the output? Or why can you only change the first block of the function? These are very odd restrictions. I've looked into this before and it's not possible to get a functions return value from inside the function in a completely general way (I wish i could find the previous question but i think most of the discussion happened in comments which don't appear to be searchable). – MrFlick Mar 25 '15 at 17:47
  • @MrFlick, I'm injecting my code to the functions which processing I want to audit. Which are in external package, used by another functions in other packages. Yeah I know the question is demanding and might not be possible to answer. [This](http://stackoverflow.com/q/13603019/2490497) is related but the solution is to wrap into new function which will not help me. – jangorecki Mar 25 '15 at 17:51
  • I was just going to suggest `trace()` but then I realized that's what [the last question](http://stackoverflow.com/questions/24687062/how-to-access-a-return-value-of-a-function-that-is-being-traced) was trying to do. It may still be possible to assign your replacement function inside a package namespace. If that's what you are really trying to do, it would help if you had a more suitable reproducible example. – MrFlick Mar 25 '15 at 17:54
  • @MrFlick, the real case is to pass the `if("data.table" %in% class(?)) nrow(?) else NA_integer_` of the value returned by `[.data.table` in [that call](https://github.com/jangorecki/dtq/blob/bde12cc3caecae3b24a06d43038fd5739716db51/R/zzz.R#L18). – jangorecki Mar 25 '15 at 18:34

2 Answers2

1

In a similar way to how you are modifying the function, you could easily wrap it as well. Here's a reproducible example.

library(data.table)

append.log<-function(x) {
    cat(paste("value:",x,"\n"))
}

idx.dt <- data.table:::`[.data.table`
environment(idx.dt)<-asNamespace("data.table")

idx.wrap <- function(...) {
    x<-do.call(idx.dt, as.list(substitute(...())), envir=parent.frame())
      append.log(if(is(x, "data.table")) {
          nrow(x)
      } else { NA })
      x
}
environment(idx.wrap)<-asNamespace("data.table")

(unlockBinding)("[.data.table",asNamespace("data.table"))
assign("[.data.table",idx.wrap,envir=asNamespace("data.table"),inherits=FALSE)


dt<-data.table(a=1:10, b=seq(2, 20, by=2), c=letters[1:10])

dt[a%%2==0]
MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • Nice! really appreciate your effort. It makes sense! Do you know if that would fits well in terms of not making any additional copies and not slowing down? I would prefer to make logging as light as possible. – jangorecki Mar 25 '15 at 20:44
  • I should clarify that just because it's possible, I don't necessarily *recommend* trying to inject code into functions in package namespaces. In terms of performance, you'll have to do your own bench-marking. I can't imagine a scenario were this type of logging would be useful or necessary, but that's up to you. – MrFlick Mar 25 '15 at 20:48
  • yes, that's the dirty way, but works. I'm going to utilize CI travis and test not only my package but also data.table test after loading my package. And the use case: ETL transformation steps. – jangorecki Mar 25 '15 at 21:05
  • see my brand new answer :) – jangorecki Apr 18 '15 at 00:26
1

Since R 3.2.0 it is fully possible, thanks to new function returnValue. Working example below.

f <- function(x, err = FALSE){
  pt <- proc.time()[[3L]]
  on.exit(message(paste("proc.time:",round(proc.time()[[3L]]-pt,4),"\nnrow:",as.integer(nrow(returnValue()))[1L])))
  Sys.sleep(0.001)
  if(err) stop("some error")
  return(x)
}
dt <- data.frame(a = 1:5, b = letters[1:5])
f(dt)
f(dt, err=T)
f(dt)
f(dt[dt$a %in% 2:3 & dt$b %in% c("c","d"),])
jangorecki
  • 16,384
  • 4
  • 79
  • 160