vctrs::vec_method_register()が必要な理由

これがなぜ必要か、という話をします。

独自のパッケージに独自のクラスをつくったとき、必須ではないけど合わせて使うと便利、みたいなパッケージにS3メソッドを提供することが人間だれしもありますよね。ちなみに私は、まだないです。

具体的には、

  • ggplot2パッケージでいい感じにプロットできるようにfortify()とかautoplot()とかautolayer()を実装する
  • tibbleのカラムとしての表示がいい感じになるようにpillarパッケージのpillar_shaft()を実装する
  • 結果をtidyに変換できるようにbroomパッケージのtidy()を実装する

みたいな感じのときです。通例、これらはパッケージに必ずしも必要ではないので、ImportsではなくSuggestsに書くことになります(後述)。

何が問題か(R 3.5.0より前)

R 3.5.0時点では、S3メソッドの登録はパッケージをアタッチ(つまりlibrary()とかrequire())した時点で読み込まれるので、::でパッケージをアタッチせずに使っているとS3メソッドはグローバル環境からは見えません。 そのため、パッケージ独自のメソッドがうまくディスパッチされません。↓このissueがわかりやすいです。

このため、.onLoad()にこういうやつを仕込むことでS3メソッドを動的に登録していました(.onLoad()はパッケージがロードされた時点、つまり::で中の関数を呼び出しても実行される)。

このregister_s3_method()というやつが今のvctrs::s3_register()の原型になっているものです。

何が問題か(R 3.5.0以降)

さて、実は上の問題は、R 3.5.0からS3メソッドディスパッチの仕組みがちょっと変わったことですでに解決しています。 何が変わったかというと、S3メソッドの登録はアタッチ時ではなくロード時になったのです。 これによって、パッケージをlibrary()require()せずに::で関数を呼び出すようなときにも適切にメソッドディスパッチが行われるようになりましたとさ。

めでたしめでたし...かと思いきや、今度はまた別の問題があります。

上に「通例、これらはパッケージに必ずしも必要ではないので、ImportsではなくSuggestsに書くことになります」と書きましたが、Suggestsのパッケージは必ずしもインストールされているとは限りません。 インストールされていないかもしれないパッケージのS3総称関数に対してメソッドをS3method()するのがR 3.5.0からは許されなくなりました。ということで、やはり動的に登録する必要があるのです。

参考:R 3.6.0?以降

これに対応するためにR本体に入ったのが、「delayed S3 method registration」という仕組みです。NAMESPACE

if(getRversion() >= "3.6.0") {
    S3method(pkg::gen, cls)
}

のように書けば、pkgが読み込まれた時だけS3メソッドを登録することができます。これによって、完全に問題が解決することになります。

ただしまあ、R 3.5系以前では変わらず問題は起こるわけなので、数年はvctrs::s3_register()を使っておいた方が無難でしょう。

おまけ

あと、パッケージ外でS3メソッドをつくるとき、具体的にはvignette内で例示的にクラスを作って見せるときなんかも必要になるらしいです。 これは私はまだちょっとピンときていませんが、まあまたトラブってから学ぶことになるのでしょう(つらそう)。

For R 3.5.0 and later, s3_register() is also useful when demonstrating class creation in a vignette, since method lookup no longer always involves the lexical scope. (Register a method for a suggested dependency — s3_register • vctrs)

まとめ

ということで、vctrs::s3_register()のマニアックな紹介でした。たぶんこの知識を必要としてる人はほとんどいないと思いますが、そのうち困ったらこの記事のことを思い出してみてください。