I want to tweak the following R6
class such that calc
is not hard coded but can be provided by the user.
library(R6)
A <- R6Class("A",
public = list(
run = function(x) {
private$calc(x)
},
result_codes = list(OK = 1, NOK = 2)
),
private = list(
calc = function(x) {
if (x >= 0) {
self$result_codes$OK
} else {
self$result_codes$NOK
}
})
)
a <- A$new()
a$run(1)
# [1] 1
If I want the user to supply a custom calc
function I could do the following:
B <- R6Class("B",
public = list(
initialize = function(calc) {
private$calc <- calc
},
run = function(x) {
private$calc(x)
},
result_codes = list(OK = 1, NOK = 2)
),
private = list(
calc = NULL
)
)
I want that the user can use self$result_codes
, but this does not work because the function is defined in the global environment where self
is not known and not "within" the R6Class:
b <- B$new(function(x) {
print(rlang::env_parent())
if (x >= 0) {
self$result_codes$OK
} else {
self$result_codes$NOK
}
})
b$run(1)
# <environment: R_GlobalEnv>
# Error in calc(...) : object 'self' not found
Thus, the user needs to provide calc
like this:
b <- B$new(function(x) {
me <- environment(rlang::caller_fn())
if (x >= 0) {
me$self$result_codes$OK
} else {
me$self$result_codes$NOK
}
})
b$run(-1)
# [2]
which I find cumbersome. Thus, I wrapped calc
in initialize
, such that the user can simply type self$result_codes$OK
without bothering about changing the environment:
B <- R6Class("B",
public = list(
initialize = function(calc) {
private$calc <- calc
environment(private$calc) <- self$.__enclos_env__
},
run = function(x) {
private$calc(x)
},
result_codes = list(OK = 1, NOK = 2)
),
private = list(
calc = NULL
)
)
b <- B$new(function(x) {
if (x >= 0) {
self$result_codes$OK
} else {
self$result_codes$NOK
}
})
b$run(-1)
# [1] 2
This feels extremely hackish, because I am using the internal environment .__enclos_env__
(it seems like a road to hell to use double underscored properties).
How would I solve this problem? Is the approach of setting the environment of private$calc
the right direction? If so, how to avoid using .__enclos_env__
?
A straightforward and fairly clean solution would be to pass self
explicitly as a parameter to the calc
callback:
B <- R6Class(
"B",
public = list(
initialize = function(calc) {
private$calc <- calc
},
run = function(x) {
private$calc(self, x)
},
result_codes = list(OK = 1, NOK = 2)
),
private = list(
calc = NULL
)
)
b <- B$new(function(self, x) {
if (x >= 0) {
self$result_codes$OK
} else {
self$result_codes$NOK
}
})
Fiddling with environments (similarly to how you have attempted it) can work, but is a lot more complex and makes the solution brittle.