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>