5

When using R6 classes, what is the proper way to define methods outside of the class that call other methods?

Consider the following example, where the function func might dispatch to another function if it is being used interactively. But, if it does so, the other function doesn't have access to the private environment. Should I be passing an environment around if I define classes this way?

## General function defined outside R6 class
func <- function(x) {
  if (missing(x) && interactive()) {
    ifunc()
  } else {
    private$a <- x * x
  }
}

## If interactive, redirect to this function
ifunc <- function() {
  f <- switch(menu(c('*', '+')), '1'=`*`, '2'=`+`)
  private$a <- f(private$a, private$a)
}

## R6 test object
Obj <- R6::R6Class("Obj",
  public=list(
    initialize=function(a) private$a <- a,
    geta=function() private$a,
    func=func  # defined in another file
  ),
  private=list(
    a=NA
  )
)

## Testing
tst <- Obj$new(5)
tst$func(3)
tst$geta()  # so func sees 'private'
# [1] 9

tst$func()  # doesn't see 'private'

Error in ifunc() (from #3) : object 'private' not found

Rorschach
  • 31,301
  • 5
  • 78
  • 129

2 Answers2

2

The problem you are facing is that you define func and ifunc in such a way that the implementation only makes sense inside the class definition - but not completely. You are getting an error because ifunc is implemented as if it knows about the internals of your class (it refers to private), but it doesn't. You never actually include it into your class definition. So you have to refer in func to the private member function ifunc and include ifunc into the class:

func <- function(x) {
    if (missing(x) && interactive()) {
        private$ifunc()
    } else {
        private$a <- x * x
    }
}


ifunc <- function() {
    f <- switch(menu(c('*', '+')), '1'=`*`, '2'=`+`)
    private$a <- f(private$a, private$a)
}


Obj <- R6::R6Class(
    "Obj",
    public=list(
        initialize=function(a) private$a <- a,
        geta=function() private$a,
        func=func  # defined in another file
    ),
    private=list(
        a=NA,
        ifunc=ifunc
    )
)

However, I must say that I don't understand this strategy. The functions func and ifunc then both have their own names in a top level environment but don't work - they really only make sense in the class definition. If code reuse is what you are interested in I think object composition or inheritance would be less surprising.

Sebastian
  • 840
  • 6
  • 11
  • I think the packages R.oo and proto would support something like you are looking for: you don't have to define all member in a class definition. But probably you would loose some features you like in R6... – Sebastian Mar 22 '16 at 20:30
1

First, let us boil the example down and make it more speaking:

# function defined outside R6 class
parent_function <- function(x) {
  if (missing(x)) {
    child_function()
  } else {
    self$a <- x * x
  }
}

# function called by previous function
child_function <- function() {
  self$a <- 999999
}

# R6 test object
my_object <- R6::R6Class(
  "my_object",
  public=list(
    func=parent_function,
    a=1
  )
)


# Testing
tst <- my_object$new()
tst

## <my_object>
## Public:
## a: 1
## clone: function (deep = FALSE) 
## func: function (x) 


tst$func(8)
tst$a

## [1] 64


tst$func()

## Error in self$a <- 999999 : object 'self' not found

Now the idea is to pass down self to the child function - since the parent function can obviously see self, so it can pass it on

# General function defined outside R6 class
parent_function <- function(x) {
  if (missing(x)) {
    child_function(self)
  } else {
    self$a <- x * x
  }
}

# If interactive, redirect to this function
child_function <- function(self) {
  self$a <- 999999
}

# R6 test object
my_object <- R6::R6Class(
  "my_object",
  public = 
    list(
      func=parent_function,
      a=1
    ),
)

## Testing
tst <- my_object$new()
tst

## <my_object>
## Public:
## a: 1
## clone: function (deep = FALSE) 
## func: function (x) 


tst$func(8)
tst$a

## [1] 64


tst$func()  
tst$a

## [1] 999999
petermeissner
  • 12,234
  • 5
  • 63
  • 63