読者です 読者をやめる 読者になる 読者になる

purrr::partial()が引数の順序のせいでうまく動かないときは.first=FALSEを指定する

R purrr

purrrにも部分適用をする関数partial()があるんですけど、↓の記事のやり方を調べてた時にちょっとつまづいたのでメモ。

部分適用はS3のgeneric関数と相性が悪いことがあります。

まずはこの挙動を見てください。

from <- as.Date("2016-01-01")
to   <- as.Date("2016-01-09")

seq(from = from, to = to, by = "days")
#> [1] "2016-01-01" "2016-01-02" "2016-01-03" "2016-01-04" "2016-01-05"
#> [6] "2016-01-06" "2016-01-07" "2016-01-08" "2016-01-09"

seq(by = "days", from = from, to = to)
#> Error in unclass(e1)/e2 : non-numeric argument to binary operator

引数の順序を変えただけでエラーになります。ナンデ!!???

debug(seq)して挙動を調べると、どうやら、前者の場合にはseq.Date()が呼ばれて、後者の場合にはseq.default()が呼ばれているようでした。(これはS3のmethod dispatchと呼ばれる仕組みです。)

これはなぜかというと、seq()の引数が...だからです。

formals(seq)
#> $...

これがもしもseq(from, to, ...)だったら、fromに指定したオブジェクトの型によって呼ばれる関数が選ばれるんですが、...なので、問答無用で1番目に指定した引数の型が使われます。

partialは、デフォルトだと、第1引数から引数を詰めていきます。

purrr::partial(seq, by = "days")
#> function (...) 
#> seq(by = "days", ...)

なので、こんなことをしようと思うとさっきのエラーになります。

f <- purrr::partial(seq, by = "days")
f(from, to)
#> Error in unclass(e1)/e2 : non-numeric argument to binary operator

こんなときは、.first=FALSEを指定しましょう。こうすれば、引数を後ろの方に持ってきてくれます。

f <- purrr::partial(seq, by = "days", .first = FALSE)
f
#> function (...) 
#> seq(..., by = "days")

f(from, to)
#> [1] "2016-01-01" "2016-01-02" "2016-01-03" "2016-01-04" "2016-01-05"
#> [6] "2016-01-06" "2016-01-07" "2016-01-08" "2016-01-09"

しかしなんという落とし穴...