RConsortium / S7

S7: a new OO system for R
https://rconsortium.github.io/S7
Other
403 stars 34 forks source link

Class intersection? #412

Open dipterix opened 3 months ago

dipterix commented 3 months ago

Would it be convenient to add new_intersect?

For example, I have a class_any_of_length_one, and a class_character, then I can intersect these two classes and define a character property with vector length 1 by class_any_of_length_one & class_character.

I understand there could be incompatible issues, but it's up to the maintainers to decide if the intersection is reasonable (maybe there is not a single object with properties that pass the validator).

hadley commented 3 months ago

I don't think so? Part of the point of a class union is to facilitate dispatch over multiple classes at once, and there isn't an equivalent for an intersection.

dipterix commented 3 months ago

Got it. Do you have any suggestions on how I can implement it by myself? Maybe I don't know the full considerations. I just thought maybe union is one of the validators succeeds and intersect is all validators returning NULL.

One potential issue is the default value. I would imaging the default value for class_character does not pass the validator of class_any_of_length_one & class_character.

It would be then easier if S7 class can offer something like default_initialization_value so users can define? But that's another issue/scope..

In C, if you declare int i; then i is initialized as 0 by default (in most of the compilers) even you don't explicitly set its value. For class_character, the default initializer is character(0L). But maybe I want it to be "" by default? Although new_property can set the default value, but the object has undefined/invalid status before the value is set in the constructor (suppose my validator fails if the value length is 0). Allowing to set default initialization value can make sure the instance is at least initialized valid

lawremi commented 3 months ago

We should decide whether we want to allow class objects that have the same name and represent the same fundamental type but differ in validation. That is technically possible now, by modifying a class object, but it will lead to confusing behavior. For example, validity is not considered during method selection. Also, the validation of properties happens only on the property itself. The class of the value is checked, but the class validator is not run, because any object of a given class is assumed valid.

In my experience, when a scalar is not set, we typically want it to be NULL instead of something like "", in the case of a character vector.

This leads to the following convenience functions:

scalar <- function(class, ..., nullable = FALSE) {
    if (nullable) {
        class <- new_union(class, NULL)
    }
    new_property(class, ..., validator = function(prop) {
        if (nullable && is.null(prop))
            NULL
        else if (length(prop) != 1L || is.na(prop))
            "must be of length one and not missing"
    })
}

Now you can do something like:

new_class(Foo, properties = list(string = scalar(class_character, nullable = TRUE)))

This is just a sketch (already at version 2). At some point, I'll put together a pull request.

lawremi commented 3 months ago

Separate comment about the notion of a class intersection: the term "class union" may be misleading, because it suggests that a class is a set and that other set operations should apply. Really new_union() creates a class set, but we should probably keep the term union due to the precedent set by S4.