lundi 10 août 2020

How do user defined S3 Group Generic Functions work in R?

I'm reading Advanced R by Hadley Wickham and I'm confused at section 13.7.3 Group Generics.

enter image description here

I was a bit confused by the phrasing, "...you cannot define your own group generic... defining a single group generic for your class..." but I think this section means to say that if I define the group generic Math.MyClass then all functions within the Math group generic (abs, sign, etc) will be overwritten for MyClass objects.

This can be confirmed by running the following:

my_class <- structure(.Data = -1, class = "MyClass")

my_class
# [1] -1
# attr(,"class")
# [1] "MyClass"

abs(my_class)
# [1] 1
# attr(,"class")
# [1] "MyClass"

Math.my_class <- function(x) { x }

abs(my_class)
# [1] -1
# attr(,"class")
# [1] "MyClass"

I understand that this follows the special naming scheme generic.class but why is the value of .Data affected in abs(my_class)?

When I created the variable my_class, I set the argument .Data = -1, and the class of -1 is numeric and that should not have changed:

class(unclass(my_class))
# [1] "numeric"

my_numeric <- unclass(my_class)

class(my_numeric)
# [1] "numeric"

abs(my_numeric)
# [1] 1

So why doesn't abs(my_class) print the same result (1) before and after I define Math.MyClass?

I do receive the same results before and after I define Math.MyClass if I define the group generic as Math.MyClass <- function(x) {NextMethod()} but what's the point of having group generics then?

And, why do I get the same answer for abs(my_matrix) both before and after I define Math.matrix when I run the following:

my_matrix <- matrix(data = -1:-10, ncol = 5) + 0.0

class(my_matrix)
# [1] "matrix"

class(my_matrix[1,1])
# [1] "numeric"

my_matrix
#      [,1] [,2] [,3] [,4] [,5]
# [1,]   -1   -3   -5   -7   -9
# [2,]   -2   -4   -6   -8  -10

abs(my_matrix)
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    1    3    5    7    9
# [2,]    2    4    6    8   10

Math.matrix <- function(x) { x }

abs(my_matrix)
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    1    3    5    7    9
# [2,]    2    4    6    8   10

And when I run the following:

your_class <- structure(.Data = list(-1), class = "YourClass")

your_class
# [[1]]
# [1] -1
# 
# attr(,"class")
# [1] "YourClass"

abs(your_class)
# Error in abs(your_class) : non-numeric argument to mathematical function

class(unclass(your_list))
# [1] "list"

your_list <- list(-1)

class(your_list)
# [1] "list"

abs(your_list)
# Error in abs(your_list) : non-numeric argument to mathematical function

It's clear that the class of .Data does matter (initially anyway) because both abs(your_class) and abs(your_list) result in the same error.

To make things even more challenging, I found that everything goes back to normal for MyClass objects once I run rm(Math.MyClass):

my_class
# [1] -1
# attr(,"class")
# [1] "MyClass"

abs(my_class)
# [1] -1
# attr(,"class")
# [1] "MyClass"

rm(Math.MyClass)

abs(my_class)
# [1] 1
# attr(,"class")
# [1] "MyClass"

Could someone explain more completely what group generics are (Why do group generics exist / what do they accomplish / what is their parent-child relationship with R objects / why are the data arguments in some objects affected when the group generic is defined and others are not / etc)?

I have more experience with OOP in Python than in R if you feel it's easier to explain with Python examples. Any help is greatly appreciated!

Aucun commentaire:

Enregistrer un commentaire