三連ドット(..., dot-dot-dot, ellipsis)の取り扱い

これもr-wakalangに投げ込んで教えてもらった話なので、まずは簡単にまとめる。

基本的な使い方

適当な...を持つ関数を定義する。

f1 <- function(x, ...)
{
  dots <- list(...)
  print(dots)
}

これに対して以下の実行結果からわかるように、引数にマッチしなかったもの(ここではx以外)がリストのdots変数として関数内で使えるようになっているのがわかる。
要するに、多言語でいうところの可変長変数のようなもんだ。
list(...)という書き方の原点がどこにあるのかは不明だが、下記参考資料にあるR Language Definitionにも載ってるし、まぁこれはこういうもんかと思っておく。

> f1("a", 3)
[[1]]
[1] 3

> f1("b", a=3, b=7)
$a
[1] 3

$b
[1] 7

...のマッチに関する注意

普通のプログラミング言語だと可変長引数の後にはなんも書けない(引数を追加できない)ような気がするが、Rではできてしまう。

f2 <- function(x, ..., y)
{
  dots <- list(...)
  print(dots)
  print(paste0("y: ", y))
}

この場合、x, y以外の変数が全部...にマッチされている。

> f2("a", 3, z=7, y=3)
[[1]]
[1] 3

$z
[1] 7

[1] "y: 3"

R言語徹底解説にあったようなお話(data.frameに対するfilter操作)


R言語徹底解説のどこだかに載っていた気がする「data.frameに対するfilter操作」をdplyrぽく書く話も以下のように実現できる。
詳しくはoreorefilter関数のコメント参照。

oreorefilter <- function(df, ...)
{
  # 評価前に言語オブジェクトにしちゃう
  object <- as.list(substitute(list(...)))
  # どういう型として内部で保持されているかの確認
  print(str(object))
  # as.listしない場合の構造(つながった1リストになっちゃってる)
  print(str(substitute(list(...))))
  # 条件の順次評価(再帰で書いたほうがスマートか?)、一個目の要素はいらないのでループに入れない
  condition <- rep(TRUE, nrow(df))
  for(obj in object[-1]){
    # 条件を順次df環境で評価し、AND(&)条件として結ぶ
    condition <- condition & eval(obj, df)
  }
  df[condition, ]
}

実際に使ってみると、ちゃんとirisデータをFilteringできていることが分かる。

> oreorefilter(iris, Species=="setosa", Petal.Width==0.2, Sepal.Length==5.0)
List of 4
 $ : symbol list
 $ : language Species == "setosa"
 $ : language Petal.Width == 0.2
 $ : language Sepal.Length == 5
NULL
 language list(Species == "setosa", Petal.Width == 0.2, Sepal.Length == 5)
NULL
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
5             5         3.6          1.4         0.2  setosa
8             5         3.4          1.5         0.2  setosa
26            5         3.0          1.6         0.2  setosa
36            5         3.2          1.2         0.2  setosa
50            5         3.3          1.4         0.2  setosa