mpiktas
mpiktas

Reputation: 11518

How to extend S3 method from another package without loading the package

I am developing a package which has the function forecast.myclass. I want that function to work nicely with forecast package. I.e. when forecast package is loaded the code forecast(object) should call forecast.myclass from my package.

Since I need only generic definition of forecast from the package forecast, and I do not use any other function from the package forecast I am reluctant to include it in the Depends. So I define the generic in my package in the following way:

##'
##' @export
forecast <- function(object,...) UseMethod("forecast") 

##' @rdname forecast.midas_r
##' @method forecast midas_r
##' @export
forecast.midas_r <- function(object,newdata=NULL,method=c("static","dynamic"),insample=get_estimation_sample(object),...) {

Now everything works as expected when package forecast is not loaded. But when I load package forecast, then forecast.midas_r is not called, when doing forecast(object) where object is of class midas_r. How should I solve this problem?

Upvotes: 19

Views: 3911

Answers (4)

thus__
thus__

Reputation: 486

This github issue was very helpful for me.

Following the patterns of {sf}:

  1. Don't export your functions in your namespace
  2. Copy the register_s3_method() function definition in a new R script or the same that has your methods
  3. copy the scaffolding for registering all methods and repeat the body for all of the methods you may have in your package
register_all_s3_methods = function() {
  register_s3_method("pkg", "function", "class")
}
  1. In a new R script (preferably zzz.R), have an .onLoad() hook containing
.onLoad = function(libname, pkgname) {
  register_all_s3_methods()
}
  1. Redocument your package
  2. devtools::load_all()

Upvotes: 1

dardisco
dardisco

Reputation: 5274

I'm not sure there's an easy solution to this. As others have said it's probably easiest to use Depends to get around this, rather than redefining a generic method.

Here's a simple example which works for me. It's largely the same as your solution, but declaring @export means you won't need to manually update the NAMESPACE file.

##' @name mean
##' @export mean.newClass
##' 
##' @method mean newClass
##'
##' @title mean for \code{newClass} object
##' @param x A \code{newClass} object
##' @param ... Additional arguments
##'
mean.newClass <- function(x, ...){
  stopifnot(class(x)=="newClass")
  return(42)
}

Then package.skeleton("newPkg"). Put file mean.R with the above contents in the directory /R of the package.

Ensure you're in the directory 1 level below, then

roxygenize("newPkg", roxygen.dir="newPkg", copy.package=F, unlink.target=F)

Now

library(devtools)
dev_mode(on=TRUE) ### don't want to have to uninstall the package later
install_local("newPkg")
library(newPkg)
x <- c(1,2)
class(x) <- "newClass"
stopifnot(mean(x)==42)
stopifnot(mean(unclass(x))==1.5)

I realize mean is a function in base but I have tested this for modifying generic functions elsewhere to give them a new method, so it should extend to your more general case also.

Upvotes: 7

Martin Morgan
Martin Morgan

Reputation: 46866

The problem here is that your definition of the forecast generic is masking the definition from the forecast package, and your method is associated with your generic rather than the forecast package generic; this is just a complicated instance of two packages defining functions of the same name. The solution is to bite the bullet and Depend: on forecast, or when at the command line and both your package and forecast are attached fully resolve the function mypackage::forecast(), or Import: forecast but not make the forecast generic available to the end user except by having them require(forecast) (this might be appropriate if forecast functionality were somehow peripheral to your package, e.g., plotting in 3D when plotting in 2D was sufficient).

For what it's worth, an S4 method in PkgB defined and exported on an imported S4 generic from PkgA implicitly exposes the S4 generic to the user, so the generic is available even if Imports: PkgA is specified in the DESCRIPTION file of PkgB.

Upvotes: 4

mpiktas
mpiktas

Reputation: 11518

One possible solution is to forcefully export forecast.midas_r. This means manually updating NAMESPACE with export(forecast.midas_r) everytime after check("yourpackagename").

What this does is make forecast.midas_r visible for package forecast. If forecast.midas_r is not exported, and exists only in the namespace of the package, then when package forecast is loaded, the generic forecast is overwritten with the identical function from package forecast. So when forecast is invoked on unknown object, R looks up for corresponding methods in package forecast and in general workspace. Since forecast.midas_r is a private method R does not find it and produces an error.

This is not a perfect solution, since you need to manually update NAMESPACE, but it is a solution nevertheless.

Upvotes: 3

Related Questions