-
Notifications
You must be signed in to change notification settings - Fork 42
Description
While I was waiting on #353 and #365, I started playing with an alternative approach to group generics in a package I am working on. I think this approach may be simpler than the current proposal, and also offer several benefits.
The basic idea is to add a general mechanism by which people can define methods on sets of generics at once. This could be implemented within method<-, or using a separate function.
The simple (probably not robust) implementation I am currently using looks like this:
#' Define methods on a group of generics
#'
#' Takes a list of generic functions and defines the same function for each,
#' allowing use of `.Generic` in the function body to refer to the actual generic.
#' @param generics a list of [symbol]s (such as returned by [expression()]),
#' each of which should correspond to a generic function, or the S4 definition
#' of a group generic, such as [`Compare`].
#' @param signature A method signature (see [`method<-`])
#' @param value A function implementing each `generic` for the given `signature`.
#' In each method, the variable `.Generic` in the method's environment will
#' refer to the generic function.
#' @noRd
`group_method<-` = \(generics, signature, value) {
.generics = if (is(generics, "groupGenericFunction")) {
lapply(getGroupMembers(generics), as.symbol)
} else {
generics
}
for (generic in .generics) {
f = value
environment(f) = new.env(parent = environment(value))
environment(f)$.Generic = get(generic, mode = "function")
eval(bquote(
method(.(generic), signature) <<- f
))
}
invisible(generics)
}Then you can use any existing S4 group generic as follows (say on a toy numeric class, "MyNum"):
group_method(Compare, list(MyNum, MyNum)) = \(e1, e2) MyNum(value = .Generic(e1@value, e2@value))Since this generates all the specific methods (instead of creating a parallel dispatch on group generics), I think it makes dispatch easier to understand and easier to extend when creating class hierarchies.
It also avoids some of the ugliness common to S3 / S4 group generics code, like switch statements singling out one or two methods that operate differently. Instead, you would just define the specific methods you want to create as a separate group. e.g. if I just wanted to override inequalities, I might do:
inequalities = expression(`<`, `<=`, `>`, `>=`) # or probably new_group_generic(...) or something
group_method(inequalities, list(MyNum, MyNum)) = \(e1, e2) MyNum(value = .Generic(e1@value, e2@value))This also avoids the need for a method to look up the actual generic function inside the implementation, and allows users to create their own groups of generics as needed.