Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom group generics (and maybe simpler group generics) #506

Open
mjskay opened this issue Nov 25, 2024 · 0 comments
Open

Custom group generics (and maybe simpler group generics) #506

mjskay opened this issue Nov 25, 2024 · 0 comments

Comments

@mjskay
Copy link

mjskay commented Nov 25, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant