12

I have some experience working with S4 objects and their slots, so I know how to access specific slots and sub-slots. What I'd like to learn is how to "de-slotify" an object in the way that unlist takes apart an S3 list.
My immediate goal is to have an S4 counterpart to one of my toys which returns the number of elements of an object:

lssize<-function(items){
            if (any(sapply(sapply(items,get),typeof)=='closure')){
        warning('Closures in list, will ignore.')
        items<-items[(sapply(sapply(bar,get),typeof)=='closure')!=TRUE]
    }
    sizes<-sapply(sapply(sapply(sapply(items,get,simplify=F), unlist,simplify=F), as.vector,simplify=F), length)
    return(sizes)
    }

(no fair laughing at my code :-) ). I am hoping not to have to write some recursion routine which extracts slots one at a time to convert them.

Edit: I know object.size will return the bytecount; not what I'm after here.

Karsten W.
  • 17,826
  • 11
  • 69
  • 103
Carl Witthoft
  • 20,573
  • 9
  • 43
  • 73
  • 1
    You might want to have a look at `str()` (`utils:::str.default` really) -- both its results when applied to S4 objects, and the code it uses to walk through all of an S4 object's slots. – Josh O'Brien Feb 10 '13 at 23:15

1 Answers1

9

(This is revised to be closer to a previous, deleted answer, using slotName and slot rather than relying on attributes). We could write a function that tests whether an instance is an S4 object, and if so extracts all the slots as a list and recurses

f = function(x) {
    if (isS4(x)) {
        nms <- slotNames(x)
        names(nms) <- nms
        lapply(lapply(nms, slot, object=x), f)
    } else x
}

and then

A = setClass("A", representation(x="numeric"))
B = setClass("B", representation(a="A", b="numeric"))
f(B())

to arrive at a plain old list that we could use for whatever purposes we want.

$a
$a$x
numeric(0)

$a$class
[1] "A"
attr(,"package")
[1] ".GlobalEnv"


$b
numeric(0)

$class
[1] "B"
attr(,"package")
[1] ".GlobalEnv"

f might need to be enhanced, e.g., to handle NULL values or S4 classes made from S3 classes via setOldClass. The code to validObject would be my choice of places to look for a more comprehensive traversal.

A generalization might make a visitor, along the lines of

visitLeavesWith <-
    function(object, FUN, ...)
{
    f = function(x) {
        if (isS4(x)) {
            slots <- setNames(slotNames(x), slotNames(x))
            lapply(lapply(slots, slot, object=x), f)
        } else FUN(x, ...)
    }
    f(object)
}

e.g.,

visitLeavesWith(B(), length)
Martin Morgan
  • 45,935
  • 7
  • 84
  • 112
  • This looks nice -- a good enhancement of the (removed) other answer. Yeah, I probably should "convert" to reporting sizes of objects in bytes rather than element counts anyway :-) – Carl Witthoft Feb 11 '13 at 01:41
  • I only get to upvote you once :-) . Thanks for taking the time to spiffify your code. I'll give you full credit when (if) I build a package of helper tools which include `lssize` . – Carl Witthoft Feb 11 '13 at 13:20
  • BTW, so I credit the right guy -- are you the fellow who works at Fred H. ? – Carl Witthoft Feb 11 '13 at 14:03
  • A thought rather later on: for my "get the length" function, I need to make that `else FUN(x,...)` be `else length(unlist(x))` to cover the possibility of a list object. Obvious in hindsight :-) – Carl Witthoft Oct 17 '13 at 15:23