Skip to content

Commit

Permalink
Syntax sugar for unions
Browse files Browse the repository at this point in the history
Fixes #224
  • Loading branch information
hadley committed Apr 17, 2023
1 parent 4c99312 commit 850c18a
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 7 deletions.
7 changes: 7 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ S3method("[",S7_object)
S3method("[<-",S7_object)
S3method("[[",S7_object)
S3method("[[<-",S7_object)
S3method("|",ClassUnionRepresentation)
S3method("|",S7_S3_class)
S3method("|",S7_base_class)
S3method("|",S7_class)
S3method("|",S7_union)
S3method("|",classGeneratorFunction)
S3method("|",classRepresentation)
S3method(Ops,S7_object)
S3method(c,S7_class)
S3method(print,S7_S3_class)
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## March 2023

* Can use `|` to create unions from S7 classes (#224).

* Can no longer subclass an environment via `class_environment` because we
need to think the consequences of this behaviour through more fully (#253).

Expand Down
26 changes: 23 additions & 3 deletions R/union.R
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#' Define a class union
#'
#' @description
#' A class union represents a list of possible classes. It can be used in two
#' A class union represents a list of possible classes. You can create it
#' with `new_union(a, b, c)` or `a | b | c`. Unions can be used in two
#' places:
#'
#' * To allow a property to be one of a set of classes,
#' `new_property(new_union("integer", Range))`.
#' `new_property(class_integer | Range)`.
#'
#' * As a convenient short-hand to define methods for multiple classes.
#' `method(foo, new_union(X, Y)) <- f` is short-hand for
#' `method(foo, X | Y) <- f` is short-hand for
#' `method(foo, X) <- f; method(foo, Y) <- foo`
#'
#' S7 includes built-in unions for "numeric" (integer and double vectors),
Expand All @@ -21,6 +22,8 @@
#' @examples
#' logical_or_character <- new_union(class_logical, class_character)
#' logical_or_character
#' # or with shortcut syntax
#' logical_or_character <- class_logical | class_character
#'
#' Foo <- new_class("Foo", properties = list(x = logical_or_character))
#' Foo(x = TRUE)
Expand All @@ -41,6 +44,23 @@ new_union <- function(...) {
out
}

#' @export
`|.S7_class` <- function(e1, e2) {
new_union(e1, e2)
}
#' @export
`|.S7_union` <- `|.S7_class`
#' @export
`|.S7_base_class` <- `|.S7_class`
#' @export
`|.S7_S3_class` <- `|.S7_class`
#' @export
`|.classGeneratorFunction` <- `|.S7_class`
#' @export
`|.ClassUnionRepresentation` <- `|.S7_class`
#' @export
`|.classRepresentation` <- `|.S7_class`

is_union <- function(x) inherits(x, "S7_union")

#' @export
Expand Down
9 changes: 6 additions & 3 deletions man/new_union.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 20 additions & 1 deletion tests/testthat/test-union.R
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,26 @@ test_that("base unions display as expected", {
})

test_that("can construct from S3 and S4 classes", {
S4_union <- methods::setClass("S4_union")
S4_union <- methods::setClass("S4_union", where = globalenv())
on.exit(S4_remove_classes("S4_union"))

u <- new_union(class_factor, S4_union)
expect_equal(u$classes, list(class_factor, getClass("S4_union")))
})

test_that("can construct with |", {
foo <- new_class("foo")
Foo1 <- setClass("Foo1", slots = list("x" = "numeric"), where = globalenv())
Foo2 <- setClass("Foo2", slots = list("x" = "numeric"), where = globalenv())
Foo3 <- setClassUnion("Foo3", c("Foo1", "Foo2"), where = globalenv())
on.exit(S4_remove_classes(c("Foo1", "Foo2", "Foo3")))

expect_equal(class_integer | class_double, class_numeric)
expect_equal(class_integer | class_numeric, class_numeric)
expect_equal(class_integer | class_factor, new_union(class_integer, class_factor))
expect_equal(class_integer | foo, new_union(class_integer, foo))
expect_equal(class_integer | Foo1, new_union(class_integer, Foo1))
expect_equal(class_integer | getClass("Foo1"), new_union(class_integer, Foo1))
expect_equal(class_integer | Foo3, new_union(class_integer, Foo3))
expect_equal(class_integer | getClass("Foo3"), new_union(class_integer, Foo3))
})

0 comments on commit 850c18a

Please sign in to comment.