From maechler at stat.math.ethz.ch  Thu Oct 30 12:20:13 2008
From: maechler at stat.math.ethz.ch (maechler at stat.math.ethz.ch)
Date: Thu, 30 Oct 2008 12:20:13 +0100 (CET)
Subject: [Rd] (PR#13209) Arith ops dropping S4 *and* 'object' bit [Was:
	...]
Message-ID: <20081030112013.2B607282EFC1@mail.pubhealth.ku.dk>

>>>>> "JMC" == John Chambers <jmc at r-project.org>
>>>>>     on Tue, 28 Oct 2008 11:50:38 -0400 writes:

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

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

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

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

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

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

That sounds very good !

Note that there's a related infelicity of primitives dealing
with class-attributes that does *not* involve S4 at all.

I'm adding an extra test to str() {i.e. str.default} in order to 
get rid of the infinite recursion in the following,
but we might also consider to change the behavior of '+' here :

## str() with an "invalid object"
ob <- structure(1, class = "test") # this is fine
is.object(ob)# TRUE
ob <- 1 + ob # << this is "broken"
is.object(ob)# FALSE - hmm, ....
identical(ob, unclass(ob)) # TRUE (!!!!)  and as a consequence :
str(ob)
## infinite recursion in R <= 2.8.0

--
Martin Maechler, ETH Zurich


    JMC> ---------------------

    JMC> Example:

    JMC> 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
    JMC> An object of class ???test1???
    JMC> [1] 11 11 11 11 11 11 11 11 11 11
    JMC> Slot "label":
    JMC> [1] "Something"

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

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

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


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

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

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



    JMC> Simon Urbanek wrote:
    >> 
    >> On Oct 27, 2008, at 12:25 , Robert.McGehee at geodecapital.com wrote:
    >> 
    >>> 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).
    >> 
    >> 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
    >> 
    >> 
    >> 
    >>> 
    >>>> setClass("test", representation("vector"))
    >>> [1] "test"
    >>>> z <- new("test", 1)
    >>>> identical(z+1, 1+z)
    >>> [1] FALSE
    >>>> str(z+1)
    >>> Formal class 'test' [package ".GlobalEnv"] with 1 slots
    >>> ..@ .Data: num 2
    >>>> str(1+z)
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class
    >>> 'test' Class 'test' Class 'test' Class 'test' Class 'test' Class 'test'
    >>> Class 'test' 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)?
    >>> 
    >>>> R.version
    >>> _ =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)
    >>> 
    >>> ______________________________________________
    >>> R-devel at r-project.org mailing list
    >>> https://stat.ethz.ch/mailman/listinfo/r-devel
    >>> 
    >>> 
    >> 
    >> ______________________________________________
    >> R-devel at r-project.org mailing list
    >> https://stat.ethz.ch/mailman/listinfo/r-devel
    >> 

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



[Rd] (PR#13209) Arith ops dropping S4 *and* 'object' bit [Was: ...]
maechler at stat.math.ethz.ch