個人的な見解を書いておきます。
まず、パッケージの中で別パッケージの関数を使うにはどうすればいいか、おさらいしてみましょう。
DESCRIPTION
ファイル
まずは、install.packages()
によって依存するパッケージが自動でインストールされるようにするため、DESCRIPTION
ファイルに必要なパッケージは列挙する必要があります。
オフィシャルな説明はWriting R Extensions - 1.1.3 Package Dependenciesあたりに書かれています。パッケージの依存関係を記述するフィールド6つ定義されていますが、主に使うのは以下の3つです。
Depends
: パッケージ全体をアタッチする必要がある*1、もしくはアタッチした方がいい(後述)パッケージはここに書きます。自動でインストールされます。Imports
: そのパッケージの関数を使うだけでよければここに書きます。自動でインストールされます。Suggests
: そのパッケージの関数を使うだけでよく、なおかつパッケージがインストールされていなくても問題ない(例やテストでしか使わない、インストールされていなければ別の処理を行う、など)ならここに書きます。dependencies = TRUE
を指定しないとインストールされません。
大概の場合、欲しいのはパッケージ読み込み時の効果ではなくその中に定義されている関数なので、Imports
が適切でしょう。
また、Depends
は自動でパッケージを読み込んでくれるので便利なように思えますが、結局、安全に関数をインポートするにはNAMESPACE
ファイルにimportFrom
(もしくはimport
、もしくは::
を使う)しないといけません。ちょっと回り道してそのあたりを見てみましょう。
Depends
の自動読み込みでは安心できない
この記事を書くために調べていて以下の質問を見つけました。
曰く、Depends
はパッケージを読み込んでくれるので、必要な関数がサーチパス上にあることは保証してくれますが、必ずそれが選ばれることは保証してくれません。
具体例として、magrittrパッケージの関数extract()
を内部で使う関数をもつパッケージをつくってみました。
具体的にはこういう関数です。x
から要素の1つ目を取り出します。
#' @export take1 <- function(x) { extract(x, 1) }
DESCRIPTION
ファイルにはこう書いておきます。
Depends:
magrittr
すると、以下は意図通り動きます。
# インストール devtools::install_github("yutannihilation/dependencyA") library("dependencyA") take1(1:10) #> [1] 1
しかし、ご存知の通りRでは後から読み込まれたパッケージの方が優先されます。ここで同じextract()
という名前の関数を持つtidyrパッケージを読み込んでもう一度やってみましょう。
library("tidyr") take1(1:10) #> Error: Invalid column specification
エラーになってしまいました。これはmagrittrのextract()
ではなくてtidyrのextract()
が呼ばれてしまうためです。
::
を使う
解決策としては、::
を使ってパッケージを明示するというのがひとつの手です。先ほどの関数をこう書き換えれば、tidyrパッケージが読み込まれた後も正しくmagrittrパッケージのextract()
の方が使われます。
#' @export take1 <- function(x) { magrittr::extract(x, 1) }
ただし、これは関数ひとつなのでいいですが、毎回::
を書くのはだるい、という問題があります。
あと、些細な問題ではありますが、::
を使うと5ナノマイクロ秒(追記:コメント欄で指摘いただきました。すみません…)程度のオーバーヘッドがあるためです。これはR Packagesでも言及されています。
There’s also a minor performance penalty associated with
::
(on the order of 5µs, so it will only matter if you call the function millions of times). (http://r-pkgs.had.co.nz/description.html)
なぜこのオーバーヘッドがあるかというと、::
も関数だからです。この関数呼び出しのオーバーヘッドがあります(ということだと思ってるんですけど違ったら誰かツッコミを…)
`::` #> function (pkg, name) #> { #> pkg <- as.character(substitute(pkg)) #> name <- as.character(substitute(name)) #> getExportedValue(pkg, name) #> } #> <bytecode: 0x000000000be7f658> #> <environment: namespace:base>
完全に余談ですが、::
が関数だというのは%>%
を使うときにたまにひっかります。こういうエラーに遭遇したことはないでしょうか?
iris %>% utils::head #> Error in .::utils : unused argument (head)
これは、%>%
によってiris
が引数として挿入される先がhead()
ではなく::
になっているからです。つまり上では、
`::`(iris, utils, head)
という関数呼び出しが実行されようとして、引数がひとつ多いのでエラーになっています。エラーにならないようにするにはutils::head
ではなくutils::head()
とする必要があります。
NAMESPACE
ファイルにimportFrom
を書く
話がそれました。::
と毎回書くのがだるい、という話でした。
他の手として、NAMESPACE
ファイルにimportFrom
を書くという手があります。こうです。
importFrom(magrittr,extract)
(https://github.com/yutannihilation/dependencyA/blob/use-imports/NAMESPACE#L4)
NAMESPACE
ファイルをroxygen2を任せているなら、Rファイルのどこかに@importFrom
ディレクティブを書いておけばインポートできます。
#' @importFrom magrittr extract
(https://github.com/yutannihilation/dependencyA/blob/use-imports/R/take1.R#L1)
これを書いておくと、ひとつ上の親環境にextract
が見つかるようになります。ユーザがlibrary()
でパッケージを読み込んでもこっちの方が優先順位が上なので安心です。
library("dependencyA") e <- environment(take1) e #> <environment: namespace:dependencyA> pe <- parent.env(e) pe #> <environment: 0x0000000014e558b0> #> attr(,"name") #> [1] "imports:dependencyA" ls(pe) #> [1] "extract"
ということでDepends
じゃなくてImports
の方がいい
ということで、::
かimportFrom
を書く必要はあるので、Depends
を使うメリットはあまりありません。変にパッケージを読み込んでグローバル環境を汚染するより、Imports
にしておく方が無難でしょう。
ちなみに、Depends
については最近はあまり使われてない、と断りつつ、数少ないメリットについて言及しています。
Field ‘Depends’ should nowadays be used rarely, only for packages which are intended to be put on the search path to make their facilities available to the end user (and not to the package itself): for example it makes sense that a user of package latticeExtra would want the functions of package lattice made available.
(https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Package-Dependencies)
あるパッケージを拡張するようなパッケージの場合、拡張される側の方のパッケージを読み込んどいた方が便利だよね、という話のようです。
しかし、これも最近の流行(というかHadley流?)のやり方としては、そういう感じでユーザが直接使えた方がいい別パッケージの関数はimportFrom
したやつをそのままexport
するという手が取られています。例えば、dplyrパッケージを読み込むだけでmagrittrパッケージの%>%
演算子が使えますが、これは以下のように指定されているからです。
#' @importFrom magrittr %>% #' @export magrittr::`%>%`
(https://github.com/tidyverse/dplyr/blob/e5b7d00555148c8d46f61e3cd2e46dd52a0f4b28/R/utils.r#L1-L3)
ここまでのまとめ
DESCRIPTION
ファイルのImports
とNAMESPACE
ファイルのimportFrom
/import
を混同しがちですが、イメージ的には、
DESCRIPTION
ファイルのImports
: そのパッケージをインストールするかどうかを決めるNAMESPACE
ファイルのimportFrom
/import
: 読み込み方をどうするかを決める
という感じです。これは、install.packages()
でパッケージをインストールしてもlibrary()
で読み込まなければ使えないし、逆にlibrary()
してもそのパッケージがインストールされていなければエラーになる、というのと同じ関係です。どちらも必要なのです。
importFrom
か::
かどっちがいいのか
ここで、R Packagesを見てみましょう。
If you are using just a few functions from another package, my recommendation is to note the package name in the Imports: field of the DESCRIPTION file and call the function(s) explicitly using
::
, e.g.,pkg::fun()
.If you are using functions repeatedly, you can avoid
::
by importing the function with@importFrom pgk fun
. This also has a small performance benefit, because::
adds approximately 5 µs to function evaluation time. (http://r-pkgs.had.co.nz/namespace.html#imports)
ちょっとだけなら::
で、いっぱいあるときはimportFrom
、という基準のようです。
importFrom
を使うのがちゃんとしているとは思います。明示的に「この関数を使います」と宣言してからその関数を使っていくのが正しい作法で、::
でアドホックにいろいろ関数を継ぎ足していくのはなんかルーズに感じます。あと、::
がいっぱいあるとなんか読みづらいというのはあるでしょう。
それでも、個人的なオススメは::
です。その理由は、
importFrom
をちゃんと書くという習慣を身につけるのはけっこう大変です。手元では動いてるからとGitHubにプッシュしたあとCIのエラーで気づく、みたいな感じになりがちです。それよりは、「別のパッケージの関数を使うときは::
」という方が方針として分かりやすいんじゃないかな、と思っています。- これはIDE的な都合ですが、補完したりヘルプを見ながらやるときに
::
だとパッケージを読み込んでいなくても候補が出てくるので便利です。
という感じです(考えたけどあんまり思いつかなかった…)。前者に関しては、手元でちゃんとR CMD check
しろという話だし、::
にしたところで結局書き忘れはあるので大した差はないかな、という気はしています。
とはいえ%>%
とか中置演算子はimportFrom
しないと使えないし、Hadleyのコードとか見ててもどっちも使ってるし、明確にどっちかにそろえるというのも難しいのかも。
どうなんでしょう。
最後に
importFrom
派からのマサカリを待っています。(ここまで書いたものの正直どっちがいいのかわからないので教えてください…)