Skip to content

Documentation of S3 and S4 classes, inheritance

9 messages · Gabor Grothendieck, Duncan Murdoch, Robert Gentleman +2 more

#
I'd like to have a class A that computes a likelihood, and a subclass B
that computes the same likelihood by sometimes throws in an additional
term (B includes measurement error).

So B's likelihood needs to call A's, and then (sometimes) multiply by an
additional term.

It sounds as if, in the S3 scheme, NextMethod is supposed to do this:

like.A <- function(stuff) compute value
like.B <- function(stuff) extraFactor*NextMethod()
?

but, after some study of both the Language Manual (2.1) and the online
help of NextMethod I can't tell exactly what it does.  In particular,
I'm not sure if it returns to the calling function, or how it decides
which flavor to call.  The language manual says the method choice
depends on the values of .Generic and .Class, but doesn't say how those
get filled in.  I would expect .Class to be the current class, in which
case the call is recursive.  The online help says "'NextMethod' invokes
the next method (determined by the class)" but it doesn't say how it is
determined.  One ambiguity is whether "class" refers to the class (B) or
the class attribute ("B", "A").

I think the documentation could be clearer.

Now, probably none of this matters to me, since several sources
(including the online help for S3)indicate that S4 classes are
preferred.

I found the documentation for S4 initially elusive.  As far as I can
tell, it isn't even mentioned in the "R Language Definition."  While the
fact that S4 is defined in a package makes clear it is not formally part
of the base language, this is not a very good way to get people to use
S4.

I think by now I've tracked down docs on S4, including the
intro/overview at http://www.omegahat.org/RSMethods/.

Finally, I'm a bit concerned that one article mentioned that S4
inheritance, in practice, is used mostly for data, not methods (Thomas
Lumley, R News 4(1), June 2004: p. 36).  Am I going down a road I
shouldn't travel?
#
On 5/23/05, Ross Boylan <ross at biostat.ucsf.edu> wrote:
Here is a working example to try out.  In the call to lik,
the first class in the class vector is "B" so lik.B gets invoked.  
The next method of x is "A" so NextMethod invokes lik.A from 
within lik.B and then returns its result to lik.A which finishes the 
calculation.

lik <- function(x) UseMethod("lik")
lik.A <- function(x) mean(x)
lik.B <- function(x) NextMethod("lik") + sd(x)

x <- structure(1:3, class = c("B", "A"))
lik(x) # 3
See above discussion.
This area seems somewhat controversial with different people stating
different opinions.   IMHO you are probably best off to start with S3 since its
simpler and if you do learn S4 later they are not unrelated so it will make 
it easier than jumping straight into it.  Also you may find you never have to 
go beyond S3 in which case you have saved yourself some time.  I 
personally use S3.

By the way if choosing from S3 and S4 is not enough, there are also 
two CRAN packages that provide additional OO models as well: 
R.oo and proto.
#
On Mon, 2005-05-23 at 14:41 -0700, Ross Boylan wrote:
....
Hmm, maybe I just found out.  If B is an S4 subclass of A (aka extends
A), how does B's method foo invoke A's foo?

The question assumes that A's foo was defined as an in place function,
so there's no (obvious) named object for it, i.e,
setMethod("A", signature(blah="numeric"), function(x) something)
#
Ross Boylan wrote:
Your question doesn't make sense in S4.  In S4, classes don't have 
methods, generics have methods.  There's no such thing as "B's method" 
or "A's method".

You might get what you want with foo(as(bObject, "A")) if bObject is an 
instance of class B.
I don't know what you mean by "in place function", but I hope my answer 
helps anyway.

Duncan Murdoch
#
Duncan Murdoch wrote:
In general it may be best to think of a generic function as a 
dispatching mechanism. For S4 methods are associated with a specific 
generic function. A generic knows about all methods that are associated 
with it, and about no others. Thus in S4, the little tiff over who owns 
label goes away - they both do - different packages can define generic 
functions for label, or anything else they care to, and users can write 
methods for specific generic functions and associate them with a 
generic. [Note that in the S3 system there is no real mechanism for 
saying that foo.bar is a method for one generic named foo, and not for 
another - but the language does allow for multiple generics named foo - 
one of the very many reasons that S3 does not really do what you want it 
to, but many seem convinced otherwise].

The class hierarchy of the actual supplied arguments, is used to 
determine the dispatch order (a linearization of the available methods) 
once the generic is invoked. The most specific method is used first. It 
may intiate a call to callNextMethod (S4) or NextMethod (S3) to transfer 
control to the next most specific method - the manual pages provide more 
specific details.

As Duncan said - classes do not own methods in this paradigm. Generic 
functions do.

  HTH
    Robert
#
On Mon, 23 May 2005, Ross Boylan wrote:

            
You may be looking for callNextMethod, or foo(as(object, "A")).

The comment about inheritance in my R News article has nothing to do with 
S3 vs S4.  It is just that extensions of data structures typically happen 
by specialisation (for which inheritance is appropriate) whereas models 
are typically extended by generalisation (for which inheritance isn't 
appropriate).

The only relevance to the S3 vs S4 discussion is that it provides an 
explanation for the lack of appreciation of S4. Since most statisticians 
don't use inheritance when programming they don't see the benefit in a 
system that gets inheritance right.

 	-thomas
#
On Tue, May 24, 2005 at 07:07:07AM -0400, Duncan Murdoch wrote:
Oops, I keep taking the references to "objects" too literally.  Thanks.
There's my confusion: the first argument should be the name of the
generic, not the class.
Just for clarification, "in place function" was in contrast to a
function defined elsewhere with an explicit name, e.g.,
   fforA<-function(x) something
   setMethod("foo", signature(blah="numeric"), fforA)
In that case I could just refer to fforA directly. (Trying to avoid
the S3ish f.A).

Is sounds as if the use of as() or callNextMethod() will get me what I
want.  And the docs seem clear that callNextMethod() returns control
(and a value) to the calling function.

Thanks to everyone for their help.
#
On Tue, May 24, 2005 at 06:27:56AM -0700, Robert Gentleman wrote:
"specific" generic is a reference to the ability to define generics
 within the context of a particular package?
Presumably setMethod does the association.  Is the where argument
intended to identify which generic method to pick?  The fact that
there is not a "package" argument to setMethod, as there is to
setGeneric, is a little confusing to me.
"They" is two different packages?  Or is this a reference to my
original confusion about class vs generic ownership of a method?
...
#
Ross Boylan wrote:
Well, more that you can identify one (either explicitly by its package 
[ie. fully-qualified name], or implicitly by the fact that it is first 
on the search path - the former being prefered). But the notion is that 
there is one and you have asked that your method be made available to 
that one generic function for dispatch. This is in contrast to S3 - 
where no such functionality exists - that I know of; under S3 any method 
is a method for all generic functions of the correct name and the 
programmer has no (easy) control. Under S4 methods are not really 
ordinary functions, should not be called directly and are invoked only 
via calls to the generic (you can of course break all of those rules).
I believe that is the documented behavior. Yes the where argument 
should allow you complete specificity. I will leave it to the auther to 
clarify differences between the where and package arguments in the call 
to setGeneric (or you, if you care to explore the code).
It is the two packages. The S langauge allows for the same symbol to 
be bound to different values in different namespaces (in R that is 
becoming explicit, in SPlus it is less so, but still generally true). I 
can think of no good reason to treat generic functions, or any other 
first class object, differently.

HTH,
  Robert
functions and associate them with a