2

I want to overload the '*' (multiplication operator) in R, when using an S3 class.

I see that * is already generic in the system, but I also want it "generic2", i.e. dispatching on the second argument.

The use case is as follows: Say my class is called "Struct". I want to be able to allow for all three of these cases

Struct * Struct 
Struct * Number
Number * Struct

However I found that if I allow for dispatching on the second argument, the (already present) dispatching on the first is overridden!

Is there a way that I can do this in S3?

# "generic1" already exists for '*'

'*' <- function(x,y){
  UseMethod('*2',y)
}

'*.Struct'<-function(x,y){
  # must both be structs, so dispatch 'normally' if not
  "times(1)"
}

`*2.Struct`<-function(x,y){
  # must both be structs, so dispatch 'normally' if not
  "times(2)"
}

Gives me...

> struct1 * struct2
[1] "times(2)"
> 2 * struct2
[1] "times(2)"
> struct1 * 2
Error in UseMethod("*2", y) : 
  no applicable method for '*2' applied to an object of class "c('double', 'numeric')"
> 

If I use this, instead

'*' <- function(x,y){ UseMethod('*',x)}

Then the dispatch on the first argument works, and the opposite happens:

> struct1 * 2
[1] "times(1)"
> struct1 * struct2
[1] "times(1)"
> 2* struct1 
Error in UseMethod("*", x) : 
  no applicable method for '*' applied to an object of class "c('double', 'numeric')"
> 

So it seems that they're definitely overwriting each other.

Any ideas about how both may coexist peacefully and productively?

Alex Gian
  • 482
  • 4
  • 10

1 Answers1

1

You could check inside the function :

'*.Struct'<-function(x,y){
  if(inherits(x,'Struct') && inherits(y,'Struct'))
    "Struct*Struct"
  else if(inherits(y,'Struct'))
    "N*Struct"
  else
    "Struct*N"
}
# N.B.: you don't need to redefine `*`,`*2.Struct` etc

e.g. :

struct1=structure(5,class='Struct')
struct2=structure(3,class='Struct')

struct1*struct2
# [1] "Struct*Struct"
struct1*2
# [1] "Struct*N"
3*struct2
# [1] "N*Struct"

As stated here dispatching works on both arguments with the following rule :

If a method is found for just one argument or the same method is found for both, it is used. If different methods are found, there is a warning about ‘incompatible methods’: in that case or if no method is found for either argument the internal method is used.

So, for example since there's also a *.difftime method define, these cases will give odd results with warnings :

difftimeObj <- Sys.time()-Sys.time()

struct1*difftimeObj
# [1] 0
# attr(,"units")
# [1] "secs"
# attr(,"class")
# [1] "Struct"
# Warning message:
# Incompatible methods ("*.Struct", "*.difftime") for "*" 

difftimeObj*struct2
# Time difference of 0 secs
# Warning message:
# Incompatible methods ("*.difftime", "*.Struct") for "*" 

while these instead work :

struct1*unclass(difftimeObj)
# [1] "Struct*N"
unclass(difftimeObj)*struct2
# [1] "N*Struct"

# skipping dispatching
`*.Struct`(struct1, difftimeObj)
# [1] "Struct*N"
`*.Struct`(difftimeObj, struct2)
# [1] "N*Struct"
digEmAll
  • 56,430
  • 9
  • 115
  • 140
  • Thanks. That will work for me in THIS case, since Struct happens to be a vector, so multiplication by a Number will be handled by default, but unfortunately I don't think it would work, if I could not rely on the sytem default behaviour, i.e. if I HAD to be able to define the behaviour of "Number * Struct" myself... – Alex Gian Sep 03 '18 at 07:26
  • @AlexGian: to be honest, I don't understand your problem... with my example, you have possibly 3 cases, just "unclass" the objects inside one of the case before multiplying if you want the default R behavior...but probably I'm missing something – digEmAll Sep 03 '18 at 07:33
  • I doubt that it's you who doesn't understand. This is only my second day with R, so I may well be missing something. I'll mull on it some more, try the unclassing you suggested, and see if I can come up with something. Thanks anyway. (BTW, what I was saying, is ¿HOW? will the overloaded operator be triggered, even, if the first argument is a number? I thought the dispatch was happening because the first arg was a Struct. – Alex Gian Sep 03 '18 at 07:42
  • Right, yes, I'd noticed that difftime method define. It has now somehow been overwritten, I don't see it there any more. Anyways I removed the '*' <- function(x,y) { UseMethod('*', x) } line, reloaded everything, and now your original code works fine! I'm not sure where the difftime thingumy has got itself to, but I am happy to proceed as it is now, I'm sure I'll get to grips with the minutiae later. Many thanks for your detailed help! – Alex Gian Sep 03 '18 at 08:14
  • 1
    Sure :) Consider also that you can override the whole set of operators (i.e. `>,<,==,+,-,*`...) using `Ops.Struct <- function(){}` check how the function for an example, check how `Ops.difftime` is implemented – digEmAll Sep 03 '18 at 08:40