From 01d7dfd2116673634c1ad0008fdc7266df1ae2b0 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Sat, 16 Sep 2023 11:10:48 -0500 Subject: [PATCH 1/9] First pass at group generics --- NAMESPACE | 3 +++ R/method-group.R | 40 ++++++++++++++++++++++++++++++ R/method-ops.R | 5 ---- R/zzz.R | 1 + tests/testthat/test-method-group.R | 14 +++++++++++ 5 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 R/method-group.R create mode 100644 tests/testthat/test-method-group.R diff --git a/NAMESPACE b/NAMESPACE index 20490a91..638b629d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,7 +8,10 @@ S3method("[<-",S7_object) S3method("[[",S7_object) S3method("[[<-",S7_object) S3method("|",S7_class) +S3method(Complex,S7_object) +S3method(Math,S7_object) S3method(Ops,S7_object) +S3method(Summary,S7_object) S3method(c,S7_class) S3method(print,S7_S3_class) S3method(print,S7_any) diff --git a/R/method-group.R b/R/method-group.R new file mode 100644 index 00000000..f0b9ec22 --- /dev/null +++ b/R/method-group.R @@ -0,0 +1,40 @@ +group_generic_Math <- NULL +group_generic_Ops <- NULL +group_generic_Complex <- NULL +group_generic_Sumary <- NULL + +on_load_define_group_generics <- function() { + group_generic_Math <<- new_generic("Math", "x") + group_generic_Ops <<- new_generic("Ops", c("e1", "e2")) + group_generic_Complex <<- new_generic("Complex", "z") + group_generic_Summary <<- new_generic("Summary", "x", function(x, ..., na.rm = FALSE) { + S7_dispatch() + }) +} + +#' @export +Math.S7_object <- function(x, ...) { + group_generic_Math(x, ..., .Generic = .Generic) +} + +#' @export +Ops.S7_object <- function(e1, e2) { + dispatch <- list(obj_dispatch(e1), obj_dispatch(e2)) + specific <- .Call(method_, base_ops[[.Generic]], dispatch, environment(), FALSE) + + if (!is.null(specific)) { + specific(e1, e2) + } else { + group_generic_Ops(e1, e2, .Generic = match.fun(.Generic)) + } +} + +#' @export +Complex.S7_object <- function(z) { + group_generic_Complex(z, .Generic = .Generic) +} + +#' @export +Summary.S7_object <- function(..., na.rm = FALSE, .Generic) { + group_generic_Summary(..., na.rm = TRUE, .Generic = .Generic) +} diff --git a/R/method-ops.R b/R/method-ops.R index 3ebbda22..879eb89c 100644 --- a/R/method-ops.R +++ b/R/method-ops.R @@ -14,11 +14,6 @@ on_load_define_ops <- function() { ) } -#' @export -Ops.S7_object <- function(e1, e2) { - base_ops[[.Generic]](e1, e2) -} - #' @rawNamespace if (getRversion() >= "4.3.0") S3method(chooseOpsMethod, S7_object) chooseOpsMethod.S7_object <- function(x, y, mx, my, cl, reverse) TRUE diff --git a/R/zzz.R b/R/zzz.R index 8e4b7b34..9d8d16af 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -120,6 +120,7 @@ methods::setOldClass(c("S7_method", "function", "S7_object")) .onLoad <- function(...) { on_load_make_convert_generic() + on_load_define_group_generics() on_load_define_matrixOps() on_load_define_ops() on_load_define_or_methods() diff --git a/tests/testthat/test-method-group.R b/tests/testthat/test-method-group.R new file mode 100644 index 00000000..d307d81c --- /dev/null +++ b/tests/testthat/test-method-group.R @@ -0,0 +1,14 @@ +test_that("specific method overrides group generic", { + foo <- new_class("foo", class_integer) + + method(`+`, list(foo, foo)) <- function(e1, e2) { + foo(S7_data(e1) + S7_data(e2) + 100L) + } + method(group_generic_Ops, list(foo, foo)) <- function(e1, e2, .Generic) { + foo(.Generic(S7_data(e1), S7_data(e2))) + } + + expect_equal(foo(1L) * foo(1:5), foo(1:5)) + expect_equal(foo(1L) + foo(1:5), foo(1:5 + 101L)) + +}) From 6c8b1c6b375acad983b76b273d083886526d6e6b Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Thu, 23 Nov 2023 09:19:13 -0600 Subject: [PATCH 2/9] Move Ops dispatch back to method-ops since it's special --- R/method-group.R | 12 ------------ R/method-ops.R | 12 ++++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/R/method-group.R b/R/method-group.R index f0b9ec22..dc4b4446 100644 --- a/R/method-group.R +++ b/R/method-group.R @@ -17,18 +17,6 @@ Math.S7_object <- function(x, ...) { group_generic_Math(x, ..., .Generic = .Generic) } -#' @export -Ops.S7_object <- function(e1, e2) { - dispatch <- list(obj_dispatch(e1), obj_dispatch(e2)) - specific <- .Call(method_, base_ops[[.Generic]], dispatch, environment(), FALSE) - - if (!is.null(specific)) { - specific(e1, e2) - } else { - group_generic_Ops(e1, e2, .Generic = match.fun(.Generic)) - } -} - #' @export Complex.S7_object <- function(z) { group_generic_Complex(z, .Generic = .Generic) diff --git a/R/method-ops.R b/R/method-ops.R index 879eb89c..ab011803 100644 --- a/R/method-ops.R +++ b/R/method-ops.R @@ -14,6 +14,18 @@ on_load_define_ops <- function() { ) } +#' @export +Ops.S7_object <- function(e1, e2) { + dispatch <- list(obj_dispatch(e1), obj_dispatch(e2)) + specific <- .Call(method_, base_ops[[.Generic]], dispatch, environment(), FALSE) + + if (!is.null(specific)) { + specific(e1, e2) + } else { + group_generic_Ops(e1, e2, .Generic = match.fun(.Generic)) + } +} + #' @rawNamespace if (getRversion() >= "4.3.0") S3method(chooseOpsMethod, S7_object) chooseOpsMethod.S7_object <- function(x, y, mx, my, cl, reverse) TRUE From 4fa13b72ff307781567289dfaa6656df4235481c Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Thu, 23 Nov 2023 09:23:19 -0600 Subject: [PATCH 3/9] Fix typo --- R/method-group.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/method-group.R b/R/method-group.R index dc4b4446..1763b05a 100644 --- a/R/method-group.R +++ b/R/method-group.R @@ -1,7 +1,7 @@ group_generic_Math <- NULL group_generic_Ops <- NULL group_generic_Complex <- NULL -group_generic_Sumary <- NULL +group_generic_Summary <- NULL on_load_define_group_generics <- function() { group_generic_Math <<- new_generic("Math", "x") From 1665d7b5b80b79fd6fa98134825114a45093163b Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Mon, 4 Dec 2023 08:19:26 -0600 Subject: [PATCH 4/9] Implement math generic --- NAMESPACE | 2 - R/method-group.R | 53 +++++++++++++++++++-------- R/method-ops.R | 3 +- tests/testthat/_snaps/method-group.md | 8 ++++ tests/testthat/test-method-group.R | 25 ++++++++----- tests/testthat/test-method-ops.R | 14 +++++++ 6 files changed, 76 insertions(+), 29 deletions(-) create mode 100644 tests/testthat/_snaps/method-group.md diff --git a/NAMESPACE b/NAMESPACE index 45eddf32..1e62c526 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,11 +8,9 @@ S3method("[<-",S7_object) S3method("[[",S7_object) S3method("[[<-",S7_object) S3method("|",S7_class) -S3method(Complex,S7_object) S3method(Math,S7_object) S3method(Ops,S7_object) S3method(Ops,S7_super) -S3method(Summary,S7_object) S3method(c,S7_class) S3method(print,S7_S3_class) S3method(print,S7_any) diff --git a/R/method-group.R b/R/method-group.R index 1763b05a..3b04f0be 100644 --- a/R/method-group.R +++ b/R/method-group.R @@ -4,25 +4,46 @@ group_generic_Complex <- NULL group_generic_Summary <- NULL on_load_define_group_generics <- function() { - group_generic_Math <<- new_generic("Math", "x") - group_generic_Ops <<- new_generic("Ops", c("e1", "e2")) - group_generic_Complex <<- new_generic("Complex", "z") - group_generic_Summary <<- new_generic("Summary", "x", function(x, ..., na.rm = FALSE) { - S7_dispatch() - }) -} + group_generic_Math <<- new_generic( + "Math", + "x", + function(x, ..., .Generic) { + S7_dispatch() + } + ) -#' @export -Math.S7_object <- function(x, ...) { - group_generic_Math(x, ..., .Generic = .Generic) -} + group_generic_Ops <<- new_generic( + "Ops", + c("e1", "e2"), + function(e1, e2, ..., .Generic) { + S7_dispatch() + } + ) -#' @export -Complex.S7_object <- function(z) { - group_generic_Complex(z, .Generic = .Generic) + group_generic_Complex <<- new_generic( + "Complex", + "z", + function(z, ..., .Generic) { + S7_dispatch() + } + ) + + group_generic_Summary <<- new_generic( + "Summary", + "x", + function(x, ..., na.rm = FALSE, .Generic) { + S7_dispatch() + } + ) } #' @export -Summary.S7_object <- function(..., na.rm = FALSE, .Generic) { - group_generic_Summary(..., na.rm = TRUE, .Generic = .Generic) +Math.S7_object <- function(x, ...) { + generic_fun <- get(.Generic, mode = "function", envir = baseenv()) + tryCatch( + return(group_generic_Math(x, ..., .Generic = generic_fun)), + S7_error_method_not_found = function(cnd) NULL + ) + + NextMethod() } diff --git a/R/method-ops.R b/R/method-ops.R index 08169bc9..dcaf20e1 100644 --- a/R/method-ops.R +++ b/R/method-ops.R @@ -23,8 +23,9 @@ Ops.S7_object <- function(e1, e2) { ) # Try group generic + generic_fun <- get(.Generic, mode = "function", envir = baseenv()) cnd <- tryCatch( - return(group_generic_Ops(e1, e2, .Generic = match.fun(.Generic))), + return(group_generic_Ops(e1, e2, .Generic = generic_fun)), S7_error_method_not_found = function(cnd) cnd ) diff --git a/tests/testthat/_snaps/method-group.md b/tests/testthat/_snaps/method-group.md new file mode 100644 index 00000000..a827eeb3 --- /dev/null +++ b/tests/testthat/_snaps/method-group.md @@ -0,0 +1,8 @@ +# can provide Math group generic + + Code + abs(foo1(-1, 2)) + Condition + Error in `abs.default()`: + ! non-numeric argument to mathematical function + diff --git a/tests/testthat/test-method-group.R b/tests/testthat/test-method-group.R index d307d81c..63076363 100644 --- a/tests/testthat/test-method-group.R +++ b/tests/testthat/test-method-group.R @@ -1,14 +1,19 @@ -test_that("specific method overrides group generic", { - foo <- new_class("foo", class_integer) +test_that("can provide Math group generic", { + local_methods(group_generic_Math) + foo1 <- new_class("foo1", properties = list(x = class_double, y = class_double)) + foo2 <- new_class("foo2", class_double) - method(`+`, list(foo, foo)) <- function(e1, e2) { - foo(S7_data(e1) + S7_data(e2) + 100L) - } - method(group_generic_Ops, list(foo, foo)) <- function(e1, e2, .Generic) { - foo(.Generic(S7_data(e1), S7_data(e2))) - } + # base behaviour + expect_snapshot(abs(foo1(-1, 2)), error = TRUE) + expect_equal(abs(foo2(c(-1, 2))), foo2(c(1, 2))) - expect_equal(foo(1L) * foo(1:5), foo(1:5)) - expect_equal(foo(1L) + foo(1:5), foo(1:5 + 101L)) + method(group_generic_Math, foo1) <- function(x, ..., .Generic) { + foo1(.Generic(x@x, ...), .Generic(x@y, ...)) + } + expect_equal(abs(foo1(-1, 2)), foo1(1, 2)) + method(group_generic_Math, foo2) <- function(x, ..., .Generic) { + foo2(.Generic(S7_data(x, ...))) + } + expect_equal(abs(foo2(c(-1, 2))), foo2(c(1, 2))) }) diff --git a/tests/testthat/test-method-ops.R b/tests/testthat/test-method-ops.R index d825b65e..c63fb3fb 100644 --- a/tests/testthat/test-method-ops.R +++ b/tests/testthat/test-method-ops.R @@ -96,6 +96,20 @@ test_that("Ops generics falls back to base behaviour", { expect_equal(1:2 + foo(1), "numeric-foo") }) +test_that("specific method overrides group generic", { + foo <- new_class("foo", class_integer) + + method(`+`, list(foo, foo)) <- function(e1, e2) { + foo(S7_data(e1) + S7_data(e2) + 100L) + } + method(group_generic_Ops, list(foo, foo)) <- function(e1, e2, .Generic) { + foo(.Generic(S7_data(e1), S7_data(e2))) + } + + expect_equal(foo(1L) * foo(1:5), foo(1:5)) + expect_equal(foo(1L) + foo(1:5), foo(1:5 + 1L + 100L)) +}) + test_that("`%*%` dispatches to S7 methods", { skip_if(getRversion() < "4.3") local_methods(base_ops[["+"]]) From 20fd8442d239b8284efbb9dce3f2f37daf9d7c6a Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Mon, 4 Dec 2023 09:04:51 -0600 Subject: [PATCH 5/9] Better names; start on docs --- NAMESPACE | 4 ++ R/method-group.R | 62 +++++++++++++++++++++++++----- R/method-ops.R | 2 +- man/S7_group_generics.Rd | 46 ++++++++++++++++++++++ tests/testthat/test-method-group.R | 6 +-- tests/testthat/test-method-ops.R | 4 +- 6 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 man/S7_group_generics.Rd diff --git a/NAMESPACE b/NAMESPACE index 1e62c526..cd36977f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -38,6 +38,10 @@ export("method<-") export("prop<-") export("props<-") export(S4_register) +export(S7_Complex) +export(S7_Math) +export(S7_Ops) +export(S7_Summary) export(S7_class) export(S7_data) export(S7_dispatch) diff --git a/R/method-group.R b/R/method-group.R index 3b04f0be..c6683f71 100644 --- a/R/method-group.R +++ b/R/method-group.R @@ -1,10 +1,49 @@ -group_generic_Math <- NULL -group_generic_Ops <- NULL -group_generic_Complex <- NULL -group_generic_Summary <- NULL +#' S7 Group Generics +#' +#' Group generics allow you to implement methods for many generics at once. +#' You cannot call a group generic directly; instead it is called automatically +#' by members of the group if a more specific method is not found. For example, +#' if you define a method for the `S7_Math` group generic, it will be called +#' when you call `abs()`, `sign()`, `sqrt()`, and many other similar generics +#' (see below for a complete list). +#' +#' @param x,z,e1,e2 Objects used for dispatch. +#' @param ...,na.rm Additional arguments passed to methods. +#' @param .Generic The name of the generic being dispatched on, i.e. if you've +#' defined a method for `S7_Math` and the user calls `abs()` then `.Generic` +#' will be `"abs"`. +#' @details +#' # Methods +#' +#' The group generics contain the following methods: +#' +#' * `Ops`: `r group_generics_md("Ops")` +#' * `Math`: `r group_generics_md("Math")` +#' * `Summary`: `r group_generics_md("Summary")` +#' * `Complex`: `r group_generics_md("Complex")` +#' * `matrixOps`: `r group_generics_md("matrixOps")` +#' +#' @name S7_group_generics +NULL + +#' @export +#' @rdname S7_group_generics +S7_Math <- NULL + +#' @export +#' @rdname S7_group_generics +S7_Ops <- NULL + +#' @export +#' @rdname S7_group_generics +S7_Complex <- NULL + +#' @export +#' @rdname S7_group_generics +S7_Summary <- NULL on_load_define_group_generics <- function() { - group_generic_Math <<- new_generic( + S7_Math <<- new_generic( "Math", "x", function(x, ..., .Generic) { @@ -12,7 +51,7 @@ on_load_define_group_generics <- function() { } ) - group_generic_Ops <<- new_generic( + S7_Ops <<- new_generic( "Ops", c("e1", "e2"), function(e1, e2, ..., .Generic) { @@ -20,7 +59,7 @@ on_load_define_group_generics <- function() { } ) - group_generic_Complex <<- new_generic( + S7_Complex <<- new_generic( "Complex", "z", function(z, ..., .Generic) { @@ -28,7 +67,7 @@ on_load_define_group_generics <- function() { } ) - group_generic_Summary <<- new_generic( + S7_Summary <<- new_generic( "Summary", "x", function(x, ..., na.rm = FALSE, .Generic) { @@ -41,9 +80,14 @@ on_load_define_group_generics <- function() { Math.S7_object <- function(x, ...) { generic_fun <- get(.Generic, mode = "function", envir = baseenv()) tryCatch( - return(group_generic_Math(x, ..., .Generic = generic_fun)), + return(S7_Math(x, ..., .Generic = generic_fun)), S7_error_method_not_found = function(cnd) NULL ) NextMethod() } + + +group_generics_md <- function(name) { + paste0("`", group_generics()[[name]], "`", collapse = ", ") +} diff --git a/R/method-ops.R b/R/method-ops.R index dcaf20e1..cbc040df 100644 --- a/R/method-ops.R +++ b/R/method-ops.R @@ -25,7 +25,7 @@ Ops.S7_object <- function(e1, e2) { # Try group generic generic_fun <- get(.Generic, mode = "function", envir = baseenv()) cnd <- tryCatch( - return(group_generic_Ops(e1, e2, .Generic = generic_fun)), + return(S7_Ops(e1, e2, .Generic = generic_fun)), S7_error_method_not_found = function(cnd) cnd ) diff --git a/man/S7_group_generics.Rd b/man/S7_group_generics.Rd new file mode 100644 index 00000000..b35465a9 --- /dev/null +++ b/man/S7_group_generics.Rd @@ -0,0 +1,46 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/method-group.R +\name{S7_group_generics} +\alias{S7_group_generics} +\alias{S7_Math} +\alias{S7_Ops} +\alias{S7_Complex} +\alias{S7_Summary} +\title{S7 Group Generics} +\usage{ +S7_Math(x, ..., .Generic) + +S7_Ops(e1, e2, ..., .Generic) + +S7_Complex(z, ..., .Generic) + +S7_Summary(x, ..., na.rm = FALSE, .Generic) +} +\arguments{ +\item{x, z, e1, e2}{Objects used for dispatch.} + +\item{..., na.rm}{Additional arguments passed to methods.} + +\item{.Generic}{The name of the generic being dispatched on, i.e. if you've +defined a method for \code{S7_Math} and the user calls \code{abs()} then \code{.Generic} +will be \code{"abs"}.} +} +\description{ +Group generics allow you to implement methods for many generics at once. +You cannot call a group generic directly; instead it is called automatically +by members of the group if a more specific method is not found. For example, +if you define a method for the \code{S7_Math} group generic, it will be called +when you call \code{abs()}, \code{sign()}, \code{sqrt()}, and many other similar generics +(see below for a complete list). +} +\section{Methods}{ +The group generics contain the following methods: +\itemize{ +\item \code{Ops}: \code{+}, \code{-}, \code{*}, \code{^}, \code{\%\%}, \code{\%/\%}, \code{/}, \code{==}, \code{>}, \code{<}, \code{!=}, \code{<=}, \code{>=}, \code{&}, \code{|} +\item \code{Math}: \code{abs}, \code{sign}, \code{sqrt}, \code{ceiling}, \code{floor}, \code{trunc}, \code{cummax}, \code{cummin}, \code{cumprod}, \code{cumsum}, \code{exp}, \code{expm1}, \code{log}, \code{log10}, \code{log2}, \code{log1p}, \code{cos}, \code{cosh}, \code{sin}, \code{sinh}, \code{tan}, \code{tanh}, \code{acos}, \code{acosh}, \code{asin}, \code{asinh}, \code{atan}, \code{atanh}, \code{cospi}, \code{sinpi}, \code{tanpi}, \code{gamma}, \code{lgamma}, \code{digamma}, \code{trigamma}, \code{round}, \code{signif} +\item \code{Summary}: \code{max}, \code{min}, \code{range}, \code{prod}, \code{sum}, \code{any}, \code{all} +\item \code{Complex}: \code{Arg}, \code{Conj}, \code{Im}, \code{Mod}, \code{Re} +\item \code{matrixOps}: \code{\%*\%} +} +} + diff --git a/tests/testthat/test-method-group.R b/tests/testthat/test-method-group.R index 63076363..ba983b0a 100644 --- a/tests/testthat/test-method-group.R +++ b/tests/testthat/test-method-group.R @@ -1,5 +1,5 @@ test_that("can provide Math group generic", { - local_methods(group_generic_Math) + local_methods(S7_Math) foo1 <- new_class("foo1", properties = list(x = class_double, y = class_double)) foo2 <- new_class("foo2", class_double) @@ -7,12 +7,12 @@ test_that("can provide Math group generic", { expect_snapshot(abs(foo1(-1, 2)), error = TRUE) expect_equal(abs(foo2(c(-1, 2))), foo2(c(1, 2))) - method(group_generic_Math, foo1) <- function(x, ..., .Generic) { + method(S7_Math, foo1) <- function(x, ..., .Generic) { foo1(.Generic(x@x, ...), .Generic(x@y, ...)) } expect_equal(abs(foo1(-1, 2)), foo1(1, 2)) - method(group_generic_Math, foo2) <- function(x, ..., .Generic) { + method(S7_Math, foo2) <- function(x, ..., .Generic) { foo2(.Generic(S7_data(x, ...))) } expect_equal(abs(foo2(c(-1, 2))), foo2(c(1, 2))) diff --git a/tests/testthat/test-method-ops.R b/tests/testthat/test-method-ops.R index c63fb3fb..8b9e69f7 100644 --- a/tests/testthat/test-method-ops.R +++ b/tests/testthat/test-method-ops.R @@ -97,12 +97,14 @@ test_that("Ops generics falls back to base behaviour", { }) test_that("specific method overrides group generic", { + local_methods(base_ops[["+"]], S7_Ops) + foo <- new_class("foo", class_integer) method(`+`, list(foo, foo)) <- function(e1, e2) { foo(S7_data(e1) + S7_data(e2) + 100L) } - method(group_generic_Ops, list(foo, foo)) <- function(e1, e2, .Generic) { + method(S7_Ops, list(foo, foo)) <- function(e1, e2, .Generic) { foo(.Generic(S7_data(e1), S7_data(e2))) } From b37da512b28a516a77387f4dd02b7fede3cfe15a Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Mon, 4 Dec 2023 09:09:29 -0600 Subject: [PATCH 6/9] Pass name; implement `find_base_generic()` --- NAMESPACE | 1 + R/method-group.R | 12 ++++++++++-- man/S7_group_generics.Rd | 8 +++++++- tests/testthat/test-method-group.R | 2 ++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index cd36977f..21c1d925 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -69,6 +69,7 @@ export(class_numeric) export(class_raw) export(class_vector) export(convert) +export(find_base_generic) export(method) export(method_explain) export(methods_register) diff --git a/R/method-group.R b/R/method-group.R index c6683f71..f34c0594 100644 --- a/R/method-group.R +++ b/R/method-group.R @@ -12,6 +12,9 @@ #' @param .Generic The name of the generic being dispatched on, i.e. if you've #' defined a method for `S7_Math` and the user calls `abs()` then `.Generic` #' will be `"abs"`. +#' +#' Use `find_base_generic()` to find the base generic that corresponds to the +#' generic name. #' @details #' # Methods #' @@ -78,15 +81,20 @@ on_load_define_group_generics <- function() { #' @export Math.S7_object <- function(x, ...) { - generic_fun <- get(.Generic, mode = "function", envir = baseenv()) tryCatch( - return(S7_Math(x, ..., .Generic = generic_fun)), + return(S7_Math(x, ..., .Generic = .Generic)), S7_error_method_not_found = function(cnd) NULL ) NextMethod() } +#' @export +#' @rdname S7_group_generics +find_base_generic <- function(.Generic) { + get(.Generic, mode = "function", envir = baseenv()) +} + group_generics_md <- function(name) { paste0("`", group_generics()[[name]], "`", collapse = ", ") diff --git a/man/S7_group_generics.Rd b/man/S7_group_generics.Rd index b35465a9..5cd47904 100644 --- a/man/S7_group_generics.Rd +++ b/man/S7_group_generics.Rd @@ -6,6 +6,7 @@ \alias{S7_Ops} \alias{S7_Complex} \alias{S7_Summary} +\alias{find_base_generic} \title{S7 Group Generics} \usage{ S7_Math(x, ..., .Generic) @@ -15,6 +16,8 @@ S7_Ops(e1, e2, ..., .Generic) S7_Complex(z, ..., .Generic) S7_Summary(x, ..., na.rm = FALSE, .Generic) + +find_base_generic(.Generic) } \arguments{ \item{x, z, e1, e2}{Objects used for dispatch.} @@ -23,7 +26,10 @@ S7_Summary(x, ..., na.rm = FALSE, .Generic) \item{.Generic}{The name of the generic being dispatched on, i.e. if you've defined a method for \code{S7_Math} and the user calls \code{abs()} then \code{.Generic} -will be \code{"abs"}.} +will be \code{"abs"}. + +Use \code{find_base_generic()} to find the base generic that corresponds to the +generic name.} } \description{ Group generics allow you to implement methods for many generics at once. diff --git a/tests/testthat/test-method-group.R b/tests/testthat/test-method-group.R index ba983b0a..79682670 100644 --- a/tests/testthat/test-method-group.R +++ b/tests/testthat/test-method-group.R @@ -8,11 +8,13 @@ test_that("can provide Math group generic", { expect_equal(abs(foo2(c(-1, 2))), foo2(c(1, 2))) method(S7_Math, foo1) <- function(x, ..., .Generic) { + .Generic <- find_base_generic(.Generic) foo1(.Generic(x@x, ...), .Generic(x@y, ...)) } expect_equal(abs(foo1(-1, 2)), foo1(1, 2)) method(S7_Math, foo2) <- function(x, ..., .Generic) { + .Generic <- find_base_generic(.Generic) foo2(.Generic(S7_data(x, ...))) } expect_equal(abs(foo2(c(-1, 2))), foo2(c(1, 2))) From 750722dcef2d1624adeb9774dc3d2de418a26ed9 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Mon, 4 Dec 2023 09:09:43 -0600 Subject: [PATCH 7/9] Add to reference index --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index 9406eeef..e1ae0757 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -30,6 +30,7 @@ reference: - method_explain - super - S7_class + - S7_group_generics - title: Packages desc: > From e50229ee7f6db130141add119e995356a5cc9452 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Mon, 4 Dec 2023 09:12:07 -0600 Subject: [PATCH 8/9] Update ops --- R/method-ops.R | 5 ++--- tests/testthat/test-method-ops.R | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/method-ops.R b/R/method-ops.R index cbc040df..39d5f1f1 100644 --- a/R/method-ops.R +++ b/R/method-ops.R @@ -23,14 +23,13 @@ Ops.S7_object <- function(e1, e2) { ) # Try group generic - generic_fun <- get(.Generic, mode = "function", envir = baseenv()) cnd <- tryCatch( - return(S7_Ops(e1, e2, .Generic = generic_fun)), + return(S7_Ops(e1, e2, .Generic = .Generic)), S7_error_method_not_found = function(cnd) cnd ) if (!S7_inherits(e1) || !S7_inherits(e2)) { - # Fallback to base behaviour. Must call NextMethod() directly here, not + # Fall back to base behaviour. Must call NextMethod() directly here, not # wrapped in an anonymous function. NextMethod() } else { diff --git a/tests/testthat/test-method-ops.R b/tests/testthat/test-method-ops.R index 8b9e69f7..4e0670da 100644 --- a/tests/testthat/test-method-ops.R +++ b/tests/testthat/test-method-ops.R @@ -105,6 +105,7 @@ test_that("specific method overrides group generic", { foo(S7_data(e1) + S7_data(e2) + 100L) } method(S7_Ops, list(foo, foo)) <- function(e1, e2, .Generic) { + .Generic <- find_base_generic(.Generic) foo(.Generic(S7_data(e1), S7_data(e2))) } From 8662f43176c82ec73c30a2a3930c1744b3a13546 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Mon, 4 Dec 2023 09:14:15 -0600 Subject: [PATCH 9/9] Squish onto each line --- R/method-group.R | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/R/method-group.R b/R/method-group.R index f34c0594..496c74f3 100644 --- a/R/method-group.R +++ b/R/method-group.R @@ -46,37 +46,21 @@ S7_Complex <- NULL S7_Summary <- NULL on_load_define_group_generics <- function() { - S7_Math <<- new_generic( - "Math", - "x", - function(x, ..., .Generic) { - S7_dispatch() - } - ) + S7_Math <<- new_generic("Math", "x", function(x, ..., .Generic) { + S7_dispatch() + }) - S7_Ops <<- new_generic( - "Ops", - c("e1", "e2"), - function(e1, e2, ..., .Generic) { - S7_dispatch() - } - ) + S7_Ops <<- new_generic("Ops", c("e1", "e2"), function(e1, e2, ..., .Generic) { + S7_dispatch() + }) - S7_Complex <<- new_generic( - "Complex", - "z", - function(z, ..., .Generic) { - S7_dispatch() - } - ) + S7_Complex <<- new_generic("Complex", "z", function(z, ..., .Generic) { + S7_dispatch() + }) - S7_Summary <<- new_generic( - "Summary", - "x", - function(x, ..., na.rm = FALSE, .Generic) { - S7_dispatch() - } - ) + S7_Summary <<- new_generic("Summary", "x", function(x, ..., na.rm = FALSE, .Generic) { + S7_dispatch() + }) } #' @export