Skip to content

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

3 messages · Blätte, Andreas, Gabriel Becker

#
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
#
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:
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
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.
Class "people1" [in ".GlobalEnv"]


Slots:



Name:          f         g

Class: character character


Extends: "person1", "person2"
Class "people2" [in ".GlobalEnv"]


Slots:



Name:          g         f

Class: character character


Extends: "person2", "person1"
+ )

[1] "ohno"
*> 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 <andreas.blaette at uni-due.de>
wrote:

  
  
#
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 <gabembecker at gmail.com>
Datum: Dienstag, 6. Oktober 2020 um 02:02
An: Bl?tte <andreas.blaette at uni-due.de>
Cc: Peter Dalgaard via R-devel <r-devel at r-project.org>
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:
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
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.
Class "people1" [in ".GlobalEnv"]



Slots:



Name:          f         g

Class: character character



Extends: "person1", "person2"
Class "people2" [in ".GlobalEnv"]



Slots:



Name:          g         f

Class: character character



Extends: "person2", "person1"
+ )

[1] "ohno"
[1] "person1!"
[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 <andreas.blaette at uni-due.de<mailto:andreas.blaette at uni-due.de>> 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


______________________________________________
R-devel at r-project.org<mailto:R-devel at r-project.org> mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel