忘れないようにメモっとく

機械学習とかプログラミングとか。

Rのオブジェクト指向プログラミング(S3とS4クラス)

R Advent Calendar 2013 : ATND 17日目の記事です。

Rのオブジェクト指向

Rをオブジェクト指向で書けると聞いて、The Art of R Programmingを読んだ。

javaとかpythonオブジェクト指向とはだいぶ違うもので、Rのオブジェクト指向は主に、「ひとつの関数でいろんな機能を呼び出す」ということを実現してる。(ジェネリック関数とかポリモフィズムとか呼ばれてるやつ)

例えば、Rのplot()は、引数に渡すデータがどんな形をしてても大体プロットしてくれる便利関数。こういう、どんな引数でもおっけーっていう器の大きいやつがジェネリック関数。

試しに、plotの中を見てみると

methods(plot)
 [1] plot.acf*           plot.cat            plot.data.frame*   
 [4] plot.decomposed.ts* plot.default        plot.dendrogram*   
 [7] plot.density        plot.ecdf           plot.factor*       
[10] plot.formula*       plot.function       plot.hclust*       
[13] plot.histogram*     plot.HoltWinters*   plot.isoreg*       
[16] plot.lm             plot.medpolish*     plot.mlm           
[19] plot.ppr*           plot.prcomp*        plot.princomp*     
[22] plot.profile.nls*   plot.spec           plot.stepfun       
[25] plot.stl*           plot.table*         plot.ts            
[28] plot.tskernel*      plot.TukeyHSD      

   Non-visible functions are asterisked

試しに線形モデルlm()を作ってclass()関数を使うと、"lm"というクラスが返ってくる。plot()はこれに対応したplot.lm()を実行する。

model.lm <- lm(c(2, 4, 7) ~ c(1, 2, 3))
class(model.lm)
[1] "lm"
plot(model.lm)
↑実際に呼ばれるのはplot.lm(model.lm)

plot自体は描画の実装をもっていなくて、引数のクラスに応じた関数を実行するという感じ。
ポケモントレーナー自身は戦わないで、相手に応じて、いけっピカチュウ!っていうのと同じですな(違う)

こういう関数の実装に、S3クラスとS4クラスが使われている。
主に使われているのはS3で、S3の安全性が弱いじゃないかということで作られたのがS4。

S3クラス

S3クラスを作ってみる。

dog <- list(名前="Taro", 種類="dog")
dog
$名前
[1] "Taro"

$種類
[1] "dog"

dog <- list(名前="Taro", 種="dog")
class(dog) <- 'animal'
class(dog)
[1] "animal"

これで、animalクラスのdogオブジェクトができる。dogオブジェクトをplotに使うと、plotはanimalクラスに対応した関数をもっていないので、エラーになる。
plot.animal関数を実装すれば、plotの配下にplot.animalが作られて、plot(dog)が実行される。

plot(dog)
 以下にエラー xy.coords(x, y, xlabel, ylabel, log) : 
  'x' is a list, but does not have components 'x' and 'y'

plot.animal <- function(animal) {
        cat(animal$名前, '\n')
        cat(animal$種, '\n')
    }
plot(dog)
Taro 
dog 

まあ、このanimalクラスは使い物にならないけど、自分で作ったデータ型に対応した俺俺plotを作りたいときには便利と思う。
再利用性とかカプセル化が実現されているので、まあオブジェクト指向と呼べるのかと。
(一応、継承もある)

S4クラス

さっきの例でいうと、S3クラスは安全性が微妙。というのもクラスの指定が後付けなので、でたらめなデータにanimalクラスを指定することもできちゃう。

dog <- c(1, 2, 3)
class(dog) <- 'animal'
class(dog)
[1] "animal"

これだとplot.animalしたときに、名前とか種がないよーとエラーがでる。

というわけで、クラスを定義するときにデータも定義しておきましょうというのがS4クラス。
setClass()を使って定義する。

setClass("animal",
        representation(
        名前="character",
        種="character",
        年="numeric")
    )

オブジェクトを作るときはnew()を使う。(オブジェクト指向っぽい!)

dog <- new("animal", 名前="Taro", 種="dog", 年=6)

dog <- new("animal", 名前="Taro", 種="dog", 年="6")
以下にエラー validObject(.Object) : 
   不正なクラス “animal” オブジェクト : invalid object for slot "年" in class "animal": got class "character", should be or extend class "numeric"

型が間違っているときは、エラーになってくれる。

まとめ

S3とS4はどっちが優秀ということは言えない、使いどころに依るので。
でもRはインタラクティブに使うことが多いと思うので、簡単に作れるS3で十分だと思う。
S3とS4は、オブジェクトを関数とかに適用したときに怒られるか、オブジェクトを作成したときに怒られるかという違いだけな気もするし、そもそも言語仕様がよくわかんなくても動かせるのがRのいいところではあるし。
そもそものそもそもだけど、オブジェクト指向で書きたかったらpython(ry

まぁ、めちゃめちゃ働きものだと思ってたplotやsummaryの陰で、plot.lmやsummary.aov達が頑張っているということに哀愁を感じていただければ十分なんじゃないかと。