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...
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.
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() ).
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).
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With