Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define an S4 prototype for inherited slots

Tags:

oop

r

s4

I have a base class (let's call it "A") whose representation is common to many other classes.

Therefore I define other classes, such as "B", to contain this class.

I would like to set the prototype of these other classes (B) to include the default values for the slots inherited from A. I thought this would be natural:

setClass("A", representation(a="character"))
setClass("B", contains="A", prototype(a = "hello"))

But it produces the error:

Error in representation[!slots] : object of type 'S4' is not subsettable

Not sure why this happens. If I omit the prototype I can do:

setClass("B", contains="A")

and then hack my own generator function:

new_B <- function(...){ 
           obj <- new("B", ...)
           obj@a = "hello"
           obj
         }

and then create my object based on the prototype with new_B(), but that's terribly crude and ugly compared to using the generic generator new("B") and having my prototype...

like image 632
cboettig Avatar asked Aug 12 '13 21:08

cboettig


People also ask

How do you create S4 objects How can you check an object is S4 object?

We can check if an object is an S4 object through the function isS4() . The function setClass() returns a generator function. This generator function (usually having same name as the class) can be used to create new objects. It acts as a constructor.

What is S4 method?

S4 provides a formal approach to functional OOP. The underlying ideas are similar to S3 (the topic of Chapter 13), but implementation is much stricter and makes use of specialised functions for creating classes ( setClass() ), generics ( setGeneric() ), and methods ( setMethod() ).

What does S4 mean in R?

The S4 system in R is a system for object oriented programing. Confusingly, R has support for at least 3 different systems for object oriented programming: S3, S4 and S5 (also known as reference classes).

How are S4 classes better than S3 classes?

S4 Class is stricter, conventional, and closely related to Object-Oriented concepts. The classes are represented by the formal class definitions of S4. More specifically, S4 has setter and getter functions for methods and generics. As compared to the S3 class, S4 can be able to facilitate multiple dispatches.


1 Answers

Supplementing my comment rather providing a new answer to the question, here's a solution where we still match arguments by position (because we specify additional representation for class B):

.A <- setClass("A", representation(a="character"))
.B <- setClass("B", representation(b="numeric"),
     prototype(a="hello"),
     contains="A")

.A() and .B() replace calls to new("A") and new("B"). At some level this is syntactic sugar, but can make object construction more transparent

## construct an object using B's prototype, like new("B", b=1:3)
> .B(b=1:3)
An object of class "B"
Slot "b":
[1] 1 2 3

Slot "a":
[1] "hello"

## construct an object using A's prototype, like new("B", new("A"), b=1:3)
> .B(.A(), b=1:3)
An object of class "B"
Slot "b":
[1] 1 2 3

Slot "a":
character(0)

(the second example uses the fact that unnamed arguments to new or B are used to initialize inherited classes).

It isn't so friendly for the user to have to use .A or .B directly, e.g., because the signature is just ... and so would be documented as 'see the definition of slots for class A'. This disrupts the separation of interface and implementation that is a strength of OOP. Also, one or the other behavior in the last code chunk (.B(.A(a=a), b=b) or .B(a=a, b=b)) might not be the intention. So instead provide a function that is exposed to the user, perhaps doing some initial data massage

A <- function(a=character(), ...) {
    ## nothing special, just a public constructor
    .A(a=a, ...)
}

B <- function(b, a="hello", ...) {
    a <- tolower(a)  ## no SHOUTing!
    .B(A(a=a), b=b)  ## A's public interface; no need for B to know A's details
}

The functions A and B define the interface, provide the user with hints about what acceptable arguments are without tying the constructor to the class definition, and perform preliminary data massaging. The latter can make initialize methods unnecessary, which is a good thing because these have a complicated contract (they're supposed to initialize and be copy constructors, and as we've seen above unnamed arguments are supposed to initialize base classes) that most people get wrong.

Mostly these are just my opinions.

like image 115
Martin Morgan Avatar answered Oct 08 '22 20:10

Martin Morgan