Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Allow user of R6 class to provide function which access `self`

Goal

I want to tweak the following R6 class such that calc is not hard coded but can be provided by the user.

Hard Coded

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

As a parameter

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
             )
)

Problem

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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__?

>Solution :

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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading