How to import only one function from another package, without loading the entire namespace
Asked Answered
P

3

46

Suppose I'm developing a package, called foo, which would like to use the description function from the memisc package. I don't want to import the whole memisc namespace because :

  1. It is bad
  2. memisc overrides the base aggregate.formula function, which breaks several things. For example, example(aggregate) would fail miserably.

The package includes the following files :

DESCRIPTION

Package: foo
Version: 0.0
Title: Foo
Imports:
    memisc
Collate:
    'foo.R'

NAMESPACE

export(bar)
importFrom(memisc,description)

R/foo.R

##' bar function
##'
##' @param x something
##' @return nothing
##' @importFrom memisc description
##' @export

`bar` <- function(x) {
    description(x)
}

I'd think that using importFrom would not load the entire memisc namespace, but only namespace::description, but this is not the case. Starting with a vanilla R :

R> getS3method("aggregate","formula")
## ... function code ...
## <environment: namespace:stats>
R> library(foo)
R> getS3method("aggregate","formula")
## ... function code ...
## <environment: namespace:memisc>
R> example(aggregate)
## Fails

So, do you know how I can import the description function from memisc without getting aggregate.formula in my environment ?

Provo answered 7/11, 2013 at 14:24 Comment(12)
Call it using memisc::description?Tommi
@HongOoi No, this won't work. The :: man page says that using it will load the entire namespace, I already tested it.Provo
It will load the namespace, but it shouldn't attach it to the search path. That way, you don't have to deal with other functions being overriden.Tommi
@HongOoi You can try the following in a R vanilla session, and you will still get aggregate.formula from memisc : memisc::description ; getS3method("aggregate","formula").Provo
I see what you mean. That's pretty weird.Tommi
I wonder to what degree to actual problem here is simply that the memisc authors should not have masked aggregate.formula.Neopythagoreanism
@Neopythagoreanism Totally agree with you. At least, for me, it will be a good reminder, never to override a base function.Provo
There's no way to load a single function from a namespace (without loading the namespace) because R has no way to know what other functions that function needs.Circumfuse
Also the whole interaction between namespaces and method registration is pretty awful and confusing.Circumfuse
I went to try and find documentation about namespaces and method registration and couldn't find any. Did not have the stomach to dive into the (I assume C level) source.Vitiated
@Circumfuse Yes, that makes sense, now I understand why R has to load every object from the namespace.Provo
Somewhat related, but not in context of package development, see this post that as of 3.6 you can selectively load functions: roelpeters.be/load-single-function-r-library Also see solutions offered on this thread: https://mcmap.net/q/373785/-load-a-package-apart-from-one-function which mentions {import} as a solution. (Again, non-package development contexts for those who stumbled here.)Doreathadoreen
V
29

You can't.

If you declare memisc in the Imports: field, the namespace will be loaded when the package is loaded and the exported objects will be findable by your package. (If you specify it in Depends:, the namespace will be loaded and attached to the search path which makes the exported objects findable by any code.)

Part of loading a namespace is registering methods with the generic. (I looked but couldn't find a canonical documentation that says this; I will appeal to the fact that functions are declared as S3 methods in the NAMESPACE file as evidence.) The defined methods are kept with the generic and have the visibility of the generic function (or, perhaps, the generic function's namespace).

Typically, a package will define a method either for a generic it creates or for a class it defines. The S3 object system does not have a mechanism for formally defining an S3 class (or what package created the class), but the general idea is that if the package defines functions which return an object with that class attribute (and is the only package that does), that class is that package's class. If either of these two conditions hold, there will not be a problem. If the generic is defined in the package, it can only be found if the package is attached; if the class is defined in the package, objects of that class would only exist (and therefore be dispatched on) if the package is attached and used.

In the memisc example, neither holds. The aggregate generic is defined in the stats package and the formula object is also defined in the stats package (based on that package defining as.formula, [.formula, etc.) Since it is neither memisc's generic nor memisc's object, the effects can be seen even (and the method dispatched to) if memisc is simply loaded but not attached.

For another example of this problem, but with reorder.factor, see Reordering factor gives different results, depending on which packages are loaded.

In general, it is not good practice to add methods to generics for which the package does not control either the object or the generic; doubly so if it overrides a method in a core package; and egregiously so if it is not a backwards compatible function to the existing function in the core packages.

For your example, you may be better off copying the code for memisc::describe into your package, although that approach has its own problems and caveats.

Vitiated answered 7/11, 2013 at 17:14 Comment(1)
Thanks a lot for the clear and detailed answer, and the advices. As a workaround I think I will just put memisc in Suggests and load the package with require when needed, as suggested by @Circumfuse here : #5260579Provo
P
4

With the caveat that I'm not too familiar with the R environment and namespaces, nor whether this would work in a package — a workaround I've used in programming is to use :: to copy the function into my own function.

It may have unknown consequences of loading the whole package, as discussed in the comments to OP's question, but it seems to not attach the package's function names to R namespace and mask existing function names.

Example:
my_memisc_description <- memisc::description

Piddling answered 15/2, 2018 at 19:16 Comment(0)
C
3

Use include.only

library(dplyr, include.only = "select")

# So `select` works:
cars |> select(speed) |> head(2)
#>   speed
#> 1     4
#> 2     4

# But other dplyr functions do not: 
cars |> slice(1:5)
#> Error in slice(cars, 1:5): could not find function "slice"

See also: exclude

Source: https://stat.ethz.ch/R-manual/R-devel/library/base/html/library.html

Concertante answered 1/9, 2023 at 12:11 Comment(1)
Hadn't seen this feature before. It was apparently added in version 3.6.0 released 2019-04-26. I don't know if it also avoids the registering of methods issue.Vitiated

© 2022 - 2024 — McMap. All rights reserved.