From jmc at r-project.org  Sat Dec 11 17:40:59 2004
From: jmc at r-project.org (John Chambers)
Date: Sat Dec 11 17:41:14 2004
Subject: [Rd] RE: [R] Testing for S4 objects
In-Reply-To: <20041211035814.KHYJ1836.tomts16-srv.bellnexxia.net@JohnDesktop8300>
References: <20041211035814.KHYJ1836.tomts16-srv.bellnexxia.net@JohnDesktop8300>
Message-ID: <41BB231B.6030108@R-project.org>



John Fox wrote:
> Dear r-devel list members,
> 
> I'm moving this question to r-devel because it seems thornier than I
> originally thought.

Yes, it's certainly not for r-help.

> 
> I've already mentioned (on r-help) that the approach that John Chambers
> suggested (below) fails for objects of class "by":
> 
> 
>>x <- rnorm(100)
>>y <- sample(2, 100, replace=TRUE)
>>res <- by(x, y, mean)
>>res
> 
> INDICES: 1
> [1] -0.03429679
> ------------------------------------------------------------ 
> INDICES: 2
> [1] -0.1273790
> 
>>class(res)
> 
> [1] "by"
> 
>>isS4object <- function(object)(length(attr(object, "class"))==1 &&
> 
> +     !is.null(getClass(class(object))))
> 
>>isS4object(res)
> 
> Error in getClass(class(object)) : "by" is not a defined class
> 
> 
> I tried to fix that, but I've now discovered more general problems; e.g.:

Let's not revert to slotNames(). For the reasons I mentioned it's 
inevitably going to produce a confusing definition.

There are a couple of problems (aside from my having used getClass() 
where I meant to use getClassDef() :-{):
- we need to handle S3 classes that have been registered with S4 
dispatch by calling setOldClass().  Doing this is strongly recommended, 
but the effect is to create an S4 definition.  That's one reason why 
lm() objects might appear to be S4 objects.  Presumably, we don't want that.
- eventually, if this is a serious thing that people need, we need to 
worry about objects defined in namespaces, with private class definitions.

Here's a more careful version of the previous idea, which I believe 
handles the first of these problems, by using the fact that an object 
generated by new("foo",...) cannot come from a VIRTUAL class. That gets 
actually to "What do we really mean by an S4 object?"  I'm essentially 
saying that an object that could not have been created by a new() call 
is not an S4 object.  People could cheat, of course, and it's not clear 
what we should do with such objects.  Both the case of no definition and 
the case of S3 classes registered with setOldClass() should produce a 
VIRTUAL class.

(By the way, I was wondering what the actual intent of this function 
was.  Usually, one would try to have generic functions deal sensibly 
with objects for which they had no method--either some default 
calculation or an error message.  The notion of "S4 object" is pretty 
general or vague, as we're demonstrating.  On the whole, it would be 
better not to get tangled up in it.)

I'm weakly confident that the current version also handles the namespace 
issue, by using the actual class() call, which should include a 
"package" attribute to get to the right namespace.

But no assertions that extensive testing has been done.  Nevertheless, 
here is a second approximation.

isS4Object <- function(object) {
     if(length(attr(object, "class"))!= 1)
         return(FALSE)
    !isVirtualClass(getClass(class(object), TRUE))
}



> 
> 
>>mod <- lm(y ~ x)
>>class(mod)
> 
> [1] "lm"
> 
>>isS4object(mod)
> 
> [1] TRUE
> 
>>class(summary(mod))
> 
> [1] "summary.lm"
> 
>>isS4object(summary(mod))
> 
> Error in getClass(class(object)) : "summary.lm" is not a defined class
> 
> 
> I've reverted to a modified version of my original proposal:
> 
> 
>>isS4object <- function(object) {
> 
> +     !(length(object) == 1 && class(object) == "character") &&
> length(slotNames(object)) != 0
> +     }
> 
>>isS4object(res)
> 
> [1] FALSE
> 
>>isS4object(mod)
> 
> [1] FALSE
> 
>>isS4object(summary(mod))
> 
> [1] FALSE
> 
>># example from ?mle
>>x <- 0:10
>>y <- c(26, 17, 13, 12, 20, 5, 9, 8, 5, 4, 8)
>>ll <- function(ymax=15, xhalf=6)
> 
> +     -sum(stats::dpois(y, lambda=ymax/(1+x/xhalf), log=TRUE))
> 
>>fit <- mle(ll)
> 
> Warning message: 
> NaNs produced in: dpois(x, lambda, log) 
> 
>>isS4object(fit)
> 
> [1] TRUE
> 
>>isS4object("mle")
> 
> [1] FALSE
> 
> 
> All this is with R 2.0.1 under Windows NT.
> 
> Comments would be appreciated.
> 
> John
> 
> --------------------------------
> John Fox
> Department of Sociology
> McMaster University
> Hamilton, Ontario
> Canada L8S 4M4
> 905-525-9140x23604
> http://socserv.mcmaster.ca/jfox 
> -------------------------------- 
> 
> 
>>-----Original Message-----
>>From: John Chambers [mailto:johnmchambers@gmail.com] 
>>Sent: Tuesday, November 30, 2004 9:40 AM
>>To: John Fox
>>Cc: Martin Maechler; r-help@stat.math.ethz.ch
>>Subject: Re: [R] Testing for S4 objects
>>
>>Let me suggest a different test, because slotNames was 
>>written to work differently when given a string or a class 
>>definition.  With your definition,
>>
>>R> x <- "classRepresentation"
>>R> isS4object(x)
>>[1] TRUE
>>
>>which I assume is not what you wanted.  (Given a single string,
>>slotNames() tries to look up the class definition of that name.)
>>
>>How about the following?  The logic is that an S4 object must 
>>have an actual class attribute of length 1 (that rules out 
>>basic data types, where class(x) is a string but there is no 
>>actual attribute, and also rules out some S3 objects).  Then 
>>if that's true, try to look up the class definition.  If it 
>>is non-null, seems like an S4 object.
>>
>>R> isS4object <- function(object)(length(attr(object, "class"))==1 &&
>>+     !is.null(getClass(class(object))))
>>R> isS4object(x)
>>[1] FALSE
>>R> isS4object(getClass(class(x)))
>>[1] TRUE
>>
>>This definition seems to work, at least on the examples I 
>>could think of right away.  Notice though, that some classes, 
>>such as "ts", that have been around for a long while are 
>>nevertheless legitimate S4 classes, so:
>>
>>R> t1 = ts(1:12)
>>R> isS4object(t1)
>>[1] TRUE
>>
>>(this applies to either version of isS4object).
>>
>>There are a couple of details, more appropriate for the r-devel list. 
>>Seems  a good candidate for a function to add to R.
>>
>>
>>On Sat, 27 Nov 2004 17:48:30 -0500, John Fox <jfox@mcmaster.ca> wrote:
>>
>>>Dear Martin,
>>>
>>>As it turns out, the test that I proposed (i.e., testing for NULL 
>>>slotNames) sometimes fails. For example:
>>>
>>>
>>>>library(car)
>>>>data(Prestige)
>>>>sum <- summary(lm(prestige ~ income + education, data=Prestige))
>>>>slotNames(sum)
>>>
>>>character(0)
>>>
>>>The following, however, seems to work (at least as far as I've been 
>>>able to
>>>ascertain):
>>>
>>>isS4object <- function(object) length(slotNames(object)) != 0
>>>
>>>I hope that this is a more robust test.
>>>
>>>
>>>
>>>John
>>>
>>>--------------------------------
>>>John Fox
>>>Department of Sociology
>>>McMaster University
>>>Hamilton, Ontario
>>>Canada L8S 4M4
>>>905-525-9140x23604
>>>http://socserv.mcmaster.ca/jfox
>>>--------------------------------
>>>
>>>
>>>>-----Original Message-----
>>>>From: Martin Maechler [mailto:maechler@stat.math.ethz.ch]
>>>>Sent: Friday, November 26, 2004 3:18 AM
>>>>To: John Fox
>>>>Cc: r-help@stat.math.ethz.ch
>>>>Subject: Re: [R] Testing for S4 objects
>>>>
>>>>
>>>>>>>>>"JohnF" == John Fox <jfox@mcmaster.ca>
>>>>>>>>>    on Thu, 25 Nov 2004 22:28:50 -0500 writes:
>>>>
>>>>    JohnF> Dear r-help list members, Is there a way to test
>>>>    JohnF> whether an object is an S4 object? The best that I've
>>>>    JohnF> been able to come up with is
>>>>
>>>>    JohnF>    isS4object <- function(object)
>>>>!(is.null(slotNames(object)))
>>>>
>>>>you can drop one pair of "(..)" to give
>>>>
>>>>  isS4object <- function(object) !is.null(slotNames(object))
>>>>
>>>>
>>>>    JohnF> which assumes that an S4 object has at least one
>>>>    JohnF> slot. I think this is safe, but perhaps I'm missing
>>>>    JohnF> something.
>>>>
>>>>The question is a very good one -- that I have posed to R-core a 
>>>>while ago myself.
>>>>
>>>>Inside  utils:::str.default  {which doesn't show the many 
>>
>>commments 
>>
>>>>in the *source* of str.default()}, I have wanted a way that even 
>>>>works when the 'methods' package is not attached and use the more 
>>>>obscure
>>>>
>>>>    #NOT yet:if(has.class <- !is.null(cl <- class(object)))
>>>>    if(has.class <- !is.null(cl <- attr(object, "class")))#
>>>>S3 or S4 class
>>>>      S4 <- !is.null(attr(cl, "package"))## <<<'kludge' FIXME!
>>>>      ##or length(methods::getSlots(cl)) > 0
>>>>
>>>>For the time being, I'd keep your function, but I don't 
>>
>>think we'd 
>>
>>>>guarantee that it will remain the appropriate test in all 
>>
>>future.  
>>
>>>>But till then many things will have happened (if not all of them 
>>>>;-).
>>>>
>>>>Martin Maechler, ETH Zurich
>>>>
>>>
>>>______________________________________________
>>>R-help@stat.math.ethz.ch mailing list
>>>https://stat.ethz.ch/mailman/listinfo/r-help
>>>PLEASE do read the posting guide! 
>>>http://www.R-project.org/posting-guide.html
>>>
> 
> 
> ______________________________________________
> R-devel@stat.math.ethz.ch mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel
>


[Rd] RE: [R] Testing for S4 objects
John Chambers