13
votes

I'm working on an R package at present and trying to follow the best practice guidelines provided by Hadley Wickham at http://r-pkgs.had.co.nz. As part of this, I'm aiming to have all of the package dependencies within the Imports section of the DESCRIPTION file rather than the Depends since I agree with the philosophy of not unnecessarily altering the global environment (something that many CRAN and Bioconductor packages don't seem to follow).

I want to use functions within the Bioconductor package rhdf5 within one of my package functions, in particular h5write(). The issue I've now run into is that it doesn't have its S3 methods declared as such in its NAMESPACE. They are declared using (e.g.)

export(h5write.default)
export(h5writeDataset.matrix)

rather than

S3method(h5write, default)
S3method(h5writeDataset, matrix)

The generic h5write is defined as:

h5write <- function(obj, file, name, ...) {
res <- UseMethod("h5write")
  invisible(res)
}

In practice, this means that calls to rhdf5::h5write fail because there is no appropriate h5write method registered.

As far as I can see, there are three solutions to this:

  1. Use Depends rather than Imports in the DESCRIPTION file.
  2. Use library("rhdf5") or require("rhdf5") in the code for the relevant function.
  3. Amend the NAMESPACE file for rhdf5 to use S3methods() rather than export().

All of these have disadvantages. Option 1 means that the package is loaded and attached to the global environment even if the relevant function in my package is never called. Option 2 means use of library in a package, which while again attaches the package to the global environment, and is also deprecated per Hadley Wickham's guidelines. Option 3 would mean relying on the other package author to update their package on Bioconductor and also means that the S3 methods are no longer exported which could in turn break other packages which rely on calling them explicitly.

Have I missed another alternative? I've looked elsewhere on StackOverflow and found the following somewhat relevant questions Importing S3 method from another package and How to export S3 method so it is available in namespace? but nothing that directly addresses my issue. Of note, the key difference from the first of these two is that the generic and the method are both in the same package, but the issue is the use of export rather than S3method.

Sample code to reproduce the error (without needing to create a package):

loadNamespace("rhdf5")
rdhf5::h5write(1:4, "test.h5", "test")

Error in UseMethod("h5write") : 
no applicable method for 'h5write' applied to an object of class
"c('integer', 'numeric')

Alternatively, there is a skeleton package at https://github.com/NikNakk/s3issuedemo which provides a single function demonstrateIssue() which reproduces the error message. It can be installed using devtools::install_github("NikNakk/s3issuedemo").

1
I'm not sure what your exact question is, are you trying to use the packages methods? Are you trying to create new methods? What is your objective? Your question is detailed but isn't very clear.cdeterman
Sorry if I wasn't clear. I want to use the rhdf5::h5write function within a function in my package. Everything works fine if I attach the rhdf5 package to the global environment by using Depends or library(). It also works fine if I alter the other package's NAMESPACE file to use S3method() rather than export(). But otherwise, it fails. I've amended my question above.Nick Kennedy
Can you provide a reproducible example? Is this package of yours on github where we could see your source code?cdeterman
The package I'm working on isn't quite ready to be put up publicly, but I've put up a skeleton package at github.com/NikNakk/s3issuedemo which demonstrates the issue using minimal code.Nick Kennedy

1 Answers

10
votes

The key here is to import the specific methods in addition to the generic you want to use. Here is how you can get it to work for the default method.

Note: this assumes that the test.h5 file already exists.

#' @importFrom rhdf5 h5write.default
#' @importFrom rhdf5 h5write
#' @export
myFun <- function(){
    h5write(1:4, "test.h5", "test")
}

I also have put up my own small package demonstrating this here.