When I tried to define an S3 class that contains multiple ggplot objects, I’ve faced the lessor-know mechanism of S3 method dispatch, double dispatch.
Problem
Take a look at this example. manyplot class contains many plots, and displays them nicely when printted.
library(ggplot2)set.seed(100)d1 <-data.frame(x =1:100, y =cumsum(runif(100)))d2 <-data.frame(x =1:100, y =cumsum(runif(100)))plot_all <-function(...) { l <-lapply(list(...), function(d) ggplot(d, aes(x, y)) +geom_line()) l <-unname(l)class(l) <-"manyplot" l}print.manyplot <-function(x, ...) {do.call(gridExtra::grid.arrange, x)}p <-plot_all(d1, d2)p
So far, so good.
Next, I want to define + method, so that I can customize the plots just as I do with usual ggplot2.
`+.manyplot`<-function(e1, e2) { l <-lapply(e1, function(x) x + e2)class(l) <-"manyplot" l}
But, this won’t work…
p +theme_bw()
Warning: Incompatible methods ("+.manyplot", "+.gg") for "+"
Error in p + theme_bw(): non-numeric argument to binary operator
What’s this cryptic error? To understand what happened, we need to dive into the concept of S3’s “double dispatch”
Double dispatch?
Usually, S3’s method dispatch depends only on the type of first argument. But, in cases of some infix operators like + and *, it uses both of their arguments; this is called double dispatch.
foo <-function(x) structure(x, class ="foo")`+.foo`<-function(e1, e2) message("foo!")bar <-function(x) structure(x, class ="bar")`+.bar`<-function(e1, e2) message("bar?")# both have the same S3 methodfoo(1) +foo(1)
foo!
NULL
# both have different S3 methodsfoo(1) +bar(1)
Warning: Incompatible methods ("+.foo", "+.bar") for "+"
[1] 2
attr(,"class")
[1] "foo"
# `a` has a method, and `b` doesn'tfoo() +1
Error in structure(x, class = "foo"): argument "x" is missing, with no default
# `b` has a method, and `a` doesn't1+foo()
Error in structure(x, class = "foo"): argument "x" is missing, with no default
# both don't have methodsrm(`+.foo`)foo(1) +foo(1)
[1] 2
attr(,"class")
[1] "foo"
Explanation
So, now it’s clear to our eyes what happened in the code below; they have different methods (+.manyplot and +.gg) so it falled back to internal method. But, because fundamentally they are list, the internal mechanism refused to add these two objects…
If you cannot wait, use S4. S4 can naturally do double dispatch because their method dispatch depends on the whole combination of types of the arguments.