R6 class さっそく調査 #rstatsj

More than 3 years have passed since last update.

昨日の Tokyo.R における R6 class についての発表が素晴らしかった。

プレゼンの最後に質問させて頂いたのだけれど、その点について調査してみた。


  • S3 との組み合わせ(特に print)はどうなっているのか?

  • サブクラスで再定義しなくてもスーパークラスのメソッドを呼べるのか?


1. R6 のインストール

CRAN にアップされているので、インストールは特に難しくない。


R

install.packages("R6")



2. 基本的な使い方

使い方も特に難しくはない。むしろかなり自然に書ける。


R

library("R6")

Person <- R6Class(
"Person",
private = list(
name = NA
),
public = list(
initialize = function(name) { self$set_name(name) },
set_name = function(name) { private$name <- name },
get_name = function() { private$name }
)
)

me <- Person$new("hoxo_m")
me$get_name()



結果

[1] "hoxo_m"



3. 参照セマンティクスと環境

R6 の発想のすごいところは、参照セマンティクスを環境で実現しているところ。それを確認してみる。


R

me <- Person$new("hoxo_m")

class(me)
mode(me)
str(me)


結果

[1] "Person" "R6"   

[1] "environment"
Classes 'Person', 'R6' <environment: 0x0000000008045d38>

確かにオブジェクトは環境になっている。

これは、内部で次のような感じで生成しているということ。


R

env <- new.env()

class(env) <- c("Hoge")
class(env)
mode(env)
str(env)


結果

[1] "Hoge"

[1] "environment"
Class 'Hoge' <environment: 0x00000000079e5b70>

環境にも class を定義できるとは初めて知った。

何がうれしいかというと、R の普通のオブジェクト(listなど)は、変数を再代入するとオブジェクトのコピーが起きる。


R

a <- list(name="hoxo_m")

a2 <- a
a2$name <- "hoxo_m2"
a$name


結果

[1] "hoxo_m"


環境ではこれが起きない。

オブジェクト指向言語としてはこっちの方が自然である。


R

me <- Person$new("hoxo_m")

me2 <- me
me2$set_name("hoxo_m2")
me$get_name()


結果

[1] "hoxo_m2"



4. S3 との共生

R6 オブジェクトを print するとどうなるだろうか。


R

me <- Person$new("hoxo_m")

print(me)


結果

<Person>

Public:
get_name: function
initialize: function
set_name: function

ここで、class を確認してみる。


R

[1] "Person" "R6"


S3 の print には R6:::print.R6 が定義されており、それが使われているようだ。

ここで、R6:::print.R6 の中身を確認してみる。


R

> R6:::print.R6

function (x, ...)
{
if (is.function(x$print)) {
x$print()
}
else {
...

どうやら、オブジェクトに print メソッドが定義されている場合、それが使われる仕様となっているようだ。

Person を継承して、print メソッドを持つ PersonWithPrint をクラスを定義してみよう。


R

PersonWithPrint <- R6Class(

"PersonWithPrint",
inherit = Person,
public = list(
print = function() {
print(sprintf("My name is %s.", self$get_name()))
}
)
)

me <- PersonWithPrint$new("hoxo_m")
print(me)



結果

[1] "My name is hoxo_m."


S3 でわざわざ定義しなくても、R6 内部に print メソッドを持たせることによって、S3 の print には対応できることが確認できた。


5. 継承

上で書いた PersonWithPrint クラスには、set_name(), get_name() メソッドの定義を書いていない。

これらがちゃんとスーパークラスから継承できているか確認する。


R

me <- PersonWithPrint$new("hoxo_m")

me$get_name()
me$set_name("hoxo_m2")
me$get_name()



結果

[1] "hoxo_m"

[1] "hoxo_m2"

おー、素晴らしい!

内部的にどうやっているのかはわからないが、普通のオブジェクト指向言語の動きを再現できているようだ。


まとめ

R6、分かりやすいしかなり使えそうですね。


追記

@kohske さん作の R6 class 定義で list 書かなくてもいいやつ。

こっちをデフォルトにしてもらいたいですね。


参考

R6パッケージの紹介―機能と実装