概要
他の言語と比べるとRの(S3)クラス定義は特殊です(※個人の感想)。
これはR言語は統計処理言語なのが意識されて作られていることに由来しているからでしょう。
ただ、慣れるとオブジェクト指向っぽい書き方ができる気がする(完全にできるとは言ってはいない
今回のクラス定義はS3クラスについて雑にまとめました(他にはS4,R5,R6等々いろいろある)
参考出典
基本的に下記のリンクを参考にしてます
- https://www.cyclismo.org/tutorial/R/s3Classes.html
- http://adv-r.had.co.nz/S3.html
- http://rtokei.tech/memo/rのことを学ぶ010s3クラス/
注意
忘備録的に書いているので、分かりにくいです。また、ソフトウェア工学しっかりとは学んでいないので、内容は保証しません。
クラス定義
RのS3クラス定義は簡単
x <- 1
class(x) <- "foo"
class(x)
これで、変数x
はfoo
クラスとして定義される。正直、なんじゃこりゃ。
総称関数
なんとなくの理解で総称関数を語る(=合っているとは保証しない)。詳しくは下記参照。
R言語で分析すると何気なく使っているprint
関数。
文字列だろうが、data.frame
クラスだろうが、lm
関数の結果(lm
クラス)だろうが、関係なくprint
してくれる。このように入力されるデータのクラスに関わらず、なんらかの出力を出してくれる便利な関数が「総称関数」です。
print("Hi")
print(iris)
print( lm(Sepal.Length~Petal.Length,iris) )
よって、上記は下記と一緒の出力結果を得る
print.default("Hi")
print.data.frame(iris)
# print.lm ( lm(Sepal.Length~Petal.Length,iris) )
新しい総称関数を作りたい場合は、以下のようにUseMethod
関数を使う。
UseMethod("GetFirst")
クラスの継承
RのS3クラス継承は少し分かりにくい。
class(x) <- c("hoo","foo")
これで、xはhoo
とfoo
クラスを持つ。上の場合だとfoo
クラスが親で、hoo
クラスは子クラスとなる。つまり、要素番号が一番若いクラスが子クラスになる。実際に以下のスクリプトで確認できる。
x <- list(x = 1,
y = "hi")
class(x)<-c("foo","hoo")
greeting <- function(x){
UseMethod("greeting",x)
}
greeting.foo <- function(x){
print(x$y)
}
greeting.hoo <- function(x){
print(x$x)
}
greeting(x)
> [1] "hi"
上ではgreeting
関数を総称関数にして、変数x
にfoo
クラスとhoo
クラスを持たせた。それぞれのクラスについて関数を定義した結果、foo
クラスの結果が返されている。
x <- list(x = 1,
y = "hi")
class(x)<-c("foo","hoo")
greeting <- function(x){
UseMethod("greeting",x)
}
greeting.hoo <- function(x){
print(x$x)
}
greeting(x)
> [1] 1
二つ目はfoo
クラスにgreeting
関数について何も定義していないと、親であるhoo
クラスの結果を返している。
クラス実装
もう少しオブジェクト指向っぽい実装を行いたい場合は以下のように実装する。
NorthAmerican <- function(eatsBreakfast=TRUE,myFavorite="cereal")
{
me <- list(
hasBreakfast = eatsBreakfast,
favoriteBreakfast = myFavorite
)
## Set the name for the class
class(me) <- append(class(me),"NorthAmerican")
return(me)
}
list
の部分をフィールドとして定義。
class(me) <- append(class(me),"NorthAmerican")
で継承関係を定義する。
以下のように、インスタンス生成っぽいアトモスフィア(雰囲気)が出てくる。
taro <- NorthAmerican(eatsBreakfast=TRUE,myFavorite="rice")
クラスにメソッドを追加するときは関数を総称関数にして、自ら定義したクラスの関数を記述する。
setHasBreakfast <- function(elObjeto, newValue)
{
print("Calling the base setHasBreakfast function")
UseMethod("setHasBreakfast",elObjeto)
print("Note this is not executed!")
}
setHasBreakfast.default <- function(elObjeto, newValue)
{
print("You screwed up. I do not know how to handle this object.")
return(elObjeto)
}
setHasBreakfast.NorthAmerican <- function(elObjeto, newValue)
{
print("In setHasBreakfast.NorthAmerican and setting the value")
elObjeto$hasBreakfast <- newValue
return(elObjeto)
}
参考にしたサイトでは上記の実装になっていたが、以下のような書き方でも可能
setHasBreakfast <- function(object , newValue)
{
print("Calling the base setHasBreakfast function")
UseMethod("setHasBreakfast")
print("Note this is not executed!")
}
magrittr
パッケージを使うと、メソッドの書き方が他の言語と見慣れた感じになる。
taro <- NorthAmerican(eatsBreakfast=TRUE,myFavorite="rice")
setHasBreakfast <- function(object, newValue)
{
print("Calling the base setHasBreakfast function")
UseMethod("setHasBreakfast")
print("Note this is not executed!")
}
setHasBreakfast.default <- function(object,newValue)
{
print("You screwed up. I do not know how to handle this object.")
return(object)
}
setHasBreakfast.NorthAmerican <- function(object,newValue)
{
print("In setHasBreakfast.NorthAmerican and setting the value")
object$hasBreakfast <- newValue
return(object)
}
require(magrittr)
taro %>% setHasBreakfast( newValue = FALSE)
詳しい説明は参考にしたサイトに譲るが、下記のようにメソッドを実装する方法もある。
bubba <- NorthAmericain(myFavorite="oatmeal")
bubba$getFavoriteBreakfast()
[1]"oatmeal"