[R-pkg-devel] Registering methods conditionally

Ivan Krylov |kry|ov @end|ng |rom d|@root@org
Sat Feb 8 21:07:26 CET 2025


В Sat, 8 Feb 2025 19:18:59 +0100
Lluís Revilla <lluis.revilla using gmail.com> пишет:

> Error in UseMethod("tidy") : no applicable method for 'tidy' applied
> to an object of class "GeneSet"

This seems to be a distant consequence of
<https://github.com/Bioconductor/BiocGenerics/issues/20>.

The example for BaseSet::tidy starts with

>> if (requireNamespace("GSEABase", quietly = TRUE)) {

which produces the following messages:

Loading required package: BiocGenerics
Loading required package: generics 
Attaching package: ‘generics’ 
The following objects are masked from ‘package:BaseSet’:         
   tidy, union                    # <-- BaseSet::tidy now shadowed

Indeed, at the point of the error, 'tidy' is

Browse[1]> tidy
function (x, ...) 
{
    UseMethod("tidy")
}
<bytecode: 0x5615feed95c0>
<environment: namespace:generics>

...which is different from your own 'tidy' generic:

Browse[1]> BaseSet::tidy
function (object) 
{
    UseMethod("tidy")
}
<bytecode: 0x5615eed320c8>
<environment: namespace:BaseSet>

...and the method is registered for the BaseSet::tidy generic, not the
generics::tidy generic:

Browse[1]> 'tidy.GeneSet' %in% ls(BaseSet:::.__S3MethodsTable__.)
[1] TRUE
Browse[1]> 'tidy.GeneSet' %in% ls(generics:::.__S3MethodsTable__.)
[1] FALSE

The problem only appears on R-devel because only the development
version of 'BiocGenerics' has the Depends: relationship with 'generics'.

This one is easier to diagnose than to cure. The example seems to work
fine if the methods are registered for the other generic at runtime:

library(BaseSet)
# these are undocumented, use .S3method() in more normal circumstances
registerS3method("tidy", "GeneSet", BaseSet:::tidy.GeneSet,
                 loadNamespace("generics"))
registerS3method("tidy", "GeneSetCollection",
                 BaseSet:::tidy.GeneSetCollection,
                 loadNamespace("generics"))
example("tidy") # doesn't crash

Since BaseSet depends on a new enough R version, it could in theory use
delayed S3 method registration in its NAMESPACE file [1] in addition to
registering a method for BaseSet::tidy, but I think that you're now
expected to importFrom(generics, tidy) and export(tidy) to register S3
methods on it. It's unfortunate that generics::tidy has a different
documented purpose. Perhaps the people better-versed in Bioconductor
have a better suggestion?

Not sure where S4 method dispatch comes into play in your example, but
conditional import of S4 classes may be hard to implement. NAMESPACE
files use R syntax but different semantics. Testing for getRversion()
works because that doesn't usually change for the lifetime of the
package library. Testing for requireNamespace() wouldn't have worked
because the results are normally cached at installation time. Maybe
there's a way using load hooks, but we'd need an S4 wizard to implement
that correctly. Thankfully, 'BaseSet' seems to only call setMethod()
for its own classes, so there are no copies of foreign classes creating
binary dependencies in its namespace.

-- 
Best regards,
Ivan

[1]
https://cran.r-project.org/doc/manuals/R-exts.html#Registering-S3-methods



More information about the R-package-devel mailing list