S3

RStudioではじめるRプログラミング入門を読んだ。RStudioについて詳しい情報があるかと思っていたのだが、これについてはそこそこの記述だった。
ただ、全体的に見て、Rとプログラミングをゼロから学ぼうと思うのであれば良い情報量だと感じた。リゲス本なんかに比べるとずっと表現が平易で分かりやすい。タイトルに「入門」と付いたオライリーの本が入門書であったことなど殆ど無いので逆に面食らった。ゆえに、本当の初心者で無ければあえて読む必要も無い本とも言えるのだが、環境やクラス、コードのベクトル化など、なかなか初心者向けの丁寧な解説が無い部分の記述もあり、個人的には参考になった。また、「他人にRを教える」場合の参考書としても良いのではないかと思う。

S3に関してよくまとまっていたので覚書。なお、本書の説明のほうがずっと長いが丁寧。

S3とは

Rに備わっているクラスシステムの一つで、ジェネリック関数、メソッド、クラスに基いている。

やること

  1. オブジェクトのクラス名を考える
  2. クラスのインスタンスのclass属性にクラス名を設定する
  3. 必要となりそうなジェネリック関数のクラスメソッドを書く

ジェネリック関数

Rには、与えられたオブジェクトの種類によって異なる振る舞いをする関数が備わっている。print()はその例。

> x <- 100000
> print(x)
[1] 1e+05
> attr(x, "class") <- "POSIXct"
> print(x)
[1] "1970-01-02 12:46:40 JST"

同じprint(x)でも"class"属性が変わることで関数の動作が変わっている。このように、ジェネリック関数はクラスに応じて振る舞いを変える。

メソッド

ジェネリック関数は呼び出された際にUseMethod()という特別な関数を呼び出す。

> prin
function (x, ...) 
UseMethod("print")
<bytecode: 0x10432ebc8>
<environment: namespace:base>

UseMethod()は関数の第1引数に与えられたオブジェクトのクラスを調べ、そのクラスを処理するための関数に全ての引数を渡す。
具体的には、print.POSIXct()の様に関数名とクラス名をドットで区切った名称の関数が呼ばれる。

> print.POSIXct
function (x, ...) 
{
    max.print <- getOption("max.print", 9999L)
    if (max.print < length(x)) {
        print(format(x[seq_len(max.print)], usetz = TRUE), ...)
        cat(" [ reached getOption(\"max.print\") -- omitted", 
            length(x) - max.print, "entries ]\n")
    }
    else print(format(x, usetz = TRUE), ...)
    invisible(x)
}
<bytecode: 0x10c0b90e8>
<environment: namespace:base>

これらの関数は(print()の場合)、printのメソッドと呼ばれる。どのようなメソッドが存在するのかについては、methods(print)のようにして調べることができる。

> methods(print)
  [1] print.abbrev*                                
  [2] print.acf*                                   
  [3] print.anova*                                 
  [4] print.Anova* 
...

自作のクラスを追加する

オブジェクトにクラス属性を追加する方法はいくつかある。attr()を使う方法や、class()を使う方法、structure()を使う方法などである。

x <- 1

class(x) <- "myclass"
attr(x, "class") <- "myclass"
structure(x, class = "myclass")

structure()を使うやり方は関数の返り値を構成する場合などに役立つだろう。

> roll <- function(){
+   # サイコロを2つ振って合計値を求める
+   die <- 1:6
+   dice <- sample(die, size = 2, replace = TRUE)
+   structure(sum(dice), dice = dice, class = "dice")
+ }
> 
> roll()
[1] 10
attr(,"dice")
[1] 6 4
attr(,"class")
[1] "dice"

class属性はメソッドがなければ単なる属性扱いであり、デフォルトのprint()では他の属性と同様に表示される*1

自作のメソッドを作成する

自作のメソッドを作成、追加する方法はとても簡単で、処理方法を定めた関数を作成し、他のメソッドと同様に、関数名とクラス名をドットで区切った関数名を設定すれば良い。

> print.dice <- function(x, ...){
+   dice <- attr(x, "dice")
+   dice <- paste("dice1:", dice[1], " dice2:", dice[2])
+   string <- paste(x, dice, sep = "\n")
+   cat(string)
+ }
> 
> roll()
11
dice1: 6  dice2: 5

*1:printはコンソール上では暗黙に使われるので上記例では明示的に使用していない。