Search code examples
rr6

R6 pass a self$FUN as parameter


I am starting to built a "user-friendly" R6 class and want to make a function does most of the work of my class. This is my structure so far

x <- X$new()
veggie_cubes <- veggie %>% x$cubesX(ID)
veggie_slices <- veggie %>% x$sliceX(ID)

My question is now if it is possible to rewrite the code such that:

x <- X$new()
veggie_cubes <- veggie %>% x$cutX(cubesX, ID)
veggie_slices <- veggie %>% x$cutX(sliceX, ID)

The function header should somehow look like: cut(.data, FUN, KEY)

So far my idea was to write cut like:

cutX= function(.data, FUN, KEY)
{
   .data %>%
     FUN({{ KEY }}) %>%
     base::return()
}

The only way this works was by calling veggie %>% x$cutX(x$cubesX, ID) which I would not really prefer as "user-friendly" solution, I also do not really like to use strings for that. Is there a way to write it without the x$?

here is the simplified R6 class:

X <- R6::R6Class(
  classname = "X",
  public = base::list(
    cubesX = function(.data, KEY)
    {
      .data %>% 
        dplyr::select(!{{ KEY }}) %>% 
        base::return()
    },
    sliceX = function(.data, KEY)
    {
      .data %>% 
        dplyr::select({{ KEY }}) %>% 
        base::return()
    },
    cutX = function(.data, FUN, KEY)
    {
      .data %>% 
        FUN( {{ KEY}}) %>% 
        base::return()
    }
  )
)

running examples:

x <- X$new()
iris %>% x$sliceX(Species)
iris %>% x$cubesX(Species)
# with FUN
iris %>% x$cutX(x$sliceX, Species)
iris %>% x$cutX(x$cubesX, Species)

not running:

iris %>% x$cutX(sliceX, Species)
iris %>% x$cutX(cubesX, Species)

Thanks in Advance :-)


Solution

  • The following implementation will allow you to call your functions via any of your three methods. It works by using non-standard evaluation to figure out whether FUN is in the format x$func or just a bare func. In either case it takes the bare function name and builds a call to turn it into self$func. Then it just evaluates that function.

    The following R6 class therefore works as expected on all your test examples:

    X <- R6::R6Class(
      classname = "X",
      public = base::list(
        cubesX = function(.data, KEY)
        {
          .data %>% 
            dplyr::select(!{{ KEY }}) %>% 
            base::return()
        },
        sliceX = function(.data, KEY)
        {
          .data %>% 
            dplyr::select({{ KEY }}) %>% 
            base::return()
        },
        cutX = function(.data, FUN, KEY)
        {
          if(is.call(substitute(FUN))) {
            FUN <- substitute(FUN) 
            FUN[[2]] <- quote(self)
          }
          else 
            FUN <- as.call(list(quote(`$`), quote(self), substitute(FUN)))
     
          eval(as.call(list(FUN, quote(.data), substitute(KEY))))
        }
      )
    )
    

    So, for example:

    x <- X$new()
    iris %>% x$cutX(sliceX, Species)
    
    #>        Species
    #> 1       setosa
    #> 2       setosa
    #> 3       setosa
    #> 4       setosa
    #> 5       setosa
    #> 6       setosa
    #> 7       setosa
    #> 8       setosa
    #> 9       setosa
    #> 10      setosa
    #> 11      setosa
    #> 12      setosa
    #> 13      setosa
    #> 14      setosa
    #> 15      setosa
    #> 16      setosa
    #> 17      setosa
    #> 18      setosa
    #> 19      setosa
    #> 20      setosa
    #> ...etc