オブジェクトの属性とクラス
R のオブジェクトには属性(attribute)と呼ばれる付加的な情報を加えることができる。属性は実体としては単なる文字列(もしくは文字列ベクトル)であり、任意にいくつでも付け加えることができる。R 言語が既定で持つ特殊な属性が幾つかあり、オブジェクトの素性を表し、他の関数がそのオブジェクトを処理する方法に関する情報を与える。例えば線形回帰関数 lm() の返り値には、自動的に クラス属性(class attribute) "lm"が付け加えられ、それが線形回帰分析関数の出力の結果であるという情報を与える。当然、オブジェクトはこのクラス属性を持つオブジェクトに期待される内容を一定のフォーマットで、備えているわけで、単にあるオブジェクトに文字列である属性 "lm" を与えても、他の関数は混乱させられる亊になる。R はオブジェクトに種々の属性を付加することにより、オブジェクトを「構造化」する。例えば、R の行列・配列はそのものとしては単なるベクトルであるが、次元属性(dimension attribute)"dim" を与えることにより、それが行列・配列であることを他の関数に教える。
> (x <- 1:8) # 等差数列ベクトル [1] 1 2 3 4 5 6 7 8 > dim(x) <- c(2,4) # x の次元属性を c(2,4) にする > x # 2x4 行列になる [,1] [,2] [,3] [,4] [1,] 1 3 5 7 [2,] 2 4 6 8 > dim(x) <- c(2,2,2) # x の次元属性を c(2,2,2) にする > x # 3次元 (2x2x2) 配列になる , , 1 # x[,,1] 部分の行列 [,1] [,2] [1,] 1 3 [2,] 2 4 , , 2 # x[,,2] 部分の行列 [,1] [,2] [1,] 5 7 [2,] 6 8 > x[7] # しかし依然としてベクトルとしてアクセスできる! [1] 7
属性は隠された情報であり、attr() 関数や str() 関数を使わない限り、ユーザーの目からは隠されている。
> x <- lm(1:5 ~ rnorm(5)) # 例示用の簡単な線形回帰 > comment(x) <- "Example (2004.4.19)" # x にコメント属性を与える > x # 結果の出力(属性は表示されない) Call: lm(formula = 1:5 ~ rnorm(5)) Coefficients: (Intercept) rnorm(5) 2.7041 -0.5122 > attributes(x) # オブジェクトの属性のリストを与える $names # names(名前)属性、文字列ベクトルである [1] "coefficients" "residuals" "effects" "rank" [5] "fitted.values" "assign" "qr" "df.residual" [9] "xlevels" "call" "terms" "model" $class # クラス属性 [1] "lm" $comment [1] "Example (2004.4.19)" # コメント属性の内容 > str(x) # str() 関数はオブジェクトの持つ全情報を簡略に示す汎用関数 List of 12 $ coefficients : Named num [1:2] 2.704 -0.512 ..- attr(*, "names")= chr [1:2] "(Intercept)" "rnorm(5)" (途中略) - attr(*, "class")= chr "lm"
R 言語で頻繁に使われる plot() 等の関数は総称的関数(generic function) と呼ばれる。これらの関数は引数である R オブジェクト x に応じて、実際に使われる関数が決まる。例えば x が線形回帰関数 lm() の返り値であれば、x はクラス属性 "lm" (線形回帰オブジェクト)を持ち、plot() 関数はこれを確認すると、線形回帰オブジェクトのプロット用に設計されたプロット関数 plot.lm() を起動する。関数 plot.lm() はプロット関数 plot() の一つのメソッド(method) と呼ばれる。plot() 関数はその他にも様々なクラス属性タイプに応じたメソッドを持つ。また、ユーザーが独自のクラスに対する独自のメソッドを定義することも可能である。この機構は、多くの同種の関数を同じ関数で代表させることで、ユーザがいちいちそれを意識せずに、一つの関数 plot() だけを使えば済むという点で、コードの大幅な簡略化を可能にする。
> methods(plot) # 総称的関数 plot() に対するメソッドの一覧を得る [1] plot.HoltWinters* plot.POSIXct plot.POSIXlt [4] plot.TukeyHSD plot.acf* plot.data.frame (途中省略) [28] plot.table plot.ts plot.tskernel* Non-visible functions are asterisked > methods(class = "lm") # クラス "lm" のオブジェクト用のメソッド一覧を得る [1] add1.lm alias.lm anova.lm case.names.lm [5] coef.lm confint.lm cooks.distance.lm deviance.lm (途中省略) [33] variable.names.lm vcov.lm
注意:もし自前のクラスとそのメソッドを定義したければ、概略次の様になるでしょうか。
(1) クラス名 "hoge" を定義。
(2) 関数 Hoge() の返り値にクラス属性 "hoge" を与え、さらにそれがもつべき情報を返り値(リストの該当する名前つき成分 "hogea", "hogeb", "hogec",...)に与える。
(3) クラス ”hoge" 用の plot メソッド関数 plot.hoge() を定義する。この関数は引数が "hoge" クラスであることから、その名前つき成分 "hogea", "hogeb", "hogec", ...の情報を用いて、然るべきプロットを実行する。
(4) R にはこれらを以上の処理を実現する専用の機能が用意されている(help.search("methods") で確かめてください)ようですが、詳細は知りません。
注意:このメカニズムはたしかに「構造化されたオブジェクト」を実現するスマートな方法ですが、いわゆる「オブジェクト指向」と呼んでいいのでしょうか?結果としては、同じことを実現できるわけですが。ちなみに、Matlab のユーザーML で、何故 Matlab はこのメカニズムを採用しないのかと文句をいっている人がいました。
追記:Rは、設計に、Lisp(or Scheme)の影響を受けているようです。Rの総称関数は、CommonLispのオブジェクト指向システムCLOSの総称関数と同じく、メソッドが、クラスに属するものではありません。その点、C++やJavaなどのオブジェクト指向と毛色が違います。が、れっきとしたオブジェクト指向でしょう。詳しくは、「CLOS, 総称関数」などのキーワードで検索してみてください。
追記へのコメント(2006.10.09) 現在 R の言語仕様はこのページで解説されている S3 方式(S 言語第3版) から S4 方式(S言語第4版) へ移行中です.S4 では完全なオブジェクト指向が可能になっているようです.詳しくは S4 クラスとメソッド入門 参照。