S4クラスを継承する時に気を付けること

背景

S4クラスメカニズムでは、setMethod関数のcontains引数に親クラスの名前を含める事で継承を実現できる。で、こんなコードを書くとエラーになってしまった。

setClass("A", representation(a="numeric"))
setMethod("initialize", "A",
          function(.Object, a){
            .Object@a <- a
            .Object
          })

setClass("B", representation(b="numeric"),
         contains="A")
# => Error in .local(.Object, ...) : argument "a" is missing, with no default

取り敢えずの解決策

こういう事をしたければ

setClass("A", representation(a="numeric"))
setMethod("initialize", "A",
          function(.Object, ...){
            args <- list(...)
            if(!is.null(args$a)) .Object@a <- args$a
            .Object
          })

setClass("B", representation(b="numeric"),
         contains="A")

こう書くのが筋であるっぽい。ポイントは、

  • S4クラスのinitialize関数の引数は(.Object, ...)という形式が標準的
  • 常にnew("ClassName")という式が評価出来るべき

という所。
これを守らないと色んな所でコケてしまうらしい。何でこういう風になっているのか、に関しては全く不明。参考ページへ

ついでに親クラスのコンストラクタの呼び方

子クラスの initialize 時に親クラスの initialize を呼ぶには callNextMethod を使う。

setMethod("initialize", "B",
          function(.Object, ...){
            args <- list(...)
            if(!is.null(args$b)) .Object@b <- args$b
            callNextMethod()
          })

new("B", a=1, b=2)
## An object of class "B"
## Slot "b":
## [1] 2

## Slot "a":
## [1] 1