#[derive(AllEqual)]
{
// Attributes available to this derive:
#[all_equal]
}
Expand description
Derive an all.equal() method for the R class of an ExternalPtr type,
comparing the struct field by field (#2149).
Without it, all.equal() on two objects of a pointer-backed class
returns TRUE whatever they hold: base R compares an external pointer
only by its data.class(). The derived method compares each field with
base all.equal() on the field’s R value, so each field keeps its own
method: numbers keep R’s tolerance, lists and data frames use base’s
methods, and a field holding another pointer class T uses T’s own
all.equal() method (another AllEqual type).
#[derive(ExternalPtr, AllEqual)]
#[all_equal(tolerance = 1e-6)]
pub struct Fit {
#[all_equal(label = "Estimates")]
estimates: Vec<f64>,
#[all_equal(tolerance = 0)]
iterations: i32,
model: ExternalPtr<Model>,
#[all_equal(skip)]
cache: RefCell<Option<Vec<f64>>>,
}
#[miniextendr(s3)]
impl Fit { /* ... */ }all.equal(fit_a, fit_b) # TRUE, or c("Estimates: Mean relative difference: 2e-05", ...)
all.equal(fit_a, fit_b, tolerance = 1e-3) # the call's tolerance replaces the struct's defaultThe type also needs #[derive(ExternalPtr)] and an impl block with a
class system (env, r6, s3, s4 or s7): the method is
all.equal.<Class>, registered with S3method(), and for S7 also with
S7::method(all.equal, Class). Installing a package with the derive on a
type without a class, or with a vctrs class (whose objects are R vectors
that all.equal() already compares), fails with a message naming it.
§The comparison
The method is all.equal.<Class>(target, current, tolerance, scale, ..., check.attributes, check.names), in all.equal.numeric()’s argument
order. A current of another class gives base’s
"target is <class>, current is <class>". Otherwise each compared field
is compared with all.equal(target_field, current_field, ...), and the
result is TRUE or the messages of every differing field, in field
order, each prefixed as all.equal.list() does (Component “estimates”: ..., Component 1: ... for a tuple field) or with the field’s label.
An object restored from a saved session (readRDS()) is refused with
the usual miniextendr_restored_* error.
A field’s R value is a clone converted with IntoR, or:
#[all_equal(with = "Self::f")]: the value of a getterfn(&Self) -> impl IntoR;- a
Computedfield: the value of its#[r_data(get = "...")]getter; - a
Sidecar<T>field: its value in the pointer’s protection list; - an
ExternalPtr<T>orOption<ExternalPtr<T>>field: the pointer itself, not a clone.
The RSidecar selector is not compared.
A field whose type names a pointer class T of the package (T,
ExternalPtr<T>, Option<ExternalPtr<T>>, or a Vec of T or
ExternalPtr<T>) has its value given T’s R class, as a method returning that type does, so T’s own
method compares it: give T the derive too, since without a method of
its own T is compared by all.equal.default(), which finds any two
pointers equal. The derive can’t see a getter’s type, so a pointer a
with getter or a Computed field returns stays bare, as does a class
of another package (#2191).
§Settings
tolerance, scale, check_attributes and check_names (the R
arguments check.attributes / check.names) are formals of the method.
Each is passed to a field’s all.equal() only when something sets it, so
a field’s own method otherwise keeps its default (a nested AllEqual
type its own struct-level settings). Precedence: the field’s
#[all_equal(...)], then the call, then the struct’s #[all_equal(...)]
(the formal’s default), then the field’s own method’s default. Other
arguments of the call are passed on to every field through ....
On the struct, #[all_equal(...)] takes the settings. On a field it
takes the settings, which the call can’t override, and:
skip: the field is not compared (alone: it takes no other option);label = "...": the field’s messages start with"<label>: ";with = "Self::f": the getter whose value is compared.