Skip to content

Instantly share code, notes, and snippets.

@jeffreypullin
Last active November 6, 2019 10:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeffreypullin/12b525b68ae5ba97ce38ad4fe79d1275 to your computer and use it in GitHub Desktop.
Save jeffreypullin/12b525b68ae5ba97ce38ad4fe79d1275 to your computer and use it in GitHub Desktop.
library(purrr)
# Check if an object is an S3 generic.
is_S3_generic <- function(obj) {
if (!is.function(obj)) {
return(FALSE)
}
# I assume that primitive functions cannot be generic.
# This assumption is neccesary as primitives don't have a body and
# this breaks `isS3stdGeneric`
if (is.primitive(obj)) {
return(FALSE)
}
isS3stdGeneric(obj)
}
# A generic is problematic if it's methods contain the class array but not the
# class matrix.
is_problematic <- function(classes) {
# Not all generics seem to have methods.
if (length(classes) == 0) {
return(FALSE)
}
"array" %in% classes && !("matrix" %in% classes)
}
# Split an S3 method and extract the class (vectorised over full_method_name)
get_method_class <- function(full_method_name, generic_name) {
out <- strsplit(full_method_name, paste0(generic_name, "."), fixed = TRUE)
out <- map(out, 2)
unlist(out)
}
package_name <- "data.table"
# Q: Is there a way to do all of the following without attaching the namespace?
library(package_name, character.only = TRUE)
# S4
namespace <- paste0("package:", package_name)
S4_generic_names <- methods::getGenerics(namespace)
S4_methods <- map(S4_generic_names, ~ methods::findMethods(.x)@signatures)
# TODO: How to properly define problematic in the case of S4 generics/methods
# with 2 arguements?
S4_issue <- any(map_lgl(S4_methods, is_problematic))
# S3
exports <- base::mget(ls(namespace), inherits = TRUE)
S3_generics <- exports[map_lgl(exports, is_S3_generic)]
# Some package define the same generic under multiple names.
# i.e. dplyr has: summarize <- summarize. (We avoid unique as it drops names)
S3_generics <- S3_generics[!duplicated(S3_generics)]
S3_generic_names <- names(S3_generics)
S3_methods <- map(S3_generic_names, .S3methods)
S3_classes <- map2(S3_methods, S3_generic_names, get_method_class)
S3_issue <- any(map_lgl(S3_classes, is_problematic))
has_issue <- S3_issue || S4_issue
if (has_issue) {
message("Potential dispatch problem detected\n")
} else {
message("No problem detected\n")
}
@gmbecker
Copy link

gmbecker commented Nov 4, 2019

Very nice. Glad to see some people taking a crack a this. Ive taken the approach of trying to guide here rather than directly give answers, hopefully that comes across well.

A few things:

1. Finding all the methods

All generics are going to have at least one method or they wouldn't work at all. Consider the difference between

namespace = asNamespace(package_name)

vs

namespace = paste("package:", package_name)

Why would that make a difference?

2. Not loading packages

To answer your question about doing this without attaching packages, yes. We can load a namespace without attaching it via loadNamespace

3. Defining problematic in S4

S4 has multiple dispatch, which makes things substantially more complicated generally, but we only care about whether the method hit will change or not.

Consider two S4 generics:

setGeneric("matgen", function(x,y) standardGeneric("matgen"))

setMethod("matgen", c("Awesomefakeclass", "matrix"), function(x,y) 1)
setMethod("matgen", c("Awesomefakeclass", "array"), function(x,y) 2)

and

setGeneric("arrgen", function(x,y) standardGeneric("arrgen"))

setMethod("arrgen", c("Awesomefakeclass", "array"), function(x,y) 1)
setMethod("arrgen", c("ANY", "ANY"), function(x,y) 2)

What will happen before and after matrices begin inheriting from arrays for each of the above generics? How can we generalize this to significantly simplify and narrow the scope of our problem?

Bonus question: Upon further reflection, there is one more unrelated place in S4 world that would be effected by this, can you figure out what it is?

A different way

Have a look at ?methods. Is there a different approach that might be more straightforward for at least some of what we want?

@jeffreypullin
Copy link
Author

jeffreypullin commented Nov 6, 2019

Thanks for the detailed feedback @gmbecker! Unfortunately I won't be able to have another look at this for a few days but I'll get to it when I can. Feel free to move forward writing code.

p.s. Is the other place in S4 you refer to class instantiation - specifically checking the class of inputs matches the prototype?

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