Skip to content

S3 objects in S4 slots

10 messages · Martin Kober, Martin Maechler, Martin Morgan +2 more

#
Hello,

I am the maintainer of the stringkernels package and have come across
a problem with using S3 objects in my S4 classes.

Specifically, I have an S4 class with a slot that takes a text corpus
as a list of character vectors. tm (version 0.5) saves corpora as
lists with a class attribute of c("VCorpus", "Corpus", "list"). I
don't actually need the class-specific attributes, I only care about
the list itself.

Here's a simplified example of my problem:
[1] "testclass"
Error in validObject(.Object) :
  invalid class "testclass" object: 1: invalid object for slot "slot"
in class "testclass": got class "VCorpus", should be or extend class
"list"
invalid class "testclass" object: 2: invalid object for slot "slot" in
class "testclass": got class "Corpus", should be or extend class
"list"
invalid class "testclass" object: 3: invalid object for slot "slot" in
class "testclass": got class "list", should be or extend class "list"

The last line is a bit confusing here (``got class "list", should be
or extend class "list"''). There's an even more confusing error
message when I try to assign the slot later on:
Error in checkSlotAssignment(object, name, value) :
  c("assignment of an object of class \"VCorpus\" is not valid for
slot \"slot\" in an object of class \"testclass\"; is(value, \"list\")
is not TRUE", "assignment of an object of class \"Corpus\" is not
valid for slot \"slot\" in an object of class \"testclass\"; is(value,
\"list\") is not TRUE", "assignment of an object of class \"list\" is
not valid for slot \"slot\" in an object of class \"testclass\";
is(value, \"list\") is not TRUE")

The last part of the message claims that ``is(value, "list") is not
TRUE'', but is(a, "list") is certainly TRUE. (??)

On a side note, it does work when "list" is the first entry in class().


I tried to use setOldClass, but seemingly using list is not possible
because it does not extend oldClass, or I didn't find out how to do
it:
Error in setOldClass(c("VCorpus", "Corpus", "list")) :
  inconsistent old-style class information for "list"; the class is
defined but does not extend "oldClass"


Intuitively I would have thought that, because the underlying data is
of type list, it would "fit" into an object slot requiring a list,
irrespective of S3 class attributes. The only thing I can think of is
a manual solution removing the class attribute.

Is there a way to define lists with S3 class attributes such that they
are accepted as lists in S4 object slots? Or any other ways to solve
this?


Thanks in advance
Best Regards
Martin
#
Martin Kober wrote:
A possible workaround is to store unclass(a) in that slot, rather than 
a.  You won't be able to use the VCorpus or Corpus methods on it, but it 
sounds as though you don't want to.

I would say checkSlotAssignment should be using some variation on 
inherits(), rather than checking for an exact class match, but probably 
the real message is that you shouldn't mix S3 and S4 systems.  Convert 
the VCorpus objects to S4 objects, or use S3 objects everywhere.

Duncan Murdoch
#

        
> Martin Kober wrote:
>> Hello,
    >> 
    >> I am the maintainer of the stringkernels package and have come across
    >> a problem with using S3 objects in my S4 classes.
    >> 
    >> Specifically, I have an S4 class with a slot that takes a text corpus
    >> as a list of character vectors. tm (version 0.5) saves corpora as
    >> lists with a class attribute of c("VCorpus", "Corpus", "list"). I
    >> don't actually need the class-specific attributes, I only care about
    >> the list itself.
    >> 
    >> Here's a simplified example of my problem:
    >> 
    >> 
    >>> setClass("testclass", representation(slot="list"))
    >>> 
    >> [1] "testclass"
    >> 
    >>> a = list(a="1", b="2")
    >>> class(a) = c("VCorpus", "Corpus", "list") # same as corpora in tm v0.5
    >>> x = new("testclass", slot=a)
    >>> 
    >> Error in validObject(.Object) :
    >> invalid class "testclass" object: 1: invalid object for slot "slot"
    >> in class "testclass": got class "VCorpus", should be or extend class
    >> "list"
    >> invalid class "testclass" object: 2: invalid object for slot "slot" in
    >> class "testclass": got class "Corpus", should be or extend class
    >> "list"
    >> invalid class "testclass" object: 3: invalid object for slot "slot" in
    >> class "testclass": got class "list", should be or extend class "list"
    >> 
    >> The last line is a bit confusing here (``got class "list", should be
    >> or extend class "list"''). There's an even more confusing error
    >> message when I try to assign the slot later on:
    >> 
    >> 
    >>> y = new("testclass")
    >>> y at slot = a
    >>> 
    >> Error in checkSlotAssignment(object, name, value) :
    >> c("assignment of an object of class \"VCorpus\" is not valid for
    >> slot \"slot\" in an object of class \"testclass\"; is(value, \"list\")
    >> is not TRUE", "assignment of an object of class \"Corpus\" is not
    >> valid for slot \"slot\" in an object of class \"testclass\"; is(value,
    >> \"list\") is not TRUE", "assignment of an object of class \"list\" is
    >> not valid for slot \"slot\" in an object of class \"testclass\";
    >> is(value, \"list\") is not TRUE")
    >> 
    >> The last part of the message claims that ``is(value, "list") is not
    >> TRUE'', but is(a, "list") is certainly TRUE. (??)
    >> 
    >> On a side note, it does work when "list" is the first entry in class().
    >> 
    >> 
    >> I tried to use setOldClass, but seemingly using list is not possible
    >> because it does not extend oldClass, or I didn't find out how to do
    >> it:
    >> 
    >>> setOldClass(c("VCorpus", "Corpus", "list"))
    >>> 
    >> Error in setOldClass(c("VCorpus", "Corpus", "list")) :
    >> inconsistent old-style class information for "list"; the class is
    >> defined but does not extend "oldClass"
    >> 
    >> 
    >> Intuitively I would have thought that, because the underlying data is
    >> of type list, it would "fit" into an object slot requiring a list,
    >> irrespective of S3 class attributes. The only thing I can think of is
    >> a manual solution removing the class attribute.
    >> 
    >> Is there a way to define lists with S3 class attributes such that they
    >> are accepted as lists in S4 object slots? Or any other ways to solve
    >> this?
    >> 

    > A possible workaround is to store unclass(a) in that slot, rather than 
    > a.  You won't be able to use the VCorpus or Corpus methods on it, but it 
    > sounds as though you don't want to.

    > I would say checkSlotAssignment should be using some variation on 
    > inherits(), rather than checking for an exact class match, but probably 
    > the real message is that you shouldn't mix S3 and S4 systems.

I don't think so.
Recent releases of R have had big improvements (instrumented by
John Chambers) in order to make exactly this (S3 classed objects as
slots of S4 objects) possible !!

    >   Convert  the VCorpus objects to S4 objects, or use S3 objects everywhere.

Well, no, at least not at all according to what I think has been the
intentions of the recent years' changes of making S3 objects
better fit into the S4 frame work.

Martin Maechler
#
On 15/09/2009 7:34 AM, Martin Maechler wrote:
Shouldn't an object that inherits from "list" be allowed to occupy a 
slot that wants a list?

Duncan Murdoch
#
Hi Martin Kober --
Martin Kober wrote:
This has the feel of a workaround, but

setOldClass(class(a)[1:2]); setIs("Corpus", "list")

helps here, first registering c('vCorpus', 'Corpus') as an S3 hierarchy
and then defining the relationship between the root of the hierarchy and
(the S4 class) 'list'.

Martin (Morgan)
#
2009/9/15 John Chambers <jmc at r-project.org>:
That would be really useful.

Do you think it would be feasible to automatically assume that all S3
objects "contain" their basic data type (like a list, in this case)
for purposes of S4 class matching, when no other information is given?

At least in this specific case, the idea is that corpus objects can be
used as a list of character vectors and code does not need to know
anything about the object structure as long as only the contents of
the list are needed. So (again, in this specific case), I would
consider "defaulting" to the underlying data type optimal.
Thanks to Martin Morgan for the workaround, it works very well.
#
On 16/09/2009 12:50 PM, John Chambers wrote:
I would disagree about this.  We do expect z$coefficients to work, not 
just coefficients(z); the former only works because z is a list. 
Similarly, z[["coefficients"]].  Now, I wouldn't likely ever type 
z[1:2], but I can see doing computations on the names and using a 
numeric index to subset z.

  S3 classes of this style are more like S4
I didn't comment on this before, because I think that's a good 
intention, and I think users appreciate all the work you're doing to 
bring it about:  but I'd still stick by my advice not to mix S3 and S4. 
  As a general principle it's better to keep your programming life 
simple.  Don't take advantage of every feature the language offers, work 
within a subset, where you're sure you understand how everything works. 
  Someone like you or either Martin M. has a deep enough knowledge of S3 
and S4 and how they interact to get away with mixing them, but most 
users don't.

The original question came up because Martin K., working with S4 
objects, wanted to make use of an object from a package that used S3.  I 
still think he would have been better off to spend a little bit of time 
writing the function to convert the S3 object into an S4 object, and 
then never having to worry again about the intricacies of how S4 
inheritance interacts with S3 inheritance.

Duncan Murdoch
#
Duncan Murdoch wrote:
But if the S3 class lives in someone else's package we will now have two 
versions with all the well-known headaches that causes.  In  practice, 
your advice ends up being a way of discouraging the use of S4 classes 
since there are so many legacy S3 classes.

(BTW somewhat in contrast to how I felt at one time, I'm not trying to 
dump on S3 classes, after all I'm to blame for much of what's in them.  
They are a more casual way to deal with some basic ideas and have a 
lower learning barrier.  But we didn't provide the material for as 
general or as trustworthy programming with them, so one hopes that users 
will feel able to move on as they get deeper into their projects.)

Nobody is forcing people to take advantage of the increased 
compatibility but let's try to sort out any problems, as Martin Kober 
did here, rather than discouraging the effort.

John