I'm building an R package with S4 classes, and I'm having trouble with the new function. I have a class called Configs
setClass("Configs", 
  slots = list(
    burnin = "numeric",
    chains = "numeric",
    features = "numeric",
    iterations = "numeric",
    mphtol = "numeric",
    samples = "numeric",
    seed = "numeric",
    thin = "numeric",
    verbose = "numeric"
  ),
  prototype = list(
    burnin = 0,
    chains = 2,
    features = 5,
    iterations = 5,
    mphtol = 1e-4,
    samples = 3,
    seed = sample(1e6, 1),
    thin = 0,
    verbose = 0
  )
)
and when I load just this part into my global environment, I can create a new Configs object with slots different than the defaults.
> new("Configs", features = 1000)
An object of class "Configs"
Slot "burnin":
[1] 0
Slot "chains":
[1] 2
Slot "features":
[1] 1000
Slot "iterations":
[1] 5
Slot "mphtol":
[1] 1e-04
Slot "samples":
[1] 3
Slot "seed":
[1] 437211
Slot "thin":
[1] 0
Slot "verbose":
[1] 0
However, when I install the whole package, load it into a fresh environment, and run new("Configs", features = 1000), I get a features of 5. Why doesn't new() put values in slots anymore?
My package passed R CMD check without any errors, warnings, or notes. Here is my session info.
> sessionInfo()
R version 3.2.0 (2015-04-16)
Platform: x86_64-unknown-linux-gnu (64-bit)
Running under: CentOS release 6.6 (Final)
locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     
other attached packages:
[1] heterosis_0.0  pracma_1.8.3   MCMCpack_1.3-3 MASS_7.3-40    coda_0.17-1   
loaded via a namespace (and not attached):
[1] tools_3.2.0     grid_3.2.0      lattice_0.20-31
Edit: I got it, but I'm still not satisfied.
It turns out that my initialize function was causing problems.
setMethod("initialize", "Configs", function(.Object, ...){ 
#  .Object = new("Configs", ...)
  validObject(.Object)      
  return(.Object)
})
When I remove it, new puts things in slots again. I'm glad I found the problem, but I don't want to remove my initialize function entirely. I want a convenient way to call validObject and do other error checking too, and initialize seems like a proper and appropriate place to do it. And if I uncomment the commented line, I get an infinite recursion. How do I create a constructor without breaking new?
initialize() is dual-purpose -- initialization and copy construction. It's usually better (also more informative to the user) to provide an explicit constructor
.A = setClass("A", representation(x="numeric"))
A = function(x=numeric(), ...)
    .A(x=x, ...)
validOjbect() is called by the default initialize method when object creation involves slot assignment, so there's no need to call it explicitly during your own initialize method (see below); maybe you'd have
.A = setClass("A", representation(x="numeric"),
    prototype=prototype(x=NA_integer_))
setValidity("A", function(object) {
    if (length(object@x) != 1L)
        "'x' must be length 1"
    else TRUE
})
A = function(x=NA_integer_, ...)
    ## signature is informative -- 'x' is integer(1), not just '...'
    ## coercion (e.g., as.integer(), below) and other set-up
    new("A", x=as.integer(x), ...)
with
> A()
An object of class "A"
Slot "x":
[1] NA
> A(x=1)
An object of class "A"
Slot "x":
[1] 1
> A(x=1:2)
Error in validObject(.Object) : 
  invalid class "A" object: 'x' must be length 1
An important caveat is that the validity method is not called when there are no slots initialized by the user, so the prototype() has to be defined to create a valid object (verify this with validObject(new("A")).
For your question, the validity function is the correct place to do 'other error checking'. It's very hard to write a correct initialize method, but something closer to correct is
.B = setClass("B",
    representation(x="numeric", y="numeric"),
    prototype=prototype(x=NA_integer_, y=NA_real_))
setMethod("initialize", "B", 
    function(.Object, ..., x=.Object@x, y=.Object@y)
{
    ## pre-processing, then invoke 'next' initialize() method
    ## base initialize() creates the object then calls validObject()
    ## so no need for explicit test of validity
    .Object <- callNextMethod(.Object, ..., x=x, y=y)
    ## post-processing
    .Object
})
This sort of weird construction allows initialize() to continue to behave  as a copy constructor
> b = new("B", x=1, y=2)    # constructor
> initialize(b, x=2)        # copy-constructor
An object of class "B"
Slot "x":
[1] 2
Slot "y":
[1] 2
which is important during class inheritance. But as you can see this is quite tricky -- in the end it's really tough and seldom worth the effort to get initialize() correct.
Note that we haven't completely fulfilled the contract of initialize(), 
setClass("C", representation(x="numeric", y="numeric"))  # default initialize()
which actually acts as copy constructor when called with new()
> c = new("C", x=1, y=2)
> new("C", c, x=2)
An object of class "C"
Slot "x":
[1] 2
Slot "y":
[1] 2
versus no copy construction for B's implementation
> b = new("B", x=1, y=2)
> new("B", b, x=2)
An object of class "B"
Slot "x":
[1] 2
Slot "y":
[1] NA
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With