[Rd] Class union of custom classes

Ezra Tucker ezr@ @end|ng |rom |@ndtucker@com
Tue Jul 19 17:01:29 CEST 2022


Hi Pantelis,

When you inheriting from the "foobar" class, you drop any slots that
foo or bar may have had. Indeed, if you do

> getSlots("foobar")
# character(0)

The reason this doesn't work is because your .Data slot is gone when
you create withId.
A basic question I'd ask by doing this, what exactly is it that you're
expecting? And why is using setClassUnion in this way better or
different than just using multiple inheritance--

> setClass("withId", contains = c("foo", "bar"), slots = c(id =
"character"))
> new("withId", TRUE, id = "5")
An object of class "withId"
[1] TRUE
Slot "id":
[1] "5"

Also worth noting what happens when you make a foo object

> z <- new("foo", TRUE)
> is(z)
[1] "foo"     "logical" "foobar"  "vector" 
# this is what I get anyway

Though I'm no expert (one should chime in if this is wrong) all the
slot info for the "foo" class is stored with "foo"-- all "foobar" is
two things:

1. it'll allow you you to set a slot in some other s4 class to
"foobar", which will be valid for any "foo" or "bar" or anything that
inherits from "foobar" like withId-- since by setting the class union,
you're saying that foo and bar inherit from foobar
2. it'll allow you to define methods with "foobar" in the signature,
which it'll dispatch for foo, bar, or withId

BUT, again, it doesn't actually contain foo's or bar's slots. I don't
think this is a bug, I think this is how setClassUnion works and is
supposed to work.

For Test 6, same problem as test 5. The slots now are just logical
called x, but again, there's no slots when you inherit from a
classUnion.

Test 7 is the same as test 5, just explicitly calling out the .Data
slot instead of just saying it inherits from logical. Super
interestingly, you can do some ops with these objects like

> new("foo", TRUE) | new("bar", FALSE)
[1] TRUE

There must be some default ops methods somewhere for classes that have
a .Data slot, but this behavior is totally new to me. I don't think I'd
recommend doing it intentionally though...

To recap it's not "tricking" setClassUnion into thinking there are real
slots-- when you inherit from a classUnion you'll be able to dispatch
any of its methods, but you don't get any slots from the "members" that
comprise that class union.

-Ezra

On Tue, 2022-07-05 at 11:31 +0200, pikappa.devel using gmail.com wrote:
> Hi Ezra,
> 
> thanks, this was very helpful! Your answer got me thinking, and I
> have tried a couple of more approaches. I thought it would be good to
> document them here in case someone stumbles on this issue in the
> future.
> 
> 
> I have tried to define classes with combatible initializers:
> 
> > # test 5
> + setClass("foo", contains = "logical")
> + setClass("bar", contains = "logical")
> + setClassUnion("foobar", c("foo", "bar"))
> + setClass("withId", contains = "foobar", slots = c(id =
> "character"))
> + w1 <- new("withId", new("foo", TRUE), id = "test 5")
> > Error in initialize(value, ...) : 
>   'initialize' method returned an object of class “foo” instead of
> the required class “withId”
> 
> > # test 6
> + setClass("foo", slots = list(x = "logical"))
> + setClass("bar", slots = list(x = "logical"))
> + setClassUnion("foobar", c("foo", "bar"))
> + setClass("withId", contains = "foobar", slots = c(id =
> "character"))
> + w1 <- new("withId", new("foo", x = TRUE), id = "test 6")
> > > Error in initialize(value, ...) : 
>   'initialize' method returned an object of class “foo” instead of
> the required class “withId”
> 
> I have also tried to "trick" setClassUnion by naming members of foo
> and bar .Data:
> 
> > # test 7
> + setClass("foo", slots = list(.Data = "logical"))
> + setClass("bar", slots = list(.Data = "logical"))
> + setClassUnion("foobar", c("foo", "bar"))
> + setClass("withId", contains = "foobar", slots = c(id =
> "character"))
> + w1 <- new("withId", new("foo", .Data = TRUE), id = "test 7")
> Error in initialize(value, ...) : 
>   'initialize' method returned an object of class “foo” instead of
> the required class “withId”
> 
> The approach you proposed is the only one that works, but with a
> catch. The withId class will not have access to foo methods, and
> wrappers are additionally needed to imitate inheritance. If foo has
> only a few methods, this approach is maintainable. For now, this is
> the approach I will follow in the real problem I am dealing with. 
> 
> Kind Regards,
> Pantelis
> 
> -----Original Message-----
> From: Ezra Tucker <ezra using landtucker.com> 
> Sent: Sunday, July 3, 2022 7:57 PM
> To: pikappa.devel using gmail.com; r-devel using r-project.org
> Subject: Re: [Rd] Class union of custom classes
> 
> Hi Pantelis,
> 
> What usually helps me in these kinds of puzzles is splitting out
> (mentally) the s4 part from the s3 part. The first test you mention,
> using the class "withId" has an s3 part of a "maybeNumber" (numeric
> or
> logical) and an s4 part of a slot "id". Kind of hidden will be a
> second slot, ".Data" which contains the s3 data --The w1 value you
> get is essentially a numeric, and will be subject to numeric methods
> (ie you could do w1 + 2) and it'll add 2 to all of the values in the
> .Data slot.
> 
> Test 2:
> part of the reason setClassUnion works in Test 1 is because the
> member classes of withID have the same slots-- both of them are
> .Data.
> However, the definition of "foo" in this test has one slot, xb, which
> is logical, and no .Data slot/no s3 part. foo and withId have
> incompatable initializers (the "initialize" method for their
> respective classes so that's why you're seeing this error.
> 
> Test 3:
> In general, I'd probably avoid using setIs, as it sets an explicit
> relationship between two classes, whether or not it's logical to do
> so.
> While the initializer for "maybeNumber" ought to complete, because of
> the issues raised above about Test 2, it'll prevent the object from
> being created.
> 
> Test 4:
> This one's tricky. A revealing question is, what happens when you try
> 
> > w1 <- new("withId", TRUE, id = "test 4")
> 
> Error in initialize(value, ...) : 
>   cannot use object of class "logical" in new():  class "withId" does
> not extend that class
> 
> wheras if you did
> 
> > setClass("withId", slots = c(data = "maybeNumber", id =
> > "character"))
> > w1 <- new("withId", data = new("foo", TRUE), id = "test 4")
> 
> it should work properly (or you could do data = 1.2 - it won't
> recognize a value TRUE as being of class foo since it's logical. Your
> withId has to have .Data of class numeric or foo, which themselves
> have incompatible initializers.
> 
> Hopefully this helps!
> 
> -Ezra
> 
> On Sun, 2022-07-03 at 13:02 +0200, pikappa.devel using gmail.com wrote:
> > Dear all,
> > 
> >  
> > 
> > This code, mostly copied from the setClassUnion help page, works as
> > described in the documentation:
> > 
> >  
> > 
> > # test 1
> > 
> > setClassUnion("maybeNumber", c("numeric", "logical"))
> > 
> > setClass("withId", contains = "maybeNumber", slots = c(id =
> > "character"))
> > 
> > w1 <- new("withId", 1.2, id = "test 1")
> > 
> >  
> > 
> > However, the following three tests do not work:
> > 
> >  
> > 
> > # test 2
> > 
> > setClass("foo", slots = list(xb = "logical"))
> > 
> > setClassUnion("maybeNumber", c("numeric", "foo"))
> > 
> > setClass("withId", contains = "maybeNumber", slots = c(id =
> > "character"))
> > 
> > w1 <- new("withId", 1.2, id = "test 2")
> > 
> >  
> > 
> > # test 3
> > 
> > setClass("foo", slots = list(xb = "logical"))
> > 
> > setClassUnion("maybeNumber", c("numeric"))
> > 
> > setIs("foo", "maybeNumber")
> > 
> > setClass("withId", contains = "maybeNumber", slots = c(id =
> > "character"))
> > 
> > w1 <- new("withId", 1.2, id = "test 3")
> > 
> >  
> > 
> > # test 4
> > 
> > setClass("foo", contains = "logical")
> > 
> > setClassUnion("maybeNumber", c("numeric", "foo"))
> > 
> > setClass("withId", contains = "maybeNumber", slots = c(id =
> > "character"))
> > 
> > w1 <- new("withId", 1.2, id = "test 4")
> > 
> >  
> > 
> > All three return:
> > 
> >  
> > 
> >   Error in initialize(value, ...) : 
> > 
> >       'initialize' method returned an object of class "numeric"
> > instead of
> > the required class "withId"
> > 
> >  
> > 
> > The error comes from:
> > 
> >  
> > 
> > traceback()
> > 
> > 3: stop(gettextf("'initialize' method returned an object of class
> > %s 
> > instead of the required class %s",
> > 
> >        paste(dQuote(class(value)), collapse = ", "), 
> > dQuote(class(.Object))),
> > 
> >        domain = NA)
> > 
> > 2: initialize(value, ...)
> > 
> > 1: new("withId", 1.2, id = "test 2")
> > 
> >  
> > 
> > I would expect tests 2-4 to work similarly to the first test. Is
> > the 
> > above error the intended behavior of setClassUnion? I do not see 
> > anything that would prevent this in the documentation. Is there 
> > something I am missing here?
> > 
> >  
> > 
> > Any help would be very much appreciated!
> > 
> >  
> > 
> > Kind regards,
> > 
> > Pantelis
> > 
> > 
> >         [[alternative HTML version deleted]]
> > 
> > ______________________________________________
> > R-devel using r-project.org mailing list
> > https://stat.ethz.ch/mailman/listinfo/r-devel
> 
> 



More information about the R-devel mailing list