1

I have two simple classes:

.A1 <- setClass("A1", 
            representation=representation( x1 ="numeric"),
            prototype = prototype(x1 = 10))

.A2 <- setClass("A2", contains="A1",
           representation=representation(x2="numeric"),
           prototype = prototype(x2 = 10))

setMethod("initialize", "A2",
      function(.Object, ..., x2 = .Object@x2)
      {
        callNextMethod(.Object, ..., x2 = x2)
      })

Using this code everything works:

a1 <- .A1(x1 = 3)
initialize(a1)

a2 <- .A2(x1 = 2, x2 = 4)
initialize(a2, x2 = 3)

.A2(a1, x2 = 2)

An object of class "A2"  # WORKS!
Slot "x2":
[1] 2

Slot "x1":
[1] 3

In particular the last line work, so a1 gets copied inside the "A2" object. The problem is that if define "initialize" also for the base class the last line doesn't work anymore:

setMethod("initialize", "A1",
      function(.Object, ..., x1 = .Object@x1)
      {
        callNextMethod(.Object, ..., x1 = x1)
      })

## I have to redefine also this initializer, otherwise it will call
## the default initializer for "A1" (it was stored in some lookup table?)
setMethod("initialize", "A2",
      function(.Object, ..., x2 = .Object@x2)
      {
        # your class-specific initialization...passed to parent constructor
        callNextMethod(.Object, ..., x2 = x2)
      })

And now I get:

.A2(a1, x2 = 2)
An object of class "A2"  # BAD!
Slot "x2":
[1] 2

Slot "x1":
[1] 10

I guess there is something wrong with my initializer of "A1", any ideas? Thanks!

Matteo Fasiolo
  • 541
  • 6
  • 17

1 Answers1

2

The A1 initialize method mis-behaves because x1 = .Object@x1 consults .Object, which for the constructor is the prototype of the class (for your example .A2(a1, x2=2), in the initialize,A1-method .Object is constructed from the prototype of A2, so x1 is assigned 10, and x1 = .Object@x1 = 10 over-writes the value provided by a1.

It's hard to know what a general solution is. You could test for missing-ness

setMethod("initialize", "A1", function(.Object, ..., x1) {
    if (!missing(x1))
        callNextMethod(.Object, ..., x1=x1)
    else 
        callNextMethod(.Object, ...)
})

or do something clever, maybe with match.call, to avoid a combinatorial problem with more than a couple of slots.

Another approach, which seems to be the one adopted in practice even though it really just side-steps the problem, is to avoid using an initialize method, and instead rely on a separate constructor to do any data massaging, as in the first code box of the Retaining copy construction section of this answer.

Community
  • 1
  • 1
Martin Morgan
  • 45,935
  • 7
  • 84
  • 112
  • Thanks a lot for your answer, I couldn't figure out what was going on. My solution has been to avoid defining the "initialize" generic for any of the two classes, and to initialize everything from within the prototypes. In my actual classes there are a lot of slots, so relying on the default initializer seemed to be the easiest thing to do to me. I have little experience in OOP, but isn't a bit odd that it necessary to rely on a lot of tricks to do something that is quite reasonable as the above? Thanks a lot for all your answer on S4, they are great learning resources! – Matteo Fasiolo Aug 16 '13 at 19:47
  • Yes, it's a bit odd! Probably the contract set out for initialize is too complicated. – Martin Morgan Aug 16 '13 at 21:00