简体   繁体   中英

Writing a decorator for R functions

A colleague recently was looking at call graphs and wanted to see what called what. We sorted that with foodweb from mvbutils, but I was wondering about how best to create a decorator (in python speak) in R. So I did this:

instrument=function(z){
  force(z) 
  n=deparse(substitute(z)) # get the name
  f=function(...){
   cat("calling ", n,"\n")
   x=z(...)
   cat("done\n")
   return(x)
   }
  return(f)
}

This lets me do:

> foo=function(x,y){x+y}
> foo(1,2)
[1] 3

and now I can make the function log itself by wrapping it:

> foo=instrument(foo)
> foo(1,2)
calling  foo
done
[1] 3

has this been done before, in a package say, and have I missed any gotchas that will break my way of doing this?

The trace function in R does that. See ?trace .

my github package tag attempts to tackle this issue.

Your example could be solved as follows :

# remotes::install_github("moodymudskipper/tag")
library(tag)
deco <- tag(args = list(.first = NULL, .last = NULL), pattern = {
  t_args <- T_ARGS()                       # fetch arguments fed to tag
  eval.parent(t_args[[".first"]])          # run .first arg
  on.exit(eval.parent(t_args[[".last"]]))  # run .last arg on exit
  CALL()                                   # run main call
})


foo <- function(x, y) {Sys.sleep(1); x + y} # sleep 1 sec to highlight expected behavior

deco(quote(message("calling foo")), quote(message("done")))$foo(1, 2)
#> calling foo
#> done
#> [1] 3

foo2 <- deco(quote(message("calling foo")), quote(message("done")))$foo
foo2(1, 2)
#> calling foo
#> done
#> [1] 3

deco2 <- deco(quote(message("calling foo")), quote(message("done")))
deco2$foo(1, 2)
#> calling foo
#> done
#> [1] 3

Created on 2020-01-30 by the reprex package (v0.3.0)

tags are function operator factories (or adverb factories), here deco is a tag, and deco(quote(message("calling foo")), quote(message("done"))) is an adverb, with a method for $ . It means you could run deco(quote(message("calling foo")), quote(message("done")))(foo)(1,2) , but the dollar notation makes it friendlier.

The tag definition features defaults arguments (default value is mandatory, dots aren't supported), and a pattern which is a bit like your new body, using special functions T_ARGS() , F_ARGS() , F_ARGS() , F_FORMALS() and CALL() to access the tag or function's arguments or formals and the call itself (see ?tag::CALL ).

Some more magic is implemented so the tag's argument can be given to the tagged function itself, so the following can be done too :

deco$foo(1, 2, quote(message("calling foo")), quote(message("done")))
#> calling foo
#> done
#> [1] 3
foo2 <- deco$foo
foo2(1, 2, quote(message("calling foo")), quote(message("done")))
#> calling foo
#> done
#> [1] 3

In these cases you can enjoy the autocomplete in RStudio :

在此处输入图片说明

More info : https://github.com/moodymudskipper/tag The package tags contains a collection of such "decorators"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM