-
Notifications
You must be signed in to change notification settings - Fork 37
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
Explore group generics implementation #365
base: main
Are you sure you want to change the base?
Changes from all commits
01d7dfd
9269226
6c8b1c6
4fa13b7
61b1623
1665d7b
20fd844
b37da51
750722d
e50229e
8662f43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
#' 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"`. | ||
#' | ||
#' Use `find_base_generic()` to find the base generic that corresponds to the | ||
#' generic name. | ||
#' @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() { | ||
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_Complex <<- new_generic("Complex", "z", function(z, ..., .Generic) { | ||
S7_dispatch() | ||
}) | ||
|
||
S7_Summary <<- new_generic("Summary", "x", function(x, ..., na.rm = FALSE, .Generic) { | ||
S7_dispatch() | ||
}) | ||
} | ||
|
||
#' @export | ||
Math.S7_object <- function(x, ...) { | ||
tryCatch( | ||
return(S7_Math(x, ..., .Generic = .Generic)), | ||
S7_error_method_not_found = function(cnd) NULL | ||
) | ||
|
||
NextMethod() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to call |
||
} | ||
|
||
#' @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 = ", ") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,17 +16,24 @@ on_load_define_ops <- function() { | |
|
||
#' @export | ||
Ops.S7_object <- function(e1, e2) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this all live in |
||
# Try "specific" generic | ||
cnd <- tryCatch( | ||
return(base_ops[[.Generic]](e1, e2)), | ||
S7_error_method_not_found = function(cnd) cnd | ||
) | ||
|
||
if (S7_inherits(e1) && S7_inherits(e2)) { | ||
stop(cnd) | ||
} else { | ||
# Must call NextMethod() directly in the method, not wrapped in an | ||
# anonymous function. | ||
# Try group generic | ||
cnd <- tryCatch( | ||
return(S7_Ops(e1, e2, .Generic = .Generic)), | ||
S7_error_method_not_found = function(cnd) cnd | ||
) | ||
|
||
if (!S7_inherits(e1) || !S7_inherits(e2)) { | ||
# Fall back to base behaviour. Must call NextMethod() directly here, not | ||
# wrapped in an anonymous function. | ||
NextMethod() | ||
} else { | ||
stop(cnd) | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ reference: | |
- method_explain | ||
- super | ||
- S7_class | ||
- S7_group_generics | ||
|
||
- title: Packages | ||
desc: > | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
test_that("can provide Math group generic", { | ||
local_methods(S7_Math) | ||
foo1 <- new_class("foo1", properties = list(x = class_double, y = class_double)) | ||
foo2 <- new_class("foo2", class_double) | ||
|
||
# base behaviour | ||
expect_snapshot(abs(foo1(-1, 2)), error = TRUE) | ||
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))) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you also need to check for a "specific" generic call here?