R S3 classes - changing class and pass back to same method

Viewed 211

I have a question about the "right" way to do something with S3 classes in R. What I want to do is have a method that changes the class and then calls the same method on the new class. Something like this:

my_func <- function(.x, ...) {
  UseMethod("my_func")
}

my_func.character <- function(.x, ...) {
  return(paste(".x is", .x, "of class", class(.x)))
}

my_func.numeric <- function(.x, ...) {
  .x <- as.character(.x)
  res <- my_func(.x) # this should call my_func.character
  return(res)
}

And this works. When I do the following, I get class character for both

> my_func("hello")
[1] ".x is hello of class character"
> my_func(1)
[1] ".x is 1 of class character"

My question is: is this the right way to do this? Something feels weird about just re-calling the same method after I've converted the class (this line res <- my_func(.x)).

I felt like NextMethod() must somehow be the answer, but I've read a bunch of docs on it (this and this for example) but they all talk about this thing where it's jumping up to the next class in the list of classes, like from data.frame up to matrix when you have class(df) and get c("data.frame", "matrix") for example.

But none of them talk about this situation where you're converting it to an entirely different class that wasn't in the original hierarchy. So maybe NextMethod() isn't the right thing to use, but is there something else, or should I just leave it like I have it?

Thanks!

2 Answers

1) If the idea is that my_func.numeric does additional processing but wants to make use of my_func.character as well without repeating that then in func.numeric set .Class and then call NextMethod like this:

my_func.numeric <- function(.x, ...) {
  .Class <- "character"
  .x <- as.character(.x)
  NextMethod()
}

NextMethod returns a result and additional processing can be optionally done after it. More information can be found by issuing ?NextMethod in R.

2) If the idea is that the numeric and character methods can be combined then:

my.func.character <- my.func.numeric <- function(.x, ...) {
    .x <- as.character(.x)
    paste(".x is", .x, "of class", class(.x))
}

It would also be possible to use the default method for this if that is not already being used for something else.

3) If the idea is just that there is shared functionality but you don't necessarily want to use all of the character method processing in the numeric method or visa versa then define a function called by each:

my_func_impl <- function(.x, ...) paste(".x is", .x, "of class", class(.x))

my_func.character <- function(.x, ...) {
  # some procesing unique to this method
  my_func_impl(.x, ...)
}

my_func.numeric <- function(.x, ...) {
  # some procesing unique to this method
  my_func_impl(.x, ...)
}

The most standard way would be to create a default method, something like:

my_func.default <- function(.x, ...) {
  stopifnot(is.atomic(.x)) # throw an error if .x isn't an atomic vector
  .x <- as.character(.x)
  paste(".x is", .x, "of class", class(.x))
}
my_func("hello")
#> [1] ".x is hello of class character"
my_func(1)
#> [1] ".x is 1 of class character"
Related