Search code examples
rnamespacesr-s3

How to declare S3 method to default to loaded environment?


In a package, I would like to call an S3 method "compact" for object foobar.

There would therefore be a compact.foobar function in my package, along with the compact function itself:

compact = function(x, ...){
    UseMethod("compact", x)
}

However, this latter would be conflicting with purrr::compact.

I could default the method to use purrr (compact.default = purrr::compact, or maybe compact.list = purrr::compact), but that would make little sense if the user does not have purrr loaded.

How can I default my method to the loaded version of compact, in the user environment? (so that it uses purrr::compact, any other declared compact function, or fails of missing function)


Solution

  • Unfortunately S3 does not deal with this situation well. You have to search for suitable functions manually. The following works, more or less:

    get_defined_function = function (name) {
        matches = getAnywhere(name)
        # Filter out invisible objects and duplicates
        objs = matches$objs[matches$visible & ! matches$dups]
        # Filter out non-function objects
        funs = objs[vapply(objs, is.function, logical(1L))]
        # Filter out function defined in own package.
        envs = lapply(funs, environment)
        funs = funs[! vapply(envs, identical, logical(1L), topenv())]
        funs[1L][[1L]] # Return `NULL` if no function exists.
    }
    
    compact.default = function (...) {
        # Maybe add error handling for functions not found.
        get_defined_function('compact')(...)
    }
    

    This uses getAnywhere to find all objects named compact that R knows about. It then filters out those that are not visible because they’re not inside attached packages, and those that are duplicate (this is probably redundant, but we do it anyway).

    Next, it filters out anything that’s not a function. And finally it filters out the compact S3 generic that our own package defines. To do this, it compares each function’s environment to the package environment (given by topenv()).

    This should work, but it has no rule for which function to prefer if there are multiple functions with the same name defined in different locations (it just picks an arbitrary one first), and it also doesn’t check whether the function signature matches (doing this is hard in R, since function calling and parameter matching is very flexible).