S4 - inheritance changed by order of setClassUnion and setAs()

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

S4 - inheritance changed by order of setClassUnion and setAs()

Blätte, Andreas
Dear colleagues,

there is a behaviour with S4 (virtual) classes that I find  very hard to understand: Depending on the position
of setAs(), the tree of inheritance changes.

This is my baseline example that defines the classes "grandma", "mother", "daughter" and a virtual
class "mr_x". For a new instance if "daughter", "mr_x" is betweeen "mother" and "grandma".

setClass("grandma", slots = c(a = "character"))
setClass("mother", slots = c(b = "matrix"), contains = "grandma")
setClass("daughter", slots = c(c = "list"), contains = "mother")
setClassUnion(name = "mr_x", members = c("daughter", "mother"))
setAs(from = "daughter", to = "grandma", def = function(from) new("grandma"))
is(new("daughter"))

[1] "daughter" "mother"   "mr_x"     "grandma"

Yet if I change the order of setAs() and setClassUnion(), this alters the pattern of inheritance.

setClass("grandma", slots = c(a = "character"))
setClass("mother", slots = c(b = "matrix"), contains = "grandma")
setClass("daughter", slots = c(c = "list"), contains = "mother")
setAs(from = "daughter", to = "grandma", def = function(from) new("grandma"))
setClassUnion(name = "mr_x", members = c("daughter", "mother"))
is(new("daughter"))

[1] "daughter" "mother"   "grandma"  "mr_x"

Is there a reasonable explanation for this behavior? I could not find any and I would appreciate
your help. If it is not an unintended behavior, I find it very confusing and hard to anticipate.

Kind regads
Andreas

--
Prof. Dr. Andreas Blätte
Professor of Public Policy and Regional Politics
University of Duisburg-Essen

        [[alternative HTML version deleted]]

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: S4 - inheritance changed by order of setClassUnion and setAs()

Gabriel Becker-2
Andreas,

As far as I can tell (/conjecture), this is because the list of classes a
particular class inherits from directly is appended to as needed, and so
the order that a class extends others isd refined by the order that those
connections are defined.

We can see this with two setClassUnion calls, rather than required setAs:

> setClass("grandma", slots = c(a = "character"))

> setClass("mother", slots = c(b = "matrix"), contains = "grandma")

> setClass("daughter", slots = c(c = "list"), contains = "mother")

> setClassUnion(name = "mr_x", members = c("daughter", "mother"))

> setClassUnion(name = "mr_y", members = c("daughter", "mother"))

> getClass("daughter")

Class "daughter" [in ".GlobalEnv"]


Slots:



Name:          c         b         a

Class:      list    matrix character


Extends:

Class "mother", directly

Class "mr_x", directly

Class "mr_y", directly

Class "grandma", by class "mother", distance 2

> setClass("grandma2", slots = c(a = "character"))

> setClass("mother2", slots = c(b = "matrix"), contains = "grandma2")

> setClass("daughter2", slots = c(c = "list"), contains = "mother2")

> setClassUnion(name = "mr_y2", members = c("daughter2", "mother2"))

> setClassUnion(name = "mr_x2", members = c("daughter2", "mother2"))

> getClass("daughter2")

Class "daughter2" [in ".GlobalEnv"]


Slots:



Name:          c         b         a

Class:      list    matrix character


Extends:

Class "mother2", directly

Class "mr_y2", directly

Class "mr_x2", directly

Class "grandma2", by class "mother2", distance 2


Note that mr_y2 appears in the list before mr_x2 the second block. The same
thing is happening with setAs which (somewhat contrary to my expectations,
admittedly) causes extends to consider "daughter" to inherit *directly* from
"grandma" in your example (though it does note its via explicit coercion).

I think the take-away here is that when modifying the class inheritance
structure explicitly, via setClassUnion or setAs (or, I assume, setIs)
order inherently matters.

In fact order also matters for multiple inheritence via the normal contains
mechanism. In practice, how could it not matter?

Multiple inheritence is very powerful but dangerous.

> setClass("person1", slots = c(f = "character"))

> setClass("person2", slots = c(g = "character"))

> setClass("people1",* contains = c("person1", "person2")*)

> getClass("people1")

Class "people1" [in ".GlobalEnv"]


Slots:



Name:          f         g

Class: character character


Extends: "person1", "person2"

> setClass("people2", *contains = c("person2", "person1")*)

> getClass("people2")

Class "people2" [in ".GlobalEnv"]


Slots:



Name:          g         f

Class: character character


Extends: "person2", "person1"

> setGeneric("ohno", function(obj) standardGeneric("ohno")

+ )

[1] "ohno"

> setMethod("ohno", "person1", function(obj) "person1!")

> setMethod("ohno", "person2", function(obj) "person2! Oh No!")

*> ohno(new("people1"))*

*[1] "person1!"*

*> ohno(new("people2"))*

*[1] "person2! Oh No!"*


Not sure if that helps any or not, but thats what I see here. And again, if
I got anything wrong here, someone please correct me :)

Best,
~G

On Mon, Oct 5, 2020 at 1:47 PM Blätte, Andreas <[hidden email]>
wrote:

> Dear colleagues,
>
> there is a behaviour with S4 (virtual) classes that I find  very hard to
> understand: Depending on the position
> of setAs(), the tree of inheritance changes.
>
> This is my baseline example that defines the classes "grandma", "mother",
> "daughter" and a virtual
> class "mr_x". For a new instance if "daughter", "mr_x" is betweeen
> "mother" and "grandma".
>
> setClass("grandma", slots = c(a = "character"))
> setClass("mother", slots = c(b = "matrix"), contains = "grandma")
> setClass("daughter", slots = c(c = "list"), contains = "mother")
> setClassUnion(name = "mr_x", members = c("daughter", "mother"))
> setAs(from = "daughter", to = "grandma", def = function(from)
> new("grandma"))
> is(new("daughter"))
>
> [1] "daughter" "mother"   "mr_x"     "grandma"
>
> Yet if I change the order of setAs() and setClassUnion(), this alters the
> pattern of inheritance.
>
> setClass("grandma", slots = c(a = "character"))
> setClass("mother", slots = c(b = "matrix"), contains = "grandma")
> setClass("daughter", slots = c(c = "list"), contains = "mother")
> setAs(from = "daughter", to = "grandma", def = function(from)
> new("grandma"))
> setClassUnion(name = "mr_x", members = c("daughter", "mother"))
> is(new("daughter"))
>
> [1] "daughter" "mother"   "grandma"  "mr_x"
>
> Is there a reasonable explanation for this behavior? I could not find any
> and I would appreciate
> your help. If it is not an unintended behavior, I find it very confusing
> and hard to anticipate.
>
> Kind regads
> Andreas
>
> --
> Prof. Dr. Andreas Blätte
> Professor of Public Policy and Regional Politics
> University of Duisburg-Essen
>
>         [[alternative HTML version deleted]]
>
> ______________________________________________
> [hidden email] mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel
>

        [[alternative HTML version deleted]]

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: S4 - inheritance changed by order of setClassUnion and setAs()

Blätte, Andreas
Dear Gabriel,

thank you so much for the scrutiny you took to look into this. Your explanation gives me a much better sense for what may be going on.

Admittedly, I still find it hard to understand why setAs() may have a side effect on inheritance. Looking at the documentation of setAs() again, this is an expected behaviour of setIs(), but not from setAs().

Maybe it makes sense to add a respective explicit explanation in the documentation of setAs()? For the time being, my personal take away is that I will try to circumvent setClassUnion() in combination with setAs().

Thanks again
Andreas

Von: Gabriel Becker <[hidden email]>
Datum: Dienstag, 6. Oktober 2020 um 02:02
An: Blätte <[hidden email]>
Cc: Peter Dalgaard via R-devel <[hidden email]>
Betreff: Re: [Rd] S4 - inheritance changed by order of setClassUnion and setAs()

Andreas,

As far as I can tell (/conjecture), this is because the list of classes a particular class inherits from directly is appended to as needed, and so the order that a class extends others isd refined by the order that those connections are defined.

We can see this with two setClassUnion calls, rather than required setAs:


> setClass("grandma", slots = c(a = "character"))

> setClass("mother", slots = c(b = "matrix"), contains = "grandma")

> setClass("daughter", slots = c(c = "list"), contains = "mother")

> setClassUnion(name = "mr_x", members = c("daughter", "mother"))

> setClassUnion(name = "mr_y", members = c("daughter", "mother"))

> getClass("daughter")

Class "daughter" [in ".GlobalEnv"]



Slots:



Name:          c         b         a

Class:      list    matrix character



Extends:

Class "mother", directly

Class "mr_x", directly

Class "mr_y", directly

Class "grandma", by class "mother", distance 2

> setClass("grandma2", slots = c(a = "character"))

> setClass("mother2", slots = c(b = "matrix"), contains = "grandma2")

> setClass("daughter2", slots = c(c = "list"), contains = "mother2")

> setClassUnion(name = "mr_y2", members = c("daughter2", "mother2"))

> setClassUnion(name = "mr_x2", members = c("daughter2", "mother2"))

> getClass("daughter2")

Class "daughter2" [in ".GlobalEnv"]



Slots:



Name:          c         b         a

Class:      list    matrix character



Extends:

Class "mother2", directly

Class "mr_y2", directly

Class "mr_x2", directly

Class "grandma2", by class "mother2", distance 2


Note that mr_y2 appears in the list before mr_x2 the second block. The same thing is happening with setAs which (somewhat contrary to my expectations, admittedly) causes extends to consider "daughter" to inherit directly from "grandma" in your example (though it does note its via explicit coercion).


I think the take-away here is that when modifying the class inheritance structure explicitly, via setClassUnion or setAs (or, I assume, setIs) order inherently matters.

In fact order also matters for multiple inheritence via the normal contains mechanism. In practice, how could it not matter?


Multiple inheritence is very powerful but dangerous.



> setClass("person1", slots = c(f = "character"))

> setClass("person2", slots = c(g = "character"))

> setClass("people1", contains = c("person1", "person2"))

> getClass("people1")

Class "people1" [in ".GlobalEnv"]



Slots:



Name:          f         g

Class: character character



Extends: "person1", "person2"

> setClass("people2", contains = c("person2", "person1"))

> getClass("people2")

Class "people2" [in ".GlobalEnv"]



Slots:



Name:          g         f

Class: character character



Extends: "person2", "person1"

> setGeneric("ohno", function(obj) standardGeneric("ohno")

+ )

[1] "ohno"

> setMethod("ohno", "person1", function(obj) "person1!")

> setMethod("ohno", "person2", function(obj) "person2! Oh No!")

> ohno(new("people1"))

[1] "person1!"

> ohno(new("people2"))

[1] "person2! Oh No!"

Not sure if that helps any or not, but thats what I see here. And again, if I got anything wrong here, someone please correct me :)


Best,
~G

On Mon, Oct 5, 2020 at 1:47 PM Blätte, Andreas <[hidden email]<mailto:[hidden email]>> wrote:
Dear colleagues,

there is a behaviour with S4 (virtual) classes that I find  very hard to understand: Depending on the position
of setAs(), the tree of inheritance changes.

This is my baseline example that defines the classes "grandma", "mother", "daughter" and a virtual
class "mr_x". For a new instance if "daughter", "mr_x" is betweeen "mother" and "grandma".

setClass("grandma", slots = c(a = "character"))
setClass("mother", slots = c(b = "matrix"), contains = "grandma")
setClass("daughter", slots = c(c = "list"), contains = "mother")
setClassUnion(name = "mr_x", members = c("daughter", "mother"))
setAs(from = "daughter", to = "grandma", def = function(from) new("grandma"))
is(new("daughter"))

[1] "daughter" "mother"   "mr_x"     "grandma"

Yet if I change the order of setAs() and setClassUnion(), this alters the pattern of inheritance.

setClass("grandma", slots = c(a = "character"))
setClass("mother", slots = c(b = "matrix"), contains = "grandma")
setClass("daughter", slots = c(c = "list"), contains = "mother")
setAs(from = "daughter", to = "grandma", def = function(from) new("grandma"))
setClassUnion(name = "mr_x", members = c("daughter", "mother"))
is(new("daughter"))

[1] "daughter" "mother"   "grandma"  "mr_x"

Is there a reasonable explanation for this behavior? I could not find any and I would appreciate
your help. If it is not an unintended behavior, I find it very confusing and hard to anticipate.

Kind regads
Andreas

--
Prof. Dr. Andreas Blätte
Professor of Public Policy and Regional Politics
University of Duisburg-Essen

        [[alternative HTML version deleted]]

______________________________________________
[hidden email]<mailto:[hidden email]> mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel

        [[alternative HTML version deleted]]

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel