4

The puzzle is accessing elements of a list in the object's "pseudo-slot".

That's successful using 2 out of 4 approaches one might try:

setClass("TempA", contains="list")
A = new("TempA", list(a=1,b=2))
A   

Just printing A does not show the list names.

## An object of class "TempA"
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2

Nevertheless, you can extract the elements by name.

A[["b"]]  
## [1] 2

And names() extracts the names.

names(A) 
## [1] "a" "b"

But there are no names here in the pseudo-slot.

A@.Data  
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2

So where are the names hiding, if not in the pseudo-slot itself?

The plot thickens. My goal is to subclass (to add some slots; not shown here). But if we subclass, even the two successful approaches above now fail. The list's names are apparently nowhere.

setClass("TempB", contains="TempA")
B = new("TempB", list(a=1,b=2))
names(B) ## no names.
## NULL
B[["b"]] ## NULL
## NULL

Here's a different approach. Does this do it? Nope.

B2 = new("TempB", new("TempA", list(a=1,b=2)))
B2[["a"]]  # NULL
## NULL
names(B2) # NULL
## NULL
names(as(B2, "TempA"))  ## still no dice
## NULL

In summary, when the pseudo-slot is a named list, trying to view or use those names is successful for only 2 out of 4 obvious approaches, and zero out of the 4 after subclassing. Working around the problem is not the issue; that's pretty easy. (Though I'd like to know how to write an accessor for a TempB object using the names.) I just want to understand.

Roger
  • 422
  • 4
  • 12

3 Answers3

2

S4 implements slots as attributes, while R stores names of list elements as an attribute on the list. There is thus a conflict, mentioned in ?Classes. The 'solution' is to create a class with a 'names' slot

A = setClass("A", representation("list", names="character"))

but this also requires explicit management of the names, e.g.,

setMethod("[", c("A", "ANY", "missing", "missing"),
    function(x, i, j, ..., drop=TRUE)
{
    initialize(x, x@.Data[i], names=names(x)[i], ...)
})

Leading to

> a = A(list(a=1, b=2))
> a[2:1]
An object of class "A"
[[1]]
[1] 2

[[2]]
[1] 1

Slot "names":
[1] "b" "a"

but also obviously incomplete

> a[20]
An object of class "A"
[[1]]
NULL

Slot "names":
[1] NA
Martin Morgan
  • 45,935
  • 7
  • 84
  • 112
0

Ah, Martin, your answer led me to some discoveries that surprised me! Thank you. Pointing me to look at the instance's attributes was key. I'd missed that paragraph in ?Classes.

The following shows that the names attribute of the list in the .Data slot transfers to the instance itself:

attributes(A)$names
## [1] "a" "b"

So, do ALL attributes move from the .Data slot to the instance? Yes indeed!

tempList = list(a=3, b=4)
attributes(tempList)$dummy = "dummy"
E = new("TempA", tempList)
attributes(E)$names
## $names
## [1] "a" "b"
## 
attributes(E)$dummy
## $dummy
## [1] "dummy"
attributes(E@.Data)
## NULL 

Well, not all attributes. The results with object B2 above in the original question show that, if the .Data item is itself an instance, its attributes do not transfer to the containing instance.

That still leaves open a question. Of course you don't want to transfer the $class attribute! But why not transfer all the other attributes?

Roger
  • 422
  • 4
  • 12
0

I think I have a solution for your problem and the clue was to set the contains of the child properly. The parent "A" class has to be first one and the namedList has to be repeated.

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

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


obj <- new("A", list(t = 2))

obj2 <- new("B", list(t = 3))

obj[["t"]]
# 2
obj2[["t"]]
# 3

is(obj, "list")
# TRUE
is(obj2, "A")
# TRUE
is(obj2, "list")
# TRUE
polkas
  • 3,797
  • 1
  • 12
  • 25