Debug in R #5: Debug in S3 methods


S3メソッドのデバッグ

S3ジェネリック

S3ジェネリックは、オブジェクト指向っぽい簡単なメソッドディスパッチの機能を提供する。詳細は、?S3Methodsとか、?methodsとかで。

普段使ってる関数の多くが、S3メソッドとして実装されてる。例えば、plot:

実行例

> methods(plot) # methodsで、その名前のS3メソッドの一覧を表示
 [1] plot.acf*           plot.data.frame*    plot.decomposed.ts* plot.default        plot.dendrogram*    plot.density       
 [7] plot.ecdf           plot.factor*        plot.formula*       plot.hclust*        plot.histogram*     plot.HoltWinters*  
[13] plot.isoreg*        plot.lm             plot.medpolish*     plot.mlm            plot.ppr*           plot.prcomp*       
[19] plot.princomp*      plot.profile.nls*   plot.spec           plot.spec.coherency plot.spec.phase     plot.stepfun       
[25] plot.stl*           plot.table*         plot.ts             plot.tskernel*      plot.TukeyHSD      

   Non-visible functions are asterisked

これだけの関数が用意されてる。*付きのものに関してはあとで説明する。当のplotの実装はというと、

function (x, y, ...) 
{
    if (is.function(x) && is.null(attr(x, "class"))) {
# ... snip ...
    }
    else UseMethod("plot") # これがポイント。
}
<environment: namespace:graphics>

UseMethod("plot")がポイントで、これによってクラス名を用いたディスパッチを行う。

例えば、lmの場合:

> v <- lm(I(1:10) ~ I(10:1))
> class(v)
[1] "lm"

と言うように、classlmが設定されてるので、plot(v)は最終的にplot.lmを呼び出すことになる。

S3メソッドのデバッグは簡単で、

  1. S3ジェネリックの名前(e.g., plot)でデバッグ
  2. S3メソッド名(e.g., plot.lm)でデバッグ
  3. S3メソッドがパッケージ内の隠し関数だった場合

の3つについて説明する。これで十分だと思う。

S3ジェネリックの名前でデバッグ

例えば、debug(plot)とするのが一番、直感的だと思う。

> debug(plot) # plotをデバッグ

> plot(1:10) # 呼び出し
debugging in: plot(1:10) # plotのデバッグに入る
debug: {
    if (is.function(x) && is.null(attr(x, "class"))) {
# ... snip ...
    else UseMethod("plot") # これはS3ディスパッチの呼び出し
}
Browse[2]> where # コールスタック
where 1: plot(1:10)

Browse[2]> c # 継続してみると
debugging in: plot.default(1:10) # もう一度デバッグに入る。これはS3ディスパッチで呼び出されたplot.defaultであることに注意。
debug: {
    localAxis <- function(..., col, bg, pch, cex, lty, lwd) Axis(...)
# ... snip ...
    invisible()
}

Browse[2]> where # コールスタック
where 1: plot.default(1:10)
where 2: plot(1:10)

Browse[2]> c
exiting from: plot(1:10)

debug(XXX)で、XXXがS3ジェネリックの場合、XXXのメソッド全ての呼び出しでデバッグに入る。これでもいいんだけど、メソッドディスパッチの連鎖が多くなると、目的のメソッドを見つけるのが難しい。

S3メソッド名前でデバッグ

例えば、lmクラスのプロットについてデバッグしたいとする。

> v <- lm(I(1:10) ~ I(10:1)) # vはlmクラスのオブジェクト

> debug(plot.lm) # plot.lmにデバッグをかける

> plot(v) # S3ジェネリックで呼び出し
debugging in: plot.lm(v) # いきなりplot.lmのデバッグに入る
debug: {
    dropInf <- function(x, h) {
# ... snip ...
    invisible()
}
Browse[2]> where # コールスタック
where 1: plot.lm(v)
where 2: plot(v)

と、S3メソッド名(XXX.class)をdebugの引数に直接指定するだけ。

パッケージ内の隠し関数をデバッグ

S3メソッドは、暗黙のうちに呼び出されるのでパッケージからエクスポートされない場合が多々ある。

> methods(plot)
 [1] plot.acf*           plot.data.frame*    plot.decomposed.ts* plot.default        plot.dendrogram*    plot.density       
 [7] plot.ecdf           plot.factor*        plot.formula*       plot.hclust*        plot.histogram*     plot.HoltWinters*  
[13] plot.isoreg*        plot.lm             plot.medpolish*     plot.mlm            plot.ppr*           plot.prcomp*       
[19] plot.princomp*      plot.profile.nls*   plot.spec           plot.spec.coherency plot.spec.phase     plot.stepfun       
[25] plot.stl*           plot.table*         plot.ts             plot.tskernel*      plot.TukeyHSD      

   Non-visible functions are asterisked

この中で*印は、エクスポートされてないので、直接呼び出すことはできない。

> z <- acf(rnorm(10), plot=F) # zはacfクラス

> plot.acf(z) # 失敗する
 エラー:  関数 "plot.acf" を見つけることができませんでした  

> plot(z) # こっちは成功する

エクスポートされてない関数とか変数にアクセスするには、packagename:::functionの書式を使う。コロンは3つ。でもその前に、「plot.acfってどのパッケージなん?」ってなる。エクスポートされてないやつがどのパッケージ所属か知るには、

> getAnywhere(plot.acf)$where
[1] "registered S3 method for plot from namespace stats" "namespace:stats"                                   

とするとstatsだということがわかる。あとは、普通のS3メソッドと同じように、debug(stats:::plot.acf)とすれば良い。

> debug(plot.acf) # これは失敗する。
 以下にエラー debug(plot.acf) :  オブジェクト 'plot.acf' がありません 

> debug(stats:::plot.acf) # `:::`を使ってパッケージ名を明示的に指定。

> plot(z) # S3ジェネリックで呼び出し。
debugging in: plot.acf(z) # 直接plot.acfのデバッグに入る。
debug: {
    ci.type <- match.arg(ci.type)
# ... snip ...
    invisible()
}

Browse[2]> where # コールスタック
where 1: plot.acf(z)
where 2: plot(z)

Browse[2]> c # 継続
exiting from: plot.acf(z)

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