Karolis Koncevičius
Karolis Koncevičius

Reputation: 9656

How to overload S4 slot selector `@` to be a generic function

I am trying to turn the @ operator in R into a generic function for the S3 system.

Based on the chapter in Writing R extensions: adding new generic I tried implementing the generic for @ like so:

`@` <- function(object, name) UseMethod("@")
`@.default` <- function(object, name) base::`@`(object, name)

However this doesn't seem to work as it breaks the @ for the S4 methods. I am using Matrix package as an example of S4 instance:

Matrix::Matrix(1:4, nrow=2, ncol=2)@Dim

Error in @.default(Matrix::Matrix(1:4, nrow = 2, ncol = 2), Dim) : no slot of name "name" for this object of class "dgeMatrix"

How to implement a generic @ so it correctly dispatches in the case of S4 classes?


EDIT

Also interested in opinions about why it might not be a good idea?

Upvotes: 3

Views: 347

Answers (2)

Mikael Jagan
Mikael Jagan

Reputation: 11306

In R 4.3.0 and newer, the @ operator will be internally S3 generic, as documented in the latest NEWS:

The @ operator is now an S3 generic. Based on contributions by Tomasz Kalinowski in PR#18482.

You can test on R-devel if you don't want to wait for the release of R 4.3.0 on April 21:

.S3method("@", "zzz", function(object, name) "OK")
structure(0, class = "zzz")@whatever
## [1] "OK"

Upvotes: 3

JDL
JDL

Reputation: 1654

R's documentation is somewhat confusing as to whether @ is already a generic or not: the help page for @ says it is, but it isn't listed on the internalGenerics page.

The @ operator has specific behaviour as well as (perhaps) being a generic. From the help page for @: "It is checked that object is an S4 object (see isS4), and it is an error to attempt to use @ on any other object." That would appear to rule out writing methods for S3 classes, though the documentation is unclear if this check happens before method dispatch (if there is any) or after (whence it could be skipped if you supplied a specific method for some S3 class).

You can implement what you want by completely redefining what @ is, along the line of the suggestion in comments:

`@.default` <- function(e1,e2) slot(e1,substitute(e2))

but there are two reasons not to do this:

1) As soon as someone loads your package, it supersedes the normal @ function, so if people call it with other S4 objects, they are getting your version rather than the R base version.

2) This version is considerably less efficient than the internal one, and because of (1) you have just forced your users to use it (unless they use the cumbersome construction base::"@"(e1,e2)). Efficiency may not matter to your use case, but it may matter to your users' other code that uses S4.

Practically, a reasonable compromise might be to define your own binary operator %@%, and have the default method call @. That is,

`%@%` <- function(e1,e2) slot(e1,substitute(e2))
setGeneric("%@%")

This is called in practice as follows:

> setClass("testClass",slots=c(a="character")) -> testClass
> x <- testClass(a="cheese")
> x %@% a
[1] "cheese"

Upvotes: 3

Related Questions