Debug in R #6: Debug in S4/R5 classes


S4/R5クラスのデバッグ

Rにビルトインされたオブジェクト指向的なフレームワークを提供する仕組みとして、S3以外に、S4とR5がある。大規模なパッケージを作るときとか、mutableな機能を実装するときにはとても役立つ。
あとR5はR5 reference classというように、参照渡しのセマンティックで実装されてるので、関数型言語らしさはなくなっちゃうんだけど、馬鹿でかいデータとかある時にはコピーが発生しないので、効率がよくなるはず。

ここらへんを使ってる人は、特に説明もいらないと思うので、デバッグのやり方は以下の実例で。ポイントだけ箇条書きします。

  • S4の場合、tracesignatureを渡す。
  • S4の場合、特定のインスタンスのメソッドだけデバッグする、というのは難しい(相当回りくどいことしないと無理だと思う)。
  • R5の場合、生成されたインスタンスのメソッドに対して普通の関数のようにdebugなり、traceなりで、デバッグできる。
  • R5の場合、S4とは逆に、あるクラスのメソッドを、どのインスタンスで呼ばれた場合にでもデバッグする、というのは難しい(試したら出来たけどbad hackっぽい)。

なお、既存のパッケージとかじゃなくて、開発中で自分でソースコードをいじれるなら、直接broweserを仕込むのが一番早いとおもう。

S4のデバッグ

クラス定義

## Articleクラスを定義
setClass("Article",
  representation(year = "integer", title = "character"),
  prototype(year = integer(0), title = character(0)) )

## ImpactFactor関数をS4 Genericとして宣言
setGeneric("ImpactFactor", function(v) standardGeneric("ImpactFactor"))

## Articleクラス用のImpactFactorを定義
setMethod("ImpactFactor", signature("Article"), 
  function(v) {
  	print(v@title)
  	print(v@year)
  	rgamma(1, 10/exp(v@year/1000), 1)
  })

実行例

> x <- new("Article", year=2000L, title="hoge") # Articleクラスのインスタンス生成

> x # 情報を表示
An object of class "Article"
Slot "year":
[1] 2000

Slot "title":
[1] "hoge"

> trace(ImpactFactor, browser, signature=signature("Article")) # ここがポイント。ArticleのImpactFactor関数にデバッグを仕込む。これは関数呼び出しの前なら、インスタンスを生成する前でも後でもいい。
[1] "ImpactFactor"
attr(,"package")
[1] ".GlobalEnv"

> IF <- ImpactFactor(x) # 関数呼び出し。
Tracing ImpactFactor(x) on entry # デバッグに入る。
Called from: eval(expr, envir, enclos)

Browse[1]> where # コールスタック。最後の方は、.doTrace内で生成されてるものなので、.doTraceまでを実効的なコールスタックと考えればいい。
where 1: eval(expr, envir, enclos)
where 2: eval(expr, p)
where 3: eval.parent(exprObj)
where 4: .doTrace(browser(), "on entry")
where 5: ImpactFactor(x)
where 6: ImpactFactor(x)

Browse[1]> n # 次の評価式
debug: {
    print(v@title)
    print(v@year)
    rgamma(1, 10/exp(v@year/1000), 1)
}
Browse[2]> c # 継続
[1] "hoge"
[1] 2000

> IF # ImpactFactor()関数の戻り値。あんまり高くない。
[1] 0.8207037

> untrace(ImpactFactor, signature=signature("Article")) # トレースの解除はsignature付きuntraceで。

R5リファレンスクラス

クラス定義

# 上のS4と同じようなクラス
RefArticle <- setRefClass("RefArticle",
  fields = list(year = "integer", title = "character"),
    methods = list(
      ImpactFactor = function() {
  	print(title)
  	print(year)
  	rgamma(1, 10/exp(year/1000), 1)
  }))

実行例

> x <- RefArticle$new(year = 2010, title = "boke") # インスタンス生成

> x # 情報を表示
An object of class "RefArticle"
<environment: 0x101c9cad0>

> debug(x$ImpactFactor) # デバッグを仕込む

> IF <- x$ImpactFactor() # 関数呼び出し
debugging in: x$ImpactFactor() # 普通にデバッグできる。
debug: {
    print(title)
    print(year)
    rgamma(1, 10/exp(year/1000), 1)
}
Browse[2]> n
debug: print(title)
Browse[2]> c
[1] "boke"
[1] 2010
exiting from: x$ImpactFactor()

> IF
[1] 0.2517559 # 全然だめ

> undebug(x$ImpactFactor) # undebugでデバッグの解除。

# traceする場合
> f <- x$ImpactFactor # インスタンスメソッドをグローバル変数に。ちょっとダサイけど、今のところ、こうするのが早い

> trace(f, browser, at=3) # それをtrace
Constructing traceable class "refMethodDefWithTrace"
Environment of class "refMethodDef" is locked; using global environment for new class
[1] "f"

> x$ImpactFactor() # こっちだとトレースが入らない
[1] "boke"
[1] 2010
[1] 0.234861

> f() # こっちだとトレース入る
[1] "boke"
Tracing f() step 3 
Called from: eval(expr, envir, enclos)
Browse[1]> n
debug: print(year)
Browse[2]> n
[1] 2010
debug: rgamma(1, 10/exp(year/1000), 1)
Browse[2]> c
[1] 1.412381

最後の例ですが、fから呼んでも、ちゃんともとのインスタンス(x)を参照してます。もしImpactFactorの中でフィールドの変更が起こる場合には、ちゃんとxのフィールドが書き換わります。多分。

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s