Defining default field values for instances of S4 Reference Classes
Asked Answered
M

1

6

How can I define the fields' default values of S4 Reference Classes instances?

For regular S4 Classes, there's the prototype argument:

setClass("Test_1",
    representation(
        x.1="numeric", 
        x.2="logical", 
        x.3="matrix"
    ),
    prototype=list(
        x.1=10, 
        x.2=FALSE,
        x.3=matrix(0,0,0)
    )
)

> new("Test_1")
An object of class "Test_1"
Slot "x.1":
[1] 10

Slot "x.2":
[1] FALSE

Slot "x.3":
<0 x 0 matrix>

As far as I understand the help page of setRefClass, this should also work for S4 Reference Classes via the ... argument. The respective section says:

... other arguments to be passed to setClass.

Yet prototype does not seem to be dispatched to setClass correctly:

gen <- setRefClass("Test_2",
    fields=list(
        x.1="numeric", 
        x.2="logical", 
        x.3="matrix"
    ),
    prototype=list(
        x.1=10, 
        x.2=FALSE,
        x.3=matrix(0,0,0)
    )
)

> gen$new()
Reference class object of class "Test_2"
Field "x.1":
numeric(0)
Field "x.2":
logical(0)
Field "x.3":
<0 x 0 matrix>

Alternatively

> new("Test_2")
Reference class object of class "Test_2"
Field "x.1":
numeric(0)
Field "x.2":
logical(0)
Field "x.3":
<0 x 0 matrix>

I didn't find anything else related to default values/prototypes on the help page of setRefClass.

Is this a bug or am I missing something obvious here?


EDIT

The closest I can find that would help me state default values is $initFields().

This is what ?setRefClass has to say:

Initialize the fields of the object from the supplied arguments. This method is usually only called from a class with a $initialize() method. It corresponds to the default initialization for reference classes. If there are slots and non-reference superclasses, these may be supplied in the ... argument as well.

Typically, a specialized $initialize() method carries out its own computations, then invokes $initFields() to perform standard initialization, as shown in the matrixViewer class in the example below.

So far, so good

gen <- setRefClass("Test_3",
    fields=list(
        x.1="numeric", 
        x.2="logical", 
        x.3="matrix"
    ),
    methods=list(
        initialize=function(
            ...
        ) {
            .self$initFields(x.1=10, x.2=TRUE, x.3=matrix(0,0,0), ...)
        }
    )
)

Works a treat for the "default intialization case":

> gen$new()
Reference class object of class "Test_3"
Field "x.1":
[1] 10
Field "x.2":
[1] TRUE
Field "x.3":
<0 x 0 matrix>

But if fails to handle situations where (some) field values are explicitly specified at initialization:

> gen$new(x.1=100)
Reference class object of class "Test_3"
Field "x.1":
[1] 10
Field "x.2":
[1] TRUE
Field "x.3":
<0 x 0 matrix>

Workaround

Really dirty, but it works

gen <- setRefClass("Test_4",
    fields=list(
        x.1="numeric", 
        x.2="logical", 
        x.3="matrix"
    ),
    methods=list(
        initialize=function(
            ...
        ) {
            defaults <- list(
                x.1=10,
                x.2=FALSE,
                x.3=matrix(0,0,0)
            )
            if (!missing(...)) {
                x.args      <- list(...)             
                specified   <- names(x.args)
                idx <- which(specified %in% names(defaults))
                if (length(idx)) {
                    for (ii in specified[idx]) {
                        defaults[[ii]] <- x.args[[ii]]
                    }
                }
            }    
            .self$initFields(x.1=defaults$x.1, x.2=defaults$x.2, 
                x.3=defaults$x.3, ...)
        }
    )
)

Intialization

> gen$new()
Reference class object of class "Test_4"
Field "x.1":
[1] 10
Field "x.2":
[1] FALSE
Field "x.3":
<0 x 0 matrix>

> gen$new(x.1=100)
Reference class object of class "Test_4"
Field "x.1":
[1] 100
Field "x.2":
[1] FALSE
Field "x.3":
<0 x 0 matrix>
> gen$new(x.1=100)

That's what I'm looking for, but I'm sure there's something more "built-in"?


EDIT 2

The whole thing a bit more generic. Method ensureDefaultValues could be a method of a class that each other class inherits from. For classes "further down the inheritance path", this method could simply be called within the intialize method:

gen <- setRefClass("RootClass",
    methods=list(
        ensureDefaultValues=function(values, ...) {
            if (!missing(...)) {
                arguments   <- list(...)             
                specified   <- names(arguments)
                idx <- which(specified %in% names(values))
                if (length(idx)) {
                    for (ii in specified[idx]) {
                        values[[ii]] <- arguments[[ii]]
                    }
                }
            }    
            temp <- paste(paste0(names(values), "=values$", 
                names(values)), collapse=", ")
            eval(parse(text=paste0(".self$initFields(", temp, ", ...)")))
            return(TRUE)            
        }
    )
)

gen <- setRefClass("Test_5",
    contains="RootClass",
    fields=list(
        x.1="numeric", 
        x.2="logical", 
        x.3="matrix"
    ),
    methods=list(
        initialize=function(
            ...
        ) {
            .self$ensureDefaultValues(
                values=list(
                    x.1=10,
                    x.2=FALSE,
                    x.3=matrix(0,0,0)
                ),
                ...
            )
            return(.self)
        }
    )
)
> gen$new()
Reference class object of class "Test_5"
Field "x.1":
[1] 10
Field "x.2":
[1] FALSE
Field "x.3":
<0 x 0 matrix>

> gen$new(x.1=100)
Reference class object of class "Test_5"
Field "x.1":
[1] 100
Field "x.2":
[1] FALSE
Field "x.3":
<0 x 0 matrix>
Mandolin answered 22/11, 2012 at 16:45 Comment(0)
O
12

I don't think the notion of prototype is intended to be implemented for reference classes. A simple approach is to provide defaults in the argument to initialize, as

Test_1 <- setRefClass("Test_1",
    field = list(x.1="numeric", x.2="logical", x.3="matrix"),
    method = list(initialize =
      function(..., x.1 = 10, x.2 = FALSE, x.3 = matrix(0, 0, 0))
    {
        callSuper(..., x.1 = x.1, x.2 = x.2, x.3 = x.3)
    })
)

The generator function (using syntax available in R-devel) can then be invoked to return

> Test_1()
Reference class object of class "Test_1"
Field "x.1":
[1] 10
Field "x.2":
[1] FALSE
Field "x.3":
<0 x 0 matrix>
Okechuku answered 22/11, 2012 at 19:12 Comment(1)
It seems a bit surprising that those S4 features didn't make it into RCs. I have a similar question with validity (+1).Injun

© 2022 - 2024 — McMap. All rights reserved.