I have a package where I have S3 methods that, based on some condition needs to re-dispatch from the original method in a way that cannot be immediately solved by S3, S4, or R6.
Here's a notional package:
devtools::create("~/newproj")
Create ~/newproj/R/func.R:
#' Some function
#'
#' @param obj some object
#' @return something
#' @export
func <- function(obj) UseMethod("func")
#' @export
func.default <- function(obj) message("I am lost")
#' @export
func.character <- function(obj) paste("5 times", obj)
#' @export
func.numeric <- function(obj) 5 * as.numeric(as.character(obj))
#' @export
func.factor <- function(obj) {
asnum <- suppressWarnings(as.numeric(as.character(obj)))
cl <- match.call()
cl[[1]] <- if (anyNA(asnum)) substitute(func.character) else substitute(func.numeric)
eval.parent(cl)
}
As inane as that method is, the takeaway is that when the object is a factor, the action taken on it depends on whether it can be number-like or not. In reality, there are a number of candidate S3 methods to be used, and I've always found the use of eval.parent to be somewhat elegant, one reason being that it reduces layers on the stack. (Perhaps this is not worth the squeeze ...)
Demo:
devtools::document("~/newproj")
# i Updating newproj documentation
# i Loading newproj
# Writing NAMESPACE
# Writing NAMESPACE
func(5)
# [1] 25
func("a")
# [1] "5 times a"
func(factor("5"))
# [1] 25
func(factor("a"))
# [1] "5 times a"
This works, but since devtools is permissive in its visibility of unexported functions, I'll load it as a package:
detach("package:newproj")
devtools::install("~/newproj")
library(newproj)
func(5)
# [1] 25
func('a')
# [1] "5 times a"
func(factor('a'))
# Error in func.character(obj = factor("a")) :
# could not find function "func.character"
Which may not be too surprising, since NAMESPACE is just
S3method(func,character)
S3method(func,default)
S3method(func,factor)
S3method(func,numeric)
export(func)
One way to fix this is to :::-reference the functions (since :: doesn't work):
#' @export
func.factor <- function(obj) {
asnum <- suppressWarnings(as.numeric(as.character(obj)))
cl <- match.call()
cl[[1]] <- if (anyNA(asnum)) substitute(newproj:::func.character) else substitute(newproj:::func.numeric)
eval.parent(cl)
}
Which now works:
detach("package:newproj")
devtools::install("~/newproj")
library(newproj)
func(5)
# [1] 25
func(factor("5"))
# [1] 25
func(factor("a"))
# [1] "5 times a"
But now, if I R CMD check the package, I get
> checking dependencies in R code ... NOTE
There are ::: calls to the package's namespace in its code. A package
almost never needs to use ::: for its own objects:
'func.character' 'func.numeric'
I can perhaps ignore the check NOTE, but I really prefer to clean up notes and such when possible. I'm trying to avoid something like:
if (anyNA(asnum)) {
func.character(obj)
} else func.numeric(obj)
perhaps for the wrong reasons though. Do more elegant solutions exist?