Skip to content

Attempt S4 dispatch for intersect() and union(), maybe#305

Open
MichaelChirico wants to merge 8 commits intomainfrom
intersect-s4
Open

Attempt S4 dispatch for intersect() and union(), maybe#305
MichaelChirico wants to merge 8 commits intomainfrom
intersect-s4

Conversation

@MichaelChirico
Copy link
Copy Markdown
Collaborator

@MichaelChirico MichaelChirico commented Mar 19, 2026

Closes #301. The rough idea is something Gemini pulled out of its hat as a way to avoid incurring anything too crufty/any dependency on {nanotime}.

With this (and #306), {nanotime} suite finally passes with devel {bit64}.

LMK if this makes sense to you.

@MichaelChirico MichaelChirico requested a review from hcirellu March 19, 2026 06:35
@MichaelChirico MichaelChirico changed the title Attempt S4 dispatch for intersect(), maybe Attempt S4 dispatch for intersect() and union(), maybe Mar 19, 2026
@hcirellu
Copy link
Copy Markdown
Collaborator

I have a different approach, which uses the regular S4 dispatch. I put it into a separate PR #307, to better compare the two.

@MichaelChirico
Copy link
Copy Markdown
Collaborator Author

What are your own thoughts on this approach? I see the main advantages as (1) simpler to maintain and (2) doesn't require us to become more "S4" in our package design generally.

@hcirellu
Copy link
Copy Markdown
Collaborator

hcirellu commented Mar 19, 2026

I agree with the advantages to be closer to a S3 package. I understand this S4 inclusion as a helper for a certain amount of time for other S4 packages, to adjust their code to use a more R consistent bit64 package. Earlier the other packages had to manouver around the missing consistency of the bit64 package. But that changes now. And later the S4 handling could be removed once the depending packages have adjusted. And then we are again a plain S3 package.

So in general, I would prefer this approach if there is no desire to become S4 anyways in the near future.

@MichaelChirico
Copy link
Copy Markdown
Collaborator Author

S4 handling could be removed once the depending packages have adjusted.

What should the downstream do though, exactly? I don't see a way around: as long as we export(intersect), downstream would need to do nanotime::intersect() to get their desired behavior, otherwise they need to attach the two packages in the "correct" order. What end state are you seeing?

@hcirellu
Copy link
Copy Markdown
Collaborator

In my understanding, S4 packages based on bit64 have to handle the S4 methods for their own classes, e.g. the intersect in nanotime. They have to have in mind, that integer64 is S3 and cannot generate their S4 objects, because they could be too complex and the base package does not know of the intended logic.
Since these packages depend on bit64 they import it anyways and should offer all the necessary methods regarding their own classes using setMethod. If someone attaches the bit64 package, it should not effect the behaviour of the S4 class package.

In #301 I tried to give an example, the way I work with S4. I usually strip it down to the base data type (here y@.Data), because this is the base class. After the operation I convert the result back to my object world.

setMethod("intersect", c("myInt64", "myCpl"), function(x, y) {
  # here to put the special intersect logic for nanotime and nanoival
  myCpl(intersect(x, y@.Data))
})

@MichaelChirico
Copy link
Copy Markdown
Collaborator Author

I'm still not seeing how that resolves this issue:

library(nanotime)
library(bit64)
intersect # bit64::intersect

@hcirellu
Copy link
Copy Markdown
Collaborator

I'm still not seeing how that resolves this issue:

library(nanotime)
library(bit64)
intersect # bit64::intersect

But this should always be the case, because bit64 overloads intersect. The overload function has to handle all cases and if one needs the former function, one has to use :: to access the function from another package.
In order to have no overloading, the S3 and S4 generics should sort that out. And since intersect ist no primitive and no generic in base, the question is what ot do.

And you want to achieve, that the intersect of nanotime is applied, if the packages are loaded in the above order?

Copy link
Copy Markdown
Collaborator

@hcirellu hcirellu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry for the late reply and that the changes in the test are all single line changes. I hope that still makes sense.

Comment thread R/setops64.R
#' @rdname sets
union = function(x, y) {
if ((isS4(x) || isS4(y)) && isGeneric("union")) {
s4_intersect = selectMethod("union", list(x=class(x), y=class(y)))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
s4_intersect = selectMethod("union", list(x=class(x), y=class(y)))
s4_union = selectMethod("union", list(x=class(x), y=class(y)))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we are in union

Comment thread R/setops64.R
union = function(x, y) {
if ((isS4(x) || isS4(y)) && isGeneric("union")) {
s4_intersect = selectMethod("union", list(x=class(x), y=class(y)))
return(s4_intersect(x, y))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return(s4_intersect(x, y))
return(s4_union(x, y))

Comment thread R/setops64.R
#' @rdname sets
setdiff = function(x, y) {
if ((isS4(x) || isS4(y)) && isGeneric("setdiff")) {
s4_intersect = selectMethod("setdiff", list(x=class(x), y=class(y)))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
s4_intersect = selectMethod("setdiff", list(x=class(x), y=class(y)))
s4_setdiff = selectMethod("setdiff", list(x=class(x), y=class(y)))

Comment thread R/setops64.R
setdiff = function(x, y) {
if ((isS4(x) || isS4(y)) && isGeneric("setdiff")) {
s4_intersect = selectMethod("setdiff", list(x=class(x), y=class(y)))
return(s4_intersect(x, y))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return(s4_intersect(x, y))
return(s4_setdiff(x, y))

Comment thread R/setops64.R

#' @export
#' @rdname sets
is.element = function(el, set) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
is.element = function(el, set) {
if ((isS4(el) || isS4(set)) && isGeneric("is.element")) {
s4_is_element = selectMethod("is.element", list(el=class(el), set=class(set)))
return(s4_is_element(el, set))
}

# of integer64 vectors, but there is no complex64 class, so it just
# shows up on the inheritance chain as 'complex' --> need to ensure
# S4 gets invoked when possible even if the inputs don't directly test
# as being is("integer64").
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# as being is("integer64").

# shows up on the inheritance chain as 'complex' --> need to ensure
# S4 gets invoked when possible even if the inputs don't directly test
# as being is("integer64").
expect_identical(intersect(x, x), "Successfully routed to S4 method!")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expect_identical(intersect(x, x), "Successfully routed to S4 method!")

# S4 gets invoked when possible even if the inputs don't directly test
# as being is("integer64").
expect_identical(intersect(x, x), "Successfully routed to S4 method!")
expect_identical(union(x, x), "Successfully routed to S4 method!")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expect_identical(union(x, x), "Successfully routed to S4 method!")

# as being is("integer64").
expect_identical(intersect(x, x), "Successfully routed to S4 method!")
expect_identical(union(x, x), "Successfully routed to S4 method!")
expect_identical(setdiff(x, x), "Successfully routed to S4 method!")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expect_identical(setdiff(x, x), "Successfully routed to S4 method!")

expect_identical(intersect(x, x), "Successfully routed to S4 method!")
expect_identical(union(x, x), "Successfully routed to S4 method!")
expect_identical(setdiff(x, x), "Successfully routed to S4 method!")
})
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
})

Copy link
Copy Markdown
Collaborator

@hcirellu hcirellu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry for the late reply and that the changes in the test are all single line changes. I hope that still makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New error in {nanotime}

2 participants