[R-pkg-devel] [External] Re: Two packages with the same generic function

Viechtbauer, Wolfgang (SP) wo||g@ng@v|echtb@uer @end|ng |rom m@@@tr|chtun|ver@|ty@n|
Tue Jun 23 10:00:05 CEST 2020


Thanks to all who replied and provided input.

@Duncan: Yes, of course there is the note about masking. In my experience, many people won't understand what this means, what the consequences are, and that pkgA::foo(x) and pkgB::foo(x) are workarounds. And yes, pkgA importing the foo() generic from pkgB or vice-versa is an obvious solution.

@Bert: Related to the suggestion that one package could import the generic from the other, you raise an important point. This in essence adds a whole package as a dependency for these two lines of code:

foo <- function(x, ...)
   UseMethod("foo")

And let's say pkgA imports this from pkgB and pkgB depends on 10 other packages? Does this mean that pkgA has now added 11 packages as dependencies for importing 2 lines of code? If so, that seems quite undesirable to me.

@Neal: A separate package with generic functions that pkgA and pkgB could import is an interesting suggestion, thanks!

@Tom: I might need some time to process what you are suggesting. What I am really hoping for (and not sure if your approach can handle this) is that the following works regardless of the order in which pkgA and pkgB are loaded:

x <- 1:4
class(x) <- "A"
foo(x)
class(x) <- "B"
foo(x)

Again, that used to be the case in R 3.6.3, but not anymore in 4.0.x.

Best,
Wolfgang

>-----Original Message-----
>From: R-package-devel [mailto:r-package-devel-bounces using r-project.org] On
>Behalf Of Bert Gunter
>Sent: Tuesday, 23 June, 2020 4:55
>To: luke-tierney using uiowa.edu
>Cc: r-package-devel using r-project.org
>Subject: Re: [R-pkg-devel] [External] Re: Two packages with the same generic
>function
>
>OK. Thanks.
>
>Bert Gunter
>
>On Mon, Jun 22, 2020 at 7:51 PM <luke-tierney using uiowa.edu> wrote:
>
>> On Tue, 23 Jun 2020, Bert Gunter wrote:
>>
>> > "Users don't get warned about overriding names in packages they've
>> > loaded, because that would just be irritating."
>>
>> All Duncan is saying is that you don't get a notification if you do
>>
>>      mean <- log
>>
>> in the interpreter. If you attach a package that does this you would
>> get a notification (or an error if you configure your conflict
>> resolution options appropriately).
>>
>> Best,
>>
>> luke
>>
>> >
>> > Is that also true if the package or generic is imported by another that
>> > they load; or is a dependency of a package they load? If so, I would not
>> > call it "just irritating" because if silent, how would they know?
>> >
>> >
>> > Bert Gunter
>> >
>> > "The trouble with having an open mind is that people keep coming along
>> and
>> > sticking things into it."
>> > -- Opus (aka Berkeley Breathed in his "Bloom County" comic strip )
>> >
>> >
>> > On Mon, Jun 22, 2020 at 5:58 PM Mark Leeds <markleeds2 using gmail.com> wrote:
>> >
>> >> Hi Duncan: I maintain dynlm and your example is the exact reason I've
>> been
>> >> getting emails from people regarding
>> >> it not working correctly. I've been telling them to load dplyr by using
>> >>
>> >> library(dplyr, exclude = c("filter", "lag"))
>> >>
>> >> On Mon, Jun 22, 2020 at 7:57 PM Duncan Murdoch <
>> murdoch.duncan using gmail.com>
>> >> wrote:
>> >>
>> >>> On 22/06/2020 3:48 p.m., Tom Wainwright wrote:
>> >>>> Yet another alternative is simply to prevent your second package from
>> >>>> overriding the previously defined generic. The basic problem is the
>> >> ease
>> >>>> with which R allows overriding prior generic definitions (one of
>those
>> >>> bits
>> >>>> of bad behavior we in the USA used to call "a Bozo No-No"), which
>> hides
>> >>> all
>> >>>> the previous methods, as demonstrated by the following code:
>> >>>>
>> >>>>> plot(1:3)
>> >>>>>> plot <- function(x, ...) UseMethod("plot")
>> >>>>>> plot(1:3)
>> >>>>> Error in UseMethod("plot") :
>> >>>>>    no applicable method for 'plot' applied to an object of class
>> >>>>> "c('integer', 'numeric')"
>> >>>>>> rm(plot)
>> >>>>>> plot(1:3)
>> >>>>
>> >>>> (Despite Murdoch's suggestion that overriding the generic SHOULD
>issue
>> >> a
>> >>>> warning, it doesn't seem to in R 4.0.1.)
>> >>>
>> >>> Sure it does, if pkgA and pkgB both export the same name, then you get
>> a
>> >>> warning when you attach the second one.  For example,
>> >>>
>> >>> > library(MASS)
>> >>> > library(dplyr)
>> >>>
>> >>> Attaching package: ‘dplyr’
>> >>>
>> >>> The following object is masked from ‘package:MASS’:
>> >>>
>> >>>      select
>> >>>
>> >>> The following objects are masked from ‘package:stats’:
>> >>>
>> >>>      filter, lag
>> >>>
>> >>> The following objects are masked from ‘package:base’:
>> >>>
>> >>>      intersect, setdiff, setequal, union
>> >>>
>> >>> Users don't get warned about overriding names in packages they've
>> >>> loaded, because that would just be irritating.
>> >>>
>> >>> Duncan Murdoch
>> >>>>
>> >>>> So, we might try protecting the generic definitions of "foo" in both
>> >>>> packages by enclosing them in something like:
>> >>>>
>> >>>> tryCatch(invisible(methods("foo")), error = {foo <- function(x,...)
>> >>>>> UseMethod("foo")}, finally=NULL)
>> >>>>
>> >>>>
>> >>>> There's probably a more elegant way to accomplish this. This relies
>on
>> >>>> "methods" returning an error if "foo" has no defined methods, so it
>is
>> >>> not
>> >>>> redefined if their are previous methods. I haven't had time to try
>> this
>> >>> in
>> >>>> the two-package example, but it might work, although I'm not sure how
>> >> to
>> >>>> handle the Namespace declarations.
>> >>>>
>> >>>>    Tom Wainwright
>> >>>>
>> >>>> On Mon, Jun 22, 2020 at 10:41 AM Bert Gunter <bgunter.4567 using gmail.com>
>> >>> wrote:
>> >>>>
>> >>>>> ...
>> >>>>> and just to add to the query, assume the author of pkg B did (does)
>> >> not
>> >>>>> know of pkg A and so, for example, could (did) not import any of pkg
>> >> A's
>> >>>>> content into B. Given that there are at the moment ~20,000 packages
>> >> out
>> >>>>> there, this does not seem to be an unreasonable assumption. One may
>> >> even
>> >>>>> further assume that the user may not know that (s)he has package B
>> >>> loaded,
>> >>>>> as it may be a dependency of another package that (s)he uses. I
>> >>> certainly
>> >>>>> don't keep track of all the dependencies of packages I use.
>> >>>>>
>> >>>>> Under these assumptions, is there any more convenient alternative to
>> >>>>> Wolfgang's pkgA:foo(x) explicit call under such assumptions? If pkgA
>> >>> has a
>> >>>>> long name, what might one do?
>> >>>>>
>> >>>>> Bert Gunter
>> >>>>>
>> >>>>> "The trouble with having an open mind is that people keep coming
>> along
>> >>> and
>> >>>>> sticking things into it."
>> >>>>> -- Opus (aka Berkeley Breathed in his "Bloom County" comic strip )
>> >>>>>
>> >>>>>
>> >>>>> On Mon, Jun 22, 2020 at 10:00 AM Viechtbauer, Wolfgang (SP) <
>> >>>>> wolfgang.viechtbauer using maastrichtuniversity.nl> wrote:
>> >>>>>
>> >>>>>> Hi All,
>> >>>>>>
>> >>>>>> Let's say there are two packages pkgA and pkgB, both of which have
>a
>> >>>>>> generic function
>> >>>>>>
>> >>>>>> foo <- function(x, ...)
>> >>>>>>     UseMethod("foo")
>> >>>>>>
>> >>>>>> and pkgA has a method for objects of class "A":
>> >>>>>>
>> >>>>>> foo.A <- function(x, ...)
>> >>>>>>     print(x)
>> >>>>>>
>> >>>>>> and pkgB has a method for objects of class "B":
>> >>>>>>
>> >>>>>> foo.B <- function(x, ...)
>> >>>>>>     plot(x)
>> >>>>>>
>> >>>>>> Both packages export foo and their method and declare their
>> >> respective
>> >>> S3
>> >>>>>> methods, so:
>> >>>>>>
>> >>>>>> export(foo)
>> >>>>>> export(foo.A)
>> >>>>>> S3method(foo, A)
>> >>>>>>
>> >>>>>> in NAMESPACE of pkgA and
>> >>>>>>
>> >>>>>> export(foo)
>> >>>>>> export(foo.B)
>> >>>>>> S3method(foo, B)
>> >>>>>>
>> >>>>>> in NAMESPACE of pkgB.
>> >>>>>>
>> >>>>>> If a user loads pkgA first and then pkgB, this fails:
>> >>>>>>
>> >>>>>> library(pkgA)
>> >>>>>> library(pkgB)
>> >>>>>> x <- 1:4
>> >>>>>> class(x) <- "A"
>> >>>>>> foo(x)
>> >>>>>>
>> >>>>>> Error in UseMethod("foo") :
>> >>>>>>    no applicable method for 'foo' applied to an object of class "A"
>> >>>>>>
>> >>>>>> and vice-versa. Of course, pkgA::foo(x) works. Aside from pkgA
>> >>> importing
>> >>>>>> foo() or vice-versa, is there some other clever way to make this
>> >> work?
>> >>> In
>> >>>>>> earlier versions of R (at least in 3.6.3), this used to work (i.e.,
>> >> the
>> >>>>>> generic foo() from pkgB would find method foo.A() and vice-versa),
>> >> but
>> >>>>> not
>> >>>>>> since 4.0.0.
>> >>>>>>
>> >>>>>> Best,
>> >>>>>> Wolfgang


More information about the R-package-devel mailing list