Skip to content

Commit eb1092b

Browse files
committed
doc updates
1 parent 6667ffc commit eb1092b

6 files changed

Lines changed: 205 additions & 117 deletions

File tree

R/S4.R

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
#' an S7 class that does not extend an S4 class, or an S3 class created by
55
#' [new_S3_class()], you need to call `S4_register()` once. Classes created by
66
#' [new_class()] with an S4 parent are registered automatically.
7-
#' Use `S4_register_contains()` when you want an S4 class to extend an S7 class
8-
#' with `contains=`. This registers the S7 class as an old class with known
9-
#' attributes so that S7 properties are represented as S4 slots.
7+
#'
8+
#' @section Details:
9+
#' ```{r child = "man/rmd/S4-registration.Rmd"}
10+
#' ```
1011
#'
1112
#' @param class An S7 class created with [new_class()], or, for
1213
#' `S4_register()` only, an S3 class created with [new_S3_class()] or an S7

R/class.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
#' with this name, i.e. `Foo <- new_class("Foo")`. This object both represents
1515
#' the class and is used to construct new instances of the class.
1616
#' @param parent The parent class to inherit behavior from.
17-
#' There are three options:
17+
#' There are four options:
1818
#'
1919
#' * An S7 class, like [S7_object].
20+
#' * An S4 class, like the result of [methods::getClass()].
2021
#' * An S3 class wrapped by [new_S3_class()].
2122
#' * A base type, like [class_logical], [class_integer], etc.
2223
#' @param package Package name. This is automatically resolved if the class is

man/S4_register.Rd

Lines changed: 46 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/new_class.Rd

Lines changed: 55 additions & 54 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/rmd/S4-compatibility.Rmd

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,63 @@
1-
When an S7 class extends an S4 class, `new_class()` automatically registers the
2-
new class with S4. After that, an S4 generic with a method for `Parent` will
3-
dispatch on a `Child` instance:
1+
`new_class()` can use an S4 class as its parent. The S4 class can be supplied
2+
as a class definition, such as `methods::getClass("Parent")`, or as a class
3+
generator returned by `methods::setClass()`. The S4 parent slots become S7
4+
properties on the new S7 class.
5+
6+
When an S7 class extends an S4 class, `new_class()` automatically registers an
7+
S4 old class for the S7 class. This lets S4 generics dispatch through the S4
8+
parent:
49

510
```r
6-
setMethod("g", "Parent", function(x) ...)
7-
g(child) # uses Parent method if no more specific Child method exists
8-
```
11+
methods::setClass("Parent", slots = list(x = "numeric"))
12+
Child <- new_class("Child", methods::getClass("Parent"),
13+
properties = list(y = class_character)
14+
)
915

10-
But the method receives the original S7 object, not a formal S4 instance, so
11-
this approach is a compatibility bridge, not full substitutability.
16+
methods::is(Child(x = 1, y = "a"), "Parent")
17+
```
1218

1319
Things that work reasonably:
1420

21+
* S4 dispatch inherits through the S4 parent. An S4 generic with a method for
22+
`Parent` can dispatch on a `Child` object.
1523
* `methods::is(child, "Parent")` returns `TRUE`.
16-
* S4 dispatch inherits through `Parent`.
17-
* `methods::validObject(child)` validates the S4 parent slots through S4 and
18-
recursively validates S7 properties and class validators through S7.
19-
* `methods::slotNames(child)` reports the S4 parent slots. S7 extension
20-
properties are not registered as formal S4 slots.
21-
* `methods::slot(child, "x")` works for stored attributes, but bypasses the S7
22-
property layer.
23-
* `child@x` works through S7 property access. With the current oldClass
24-
representation, the object is not S4-bit so `@` can dispatch to S7's
25-
`@.S7_object` method. If S7 objects that extend S4 classes are represented
26-
as S4-bit objects in the future, R's `@` primitive will need to dispatch
27-
before taking the S4 slot-access path.
28-
* `methods::initialize(child, ...)` works for reinitializing an existing S7
29-
object through S4 code. This is most useful for inherited S4 methods that
30-
update parent slots or data parts. Values are routed through S7 property
31-
assignment, so S7 metadata and validation are preserved.
32-
* `as(child, "Parent")` can produce a real S4 `Parent` object containing the
33-
parent slots.
34-
35-
Likely brittle or incompatible with S4-method assumptions:
36-
37-
* `isS4(child)` is `FALSE` with the current oldClass representation. This
38-
avoids advertising formal S4 object invariants that S7 objects do not
39-
satisfy. S4 code is still exposed to S3-compatible, non-scalar `class()`
40-
vectors through the oldClass bridge.
41-
* `class(child)` is length greater than 1, for example
42-
`c("Child", "S4/Parent", "S7_object")`. This can break S4 code that treats
43-
`class()` as a scalar class name. Code that needs a scalar primary class
44-
name should use `class(x)[1L]`, or `methods::class1(x)` once that helper
45-
has been made public.
46-
* Methods that access slots with `methods::slot()` and `methods::slot<-()`
47-
bypass the S7 property layer. This is similar to deriving from an S3 class
48-
whose methods use `attr()` and `attr<-()` directly. Overriding S4 slots is
49-
therefore discouraged.
50-
* Calls like `methods::new(class(x)[1L], ...)` or
51-
`methods::new(class1(x), ...)` construct a new S4 object from S4's class
52-
definition. For an S7 class registered with S4, the result can satisfy
53-
`inherits(object, "S7_object")` through the S4 oldClass graph while still
54-
lacking the `S7_class` attribute. Such objects are not S7 instances, and
55-
`methods::validObject()` will reject them. Code that is updating an
56-
existing object should prefer `methods::initialize(x, ...)`, which gives
57-
S7's S4 `initialize()` method a chance to preserve S7 metadata.
58-
59-
In summary, inherited S4 methods must be written against the "registered
60-
oldClass that preserves the S4 parent" contract, not the stricter "formal S4
61-
instance" contract. They should use a scalar primary-class helper where
62-
appropriate and avoid `slot()` and `slot<-()` when they need S7 property
63-
management to apply.
24+
* S4 parent slots are available as S7 properties, so `prop(child, "x")` and
25+
`child@x` use the S7 property path.
26+
* `methods::slot(child, "x")` can read stored S4 parent slots and stored S7
27+
properties. Direct slot access bypasses custom S7 property behavior, so S4
28+
code should use it only for slots it owns.
29+
* `methods::validObject(child)` checks S4 slot types and recursively runs S7
30+
property validation and S7 class validators.
31+
* `methods::initialize(child, ...)` can reinitialize S7 objects from S4 code.
32+
Unnamed S7, S4, and S3-data-part arguments contribute their known
33+
properties or slots; later arguments override earlier arguments, and named
34+
arguments override unnamed ones.
35+
* `methods::as(child, "Parent")` and `convert(child, to = Parent)` can produce
36+
a real S4 object for the S4 parent class.
37+
38+
Caveats:
39+
40+
* An S7 object that directly extends S4 is currently represented as an S3
41+
old-class object, not as an S4-bit object, so `isS4(child)` is `FALSE`.
42+
This avoids advertising full S4 object invariants that the object does not
43+
satisfy.
44+
* The S3 class vector is not scalar. S4 code that needs one primary class name
45+
should use `class(x)[1L]`, or `methods::class1(x)` if available.
46+
* `methods::new(class(x)[1L], ...)` creates a fresh S4 object from the S4 class
47+
definition. For an S7 class registered with S4, that object can inherit from
48+
`S7_object` through the old-class graph while lacking the `S7_class` slot or
49+
attribute that makes it an S7 object. Code that updates an existing object
50+
should prefer `methods::initialize(x, ...)`.
51+
* S4 methods that call `methods::slot<-()` can bypass S7 property setters and
52+
ordinary S7 validation. Call `methods::validObject()` after such updates when
53+
the object should satisfy the S7 class contract.
54+
* Properties with custom getters or setters cannot be exposed as S4 slots. An
55+
S7 class that extends S4, or that is intended to be extended by S4, must use
56+
stored properties for the properties that need to cross the S4 boundary.
57+
* `NULL` properties are stored internally using the same sentinel that S4 uses
58+
for `NULL` slots. `prop()`, `@`, and `methods::slot()` translate this back to
59+
`NULL`; code that reads raw attributes should treat the representation as an
60+
implementation detail.
61+
62+
S4 classes can also extend S7 classes with `S4_register_contains()`. See
63+
`S4_register()` for details.

0 commit comments

Comments
 (0)