paste0関数の挙動

こんな話がある。

結論からいえば、三連ドット(...)で引数を受ける+引数のチェックを明示的に行わなかった結果だと思われる。

原因を探る

paste0関数内部をみると.InternalでC実装のpaste0が呼ばれている。

> paste0
function (..., collapse = NULL) 
.Internal(paste0(list(...), collapse))
<bytecode: 0x000000000581fef0>
<environment: namespace:base>

.Internalの中身を見るにはソースを見たらよい。
https://github.com/wch/r-source
names.cを確認すると、C実装のpasteと同様にdo_pasteが呼ばれている。
pasteとの違いはpaste0の場合、offsetが1(pasteは0)でarityが2(pasteは3)という点である。
https://github.com/wch/r-source/blob/48536f1756a88830076023db9566fbb2c1dbb29b/src/main/names.c#L514

offsetは動作を切り替えるフラグであり次のdo_pasteの中ではopとして扱われている。
arityは引数の数を示しており(paste0はlist(...)とcollapseの2つ、pasteはsepが加わって3つ)。

さて、今度は呼ばれるdo_pasteの中身をみてみる。
https://github.com/wch/r-source/blob/trunk/src/main/paste.c#L52
ここではoffset(op)をチェックして0ならばuse_sepがTRUEになる。
つまりpaste0ならuse_sepはFALSEである。
use_sepがTRUEの時はsep引数の値をみて処理を行っているが、
FALSEの時はsep引数にnilを入れるのみでありチェックは行われない。
https://github.com/wch/r-source/blob/trunk/src/main/paste.c#L77

結果として、sepに限らずどんな引数でも渡せるようになっており
冒頭の例の他にも以下のような例でも通る。

> paste0(1,2,yeah=1)
[1] "121"
> paste0(1,2,takayanagi=3)
[1] "123"

三連ドット(...)で引数を受ける+引数のチェックを明示的に行わなかった結果、以上のような挙動になっているものと思われる。
三連ドットは便利だけど、Advanced RのFunctionsの章にもあるように、引数のチェックが行われず思わぬ挙動を示す*1
paste0関数はその良い例とも言える。

*1:sep引数なんてそもそもこの関数の引数として用意してないんだから使うなよといえばそれまでだが