Closed gaborcsardi closed 5 years ago
Here's a slightly modified version:
library(R6)
x <- 1
deco <- function(fun, y) {
force(fun)
force(y)
function(...) {
cat("Decorated\n")
fun(...) + x + y
}
}
A <- R6Class(
"A",
public = list(
method = deco(function() { self$z }, 10),
z = 100
)
)
a <- A$new()
a$method()
These are the three functions involved:
deco
, which in this case closes over the global environment. It gets x
from the enclosed environment, and y
is passed in.method
: The decorated function returned by deco
.fun
, which is the function passed to deco
. In the example above, we want it to have its environment reassigned when an object is instantiated or cloned, so that it can refer to the correct self
.fun
is the function whose environment should change on instantiation or cloning. In order to do that, we would need to reach into method
's enclosing environment and essentially do:
e <- environment(method)
environment(e$fun) <- private$.__enclos_env__
To make this work, we would need to tell R6Class
how to find fun
, given method
. The challenge would be to come up with a decent API for this.
Another thing that works right now is to call self$method <- deco(self$method)
in initialize
:
library(R6)
x <- 1
deco <- function(fun, y) {
force(fun)
force(y)
function(...) {
cat("Decorated\n")
fun(...) + x + y
}
}
A <- R6Class(
"A",
public = list(
initialize = function() {
unlockBinding("method", self)
self$method <- deco(self$method, 10)
},
method = function() { self$z },
z = 100
)
)
a <- A$new()
a$method()
#> Decorated
#> [1] 111
The unlockBinding
is not great, but that could be made into a parameter for R6Class
. lock_objects=F
currently allows new members to be added, but don't allow existing ones to be changed. Maybe it could do that.
The other problem is that cloning won't work properly, but maybe that could be worked around somehow.
Yeah, I ended up with a similar approach, and calling a decorate()
function from init
: https://github.com/r-lib/ps/blob/764dc78c03691d5692f28c874e87bd31a924dd85/R/decorators.R
I think in general it would be useful to allow the methods to be a closure, I am not sure what the best syntax would be.
If there were some standard for decorators to add a "decorator"
class (and possibly some attributes), that could make this request feasible. That could also help solve the issue that @hadley mentioned elsewhere: that decorators obscure arguments to the function, and that decorated functions don't print nicely.
decorators obscure arguments to the function, and that decorated functions don't print nicely.
These can be fixed, though, but yeah, without decorators in general, I'll just close this.
I wonder if there is a way to use "decorators" (as in Python) for the methods. Decorators are basically functionals and they give a nice and concise way to transform functions. E.g. something like this would be cool:
But of course this fails
because the environment of the method is lost. I wonder if it was possible to keep it, somewhere before the object's environment. Maybe not, just wondering, really.