Skip to content

S4 object does not commute? (PR#13209)

3 messages · McGehee, Robert, Simon Urbanek, John Chambers

#
Hello all,
It appears that for the simplest of S4 objects, z+1 does not equal 1+z.
Presumably this is a bug, as 1+z seems to make a malformed object (at
least malformed as an input to str).
Thanks, Robert
[1] "test"
[1] FALSE
Formal class 'test' [package ".GlobalEnv"] with 1 slots
  ..@ .Data: num 2
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
Class 'test' Class 'test' Error: evaluation nested too deeply: infinite
recursion / options(expressions=3D)?
_                          =20
platform       x86_64-unknown-linux-gnu   =20
arch           x86_64                     =20
os             linux-gnu                  =20
system         x86_64, linux-gnu          =20
status                                    =20
major          2                          =20
minor          8.0                        =20
year           2008                       =20
month          10                         =20
day            20                         =20
svn rev        46754                      =20
language       R                          =20
version.string R version 2.8.0 (2008-10-20)
#
On Oct 27, 2008, at 12:25 , Robert.McGehee at geodecapital.com wrote:

            
FWIW the difference is that z+1 has the S4 bit set, 1+z does not. The  
objects are otherwise identical. AFAICS the same behavior is  
reproducible with any binary arithmetic operator (i.e. non-S4 %op% S4  
will produce a result with S4 bit cleared yet valid S4 attributes).

Cheers,
S
#
The asymmetry is just the symptom of a more fundamental issue: There are 
no operator methods currently defined for "vector" classes, either 
combined with each other or with a non-S4 object.

The consequence is that computations drop through to the primitive C 
code. Not a good idea, because that code does various things to objects 
with attributes, some of them bizarre and most of them not what should 
logically happen for S4 classes.

Consider two classes with slightly more content than "test":
 > setClass("test1",contains = "vector", representation(label = 
"character"))
[1] "test1"
 > setClass("test2",contains = "vector", representation(flag = "logical"))
[1] "test2"

These two classes both inherit from "vector" but are unrelated to each 
other.

What should happen for arithmetic and other operators combining these 
two classes? There's some scope for discussion, but a reasonable policy 
is that the vector parts should be used and a result returned that is a 
simple vector. What should NOT happen is that one class is retained and 
the other thrown away--that's not a meaningful interpretation of the two 
definitions here. Unfortunately, that's what does happen with the 
primitives. Example below.

We need to develop some methods for combinations of "vector" and "ANY" 
reflecting what's sensible. I'll put some first attempts on r-devel and 
we can discuss what's wanted. (May not happen right away, but hopefully 
in a week or two.)

John

---------------------

Example:

If the objects are equal in length, the left operand wins, or seems to:

 > x1 = new("test1", 1:10, label = "Something")
 > x2 = new("test2", 10:1, flag = rep(TRUE, 10))
 > x1+x2
An object of class ?test1?
[1] 11 11 11 11 11 11 11 11 11 11
Slot "label":
[1] "Something"

 > x2+x1
An object of class ?test2?
[1] 11 11 11 11 11 11 11 11 11 11
Slot "flag":
[1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE

But in fact, it's weirder than that, because _all_ the attributes are 
retained:

 > names(attributes(x1+x2))
[1] "flag" "class" "label"
 > names(attributes(x2+x1))
[1] "label" "class" "flag"


That was with equal lengths. Otherwise, the code uses the longer 
object's attributes, including the class.

 > x11 = new("test1", 101:105,label = "Smaller")
 > x11 +x2
An object of class ?test2?
[1] 111 111 111 111 111 106 106 106 106 106
Slot "flag":
[1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE

 > x2+x11
An object of class ?test2?
[1] 111 111 111 111 111 106 106 106 106 106
Slot "flag":
[1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
 > names(attributes(x11+x2))
[1] "flag" "class"
Simon Urbanek wrote: