rr-s4

Assign S4 slot within loop


How can assign values for S4 slot, based on predefined lists?

B1 class created as:

mySlots <- c("myA", "c")
myTypes <- c("numeric", "character")
myVals <- c(1222, "blabla")
  
mySlotTypes <- setNames(as.list(myTypes), as.list(mySlots))
mySlotValues <- setNames(as.list(myVals), as.list(mySlots))
    
setClass("B1",
    do.call("representation", mySlotTypes)
)

Manually slot can assign with:

> x <- new("B1")
> x@myA <- 2333
> x@c <- "ddd"
> str(x)
Formal class 'B1' [package ".GlobalEnv"] with 2 slots
  ..@ myA: num 2333
  ..@ c  : chr "ddd"

I tried two options:

Option A

Trying to assign with prototype:

  setClass("B1",
                 do.call("representation", mySlotTypes),
                 do.call("prototype", mySlotValues)
  )  

what gave error:

Error in makePrototypeFromClassDef(properties, ClassDef, immediate, where) : in making the prototype for class “B1” elements of the prototype failed to match the corresponding slot class: myA (class "numeric" )

Option B

Tried with initialize method without luck as well:

  setMethod("initialize", signature  = "B1",
    definition = function (.Object) {
        for(i in slotNames(.Object)) {
            do.call("<-", list( str_c(".Object@", i), mySlotValues[[i]]))
         }
    return (.Object)
    })

This solution doesn't gave error, but not initalized at all:

>   x <- new("B1")
>   str(x)
Formal class 'B1' [package ".GlobalEnv"] with 2 slots
  ..@ myA: num(0) 
  ..@ c  : chr(0)

Is there any simple solution, how can replace the manual .Object@myA <- 1222 call with loop friendly assignement?


Solution

  • If you look at the ?slot help page (linked from the ?"@" help page), it appears you can do something like this:

    slot(.Object, i) <- mySlotValues[[i]]
    

    Generally concatenating code as a string (as you've attempted with str_c(".Object@", i)) doesn't work unless you use eval(parse(...)), on the resulting string. This is best avoided. (In this case since you're assigning to the parsed string, you'd further need to use assign() or include the assignment as part of the string.)

    Your do.call is a good idea and is pretty close. It would probably also work if you used the function "@<-" instead of just <-. Assigning to indices/items/slots have their own functions combining the accessing operator (like $, [, [[, names() or in this case @) with the assignment. Using do.call on the @<- function would look like this, and would also work, but it's a bit harder to read.

    do.call("@<-", list(object = ".Object", name = i, value = mySlotValues[[i]]))