I am trying to write a method for concatenation of two S4 classes that I have defined:
setClass("My_item",
representation(contents = "vector"))
setClass("My_group",
representation(members = "list"))
The members
of every instance of the My_group
class are all members of the My_item
class but I have not included here the validation code that enforces that requirement.
I wish to write a concatenation method for the My_group
class based on c()
from base R. Its inputs will be any number (including zero) of elements which might be a mixture of members of either My_item
class or the My_group
class. The method should return a single member of the My_group
class consisting of all the My_item
members in the inputs, just as c(c(1, 2), 3)
returns c(1, 2, 3)
.
I understand that the definition of my method must follow exactly the definition of c()
and so must take the following form:
setMethod(
f = "c",
signature = "My_group",
definition = function(x, ..., recursive = FALSE) {
[code to be written]
}
)
My question is about the function that does the work.
I can write a straight R function that does what I want:
myf<- function(...){
elements <- list(...)
if (length(elements) != 0) {
items <- unlist(lapply(
elements,
FUN = function(object) {
if (is(object, "My_group")) {
return(getMy_group(object))
} else {
return(object)
}
}
))
object <- new("My_group",
members = items )
} else {
object <- new("My_group")
}
}
(getMy_group
is a simple method that unpacks a member of the My_group
class into a list of its members.)
If I define a1, a2, a3
as members of the My_item
class, and g1
as a My_group
with members a1
and a2
,
a1 <- new("My_item", contents = c(1, 2, 3))
a2 <- new("My_item", contents = c( "x", "y", "z"))
a3 <- new("My_item", contents = c(0.1, 0.2, 0.3))
g1 <- new("My_group", members = list(a1, a2))
then myf(g1, a3)
returns a My_group
with 3 members, as required.
R>str(myf(g1, a3))
Formal class 'My_group' [package ".GlobalEnv"] with 1 slot
..@ members:List of 3
.. ..$ :Formal class 'My_item' [package ".GlobalEnv"] with 1 slot
.. .. .. ..@ contents: num [1:3] 1 2 3
.. ..$ :Formal class 'My_item' [package ".GlobalEnv"] with 1 slot
.. .. .. ..@ contents: chr [1:3] "x""y""z"
.. ..$ :Formal class 'My_item' [package ".GlobalEnv"] with 1 slot
.. .. .. ..@ contents: num [1:3] 0.1 0.2 0.3
But if I define my method using the same code as in the function myf
, as follows:
setMethod(
f = "c",
signature = "My_group",
definition = function(x, ..., recursive = FALSE) {
elements <- list(...)
if (length(elements) != 0) {
items <- unlist(lapply(
elements,
FUN = function(object) {
if (is(object, "My_group")) {
return(getMy_group(object))
} else {
return(object)
}
}
))
object <- new("My_group",
members = items)
} else {
object <- new("My_group")
}
return(object)
}
)
I get the wrong answer:
R>c(g1, a3)
An object of class "My_group"
Slot "members":
[[1]]
An object of class "My_item"
Slot "contents":
[1] 0.1 0.2 0.3
The method appears to have ignored g1
.
I suspect I have misunderstood the role of the, to me, mysterious x
that appears in the definition of c()
, but I can't get any further than that in my diagnosis.
Edit: following JDL's helpful and reasoned suggestion that I use setClassUnion
I wrote the following with a simple method that is supposed merely to return the arguments supplied to c()
.:
setClassUnion("mySortOfThing",c("My_item","My_group"))
setMethod(
f = "c",
signature = "mySortOfThing",
definition = function(x, ..., recursive = FALSE) {
elements <- list(...)
return(elements)
}
)
But I find
g3 <- c(g1, a3)
R>str(g3)
List of 1
$ :Formal class 'My_item' [package ".GlobalEnv"] with 1 slot
.. ..@ contents: num [1:3] 0.1 0.2 0.3
I am obviously still getting something wrong.
Second edit: alan o'callaghan's suggestion solved the problem. For the record my method is now:
setMethod(
f = "c",
signature = "My_union",
definition = function(x, ..., recursive = FALSE) {
elements <- list(x, ...)
if (length(elements) != 0) {
items <- unlist(lapply(
elements,
FUN = function(object) {
if (is(object, "My_group")) {
return(getMy_group(object))
} else {
return(object)
}
}
))
object <- new("My_group",
members = items)
} else {
object <- new("My_group")
}
return(object)
}
)
That yields:
R>c(g1, a3)
An object of class "My_group"
Slot "members":
[[1]]
An object of class "My_item"
Slot "contents":
[1] 1 2 3
[[2]]
An object of class "My_item"
Slot "contents":
[1] "x""y""z"
[[3]]
An object of class "My_item"
Slot "contents":
[1] 0.1 0.2 0.3
which is exactly what I wanted.