[Rd] S3 lookup rules changed in R 3.6.1
Konrad Rudolph
konr@d@rudo|ph @end|ng |rom gm@||@com
Wed Oct 9 21:22:13 CEST 2019
tl;dr: S3 lookup no longer works in custom non-namespace environments as of
R 3.6.1. Is this a bug?
I am implementing S3 dispatch for generic methods in environments that are
not
packages. I am trying to emulate the R package namespace mechanism by
having a
“namespace” environment that defines generics and methods, but only exposes
the
generics themselves, not the methods.
To make S3 lookup work when using the generics, I am using
`registerS3method`.
While this method itself has no extensive documentation, the documentation
of
`UseMethod` contains this relevant passage:
> Namespaces can register methods for generic functions. To support this,
> ‘UseMethod’ and ‘NextMethod’ search for methods in two places: in the
> environment in which the generic function is called, and in the
registration
> data base for the environment in which the generic is defined (typically a
> namespace). So methods for a generic function need to be available in the
> environment of the call to the generic, or they must be registered. (It
does
> not matter whether they are visible in the environment in which the
generic is
> defined.) As from R 3.5.0, the registration data base is searched after
the
> top level environment (see ‘topenv’) of the calling environment (but
before
> the parents of the top level environment).
This used to work but it stopped working in R 3.6.1 and I cannot figure out
(a)
why, and (b) how to fix it. Unfortunately I am unable to find the relevant
information by reading the R source code, even when “diff”ing what seem to
be
the only even remotely relevant changes [1].
The R NEWS merely list the following change for R 3.6.0:
> * S3method() directives in ‘NAMESPACE’ can now also be used to perform
delayed
> S3 method registration.
> […]
> * Method dispatch uses more relevant environments when looking up class
> definitions.
Unfortunately it is not clear to me what exactly this means.
Here’s a minimal example code that works under R 3.5.3 but breaks under
R 3.6.1
(I don’t know about 3.6.0).
```
# Define “package namespace”:
ns = new.env(parent = .BaseNamespaceEnv)
local(envir = ns, {
test = function (x) UseMethod('test')
test.default = function (x) message('test.default')
test.foo = function (x) message('test.foo')
.__S3MethodsTable__. = new.env(parent = .BaseNamespaceEnv)
.__S3MethodsTable__.$test.default = test.default
.__S3MethodsTable__.$test.foo = test.foo
# Or, equivalently:
# registerS3method('test', 'default', test.default)
# registerS3method('test', 'foo', test.foo)
})
# Expose generic publicly:
test = ns$test
# Usage:
test(1)
test(structure(1, class = 'foo'))
```
Output in R up to 3.5.3:
```
test.default
test.foo
```
Output in R 3.6.1:
```
Error in UseMethod("test") :
no applicable method for 'test' applied to an object of class
"c('double', 'numeric')"
```
It’s worth noting that the output of `.S3methods` is the same for all R
versions, and from my understanding of its output, this *should* indicate
that
S3 lookup should behave identically, too. Furthermore, lookup via
`getS3method`
succeeds in all R versions, and (again, in my understanding) the logic of
this
function should be identical to the logic of R’s internal S3 dispatch:
```
getS3method('test', 'default')(1)
getS3method('test', 'foo')(1)
```
Conversely, specialising an existing generic from a loaded package works.
E.g.:
```
local(envir = ns, {
print.foo = function (x) message('print.foo')
registerS3method('print', 'foo', print.foo)
})
print(structure(1, class = 'foo'))
```
This prints “print.foo” in all R versions as expected.
So my question is: Why do the `test(…)` calls in R 3.6.1 no longer trigger
S3
method lookup in the generic function’s environment? Is this behaviour by
design
or is it a bug? If it’s by design, why does `getS3method` still use the old
behaviour? And, most importantly, how can I fix my definition of `ns` to
make
S3 dispatch for non-exposed methods work again?
… actually I just found a workaround:
```
ns$.packageName = 'not important'
```
This marks `ns` as a package namespace. To me, the documentation seems to
imply
that this shouldn’t be necessary (and it previously wasn’t). Furthermore,
the
code for `registerS3method` explicitly supports non-package namespace
environments. Unfortunately this workaround is not satisfactory because
pretending that the environment is a package namespace, when it really
isn’t,
might break other things.
[1] See r75273; there’s also r74625, which changes the actual lookup
mechanism
used by `UseMethod`, but that seems even less relevant, because it is
disabled unless a specific environment variable is set.
--
Konrad Rudolph
[[alternative HTML version deleted]]
More information about the R-devel
mailing list