I am writing a package in {S7} and wish to follow the recommendation that classes exported from the package are exported with the package name, e.g. minrep::accr
, to avoid namespace conflicts.
I have run into an issue because I need to write a method which works both on an S7 object (let's say minrep::accr
) and on an array. I can't do this as an S7 method, because S7 doesn't have a class_array()
and so it looks for tsum.integer
which is obviously far too wide a scope. I can make it work with an S3 method if I don't set the package name (comment out line 23 in the example code), but that isn't following good practice (see above). If I do set the package name, then it looks for, say, tsum.minrep::accr
, which I do not know how to specify in a function name. What trick am I missing?
Minrep:
#' Generic for tsum
#' @param x Thing that needs summing
#' @export
#'
tsum <- function(x, ...) {
UseMethod("tsum", x)
}
#' tsum for arrays
#' @param x Array that needs summing
#' @export
#'
tsum.array <- function(x) {
rowSums(aperm(x, c(2, 1, 3)))
}
#' S7 class for an object with an accrual property that is an array
#' @slot accrual Array
#' @param accrual Array
#' @export
#'
accr <- S7::new_class("accr",
package = "minrep",
properties = list(
accrual = S7::class_integer
),
constructor = function(
accrual = S7::class_missing
) {
S7::new_object(
S7::S7_object(),
accrual = accrual
)
}
)
#' tsum for objects of class "accr"
#' @param x Object of class "accr"
#' @export
#'
tsum.accr <- function(x) {
tsum(x@accrual)
}
#' Do the thing
#' @param x 3D integer array
#' @export
#'
do_tsum <- function(x) {
a <- accr(x)
print("Treatment sum of array")
print(tsum(x))
print("Treatment sum of object")
tsum(a)
}
Desired output:
r$> do_tsum(array(1:24, 2:4))
[1] "Treatment sum of array"
[1] 84 100 116
[1] "Treatment sum of object"
[1] 84 100 116
The trick you are missing is that you can wrap the name of the tsum.minrep::accr
function in backticks:
`tsum.minrep::accr` <- function(x) {
tsum(x@accrual)
}
Which results in
do_tsum(array(1:24, 2:4))
#> [1] "Treatment sum of array"
#> [1] 84 100 116
#> [1] "Treatment sum of object"
#> [1] 84 100 116
If you insert a browser()
call as the first line of `tsum.minrep::accr`
you will see that this function is indeed dispatched by the generic when you call tsum(x)
The function name is non-standard and looks a little odd, but this is a necessary side-effect of how S7 classes are named, since they use the non-standard "package::function"
schema for class names.
The alternatives are
tsum.S7_object
as your method, which will be called on all S7 objects, and act conditionally within that function according to the specific manually-checked S7 class name. This kind of misses the whole point of generic dispatch, and to my nostrils gives a worse code smell than a backticked non-standard function name does.The backticked function seems like the most reasonable best-practice approach to me.