昨日の Tokyo.R における R6 class についての発表が素晴らしかった。
プレゼンの最後に質問させて頂いたのだけれど、その点について調査してみた。
- S3 との組み合わせ(特に print)はどうなっているのか?
- サブクラスで再定義しなくてもスーパークラスのメソッドを呼べるのか?
1. R6 のインストール
CRAN にアップされているので、インストールは特に難しくない。
install.packages("R6")
2. 基本的な使い方
使い方も特に難しくはない。むしろかなり自然に書ける。
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 の発想のすごいところは、参照セマンティクスを環境で実現しているところ。それを確認してみる。
me <- Person$new("hoxo_m")
class(me)
mode(me)
str(me)
[1] "Person" "R6"
[1] "environment"
Classes 'Person', 'R6' <environment: 0x0000000008045d38>
確かにオブジェクトは環境になっている。
これは、内部で次のような感じで生成しているということ。
env <- new.env()
class(env) <- c("Hoge")
class(env)
mode(env)
str(env)
[1] "Hoge"
[1] "environment"
Class 'Hoge' <environment: 0x00000000079e5b70>
環境にも class を定義できるとは初めて知った。
何がうれしいかというと、R の普通のオブジェクト(listなど)は、変数を再代入するとオブジェクトのコピーが起きる。
a <- list(name="hoxo_m")
a2 <- a
a2$name <- "hoxo_m2"
a$name
[1] "hoxo_m"
環境ではこれが起きない。
オブジェクト指向言語としてはこっちの方が自然である。
me <- Person$new("hoxo_m")
me2 <- me
me2$set_name("hoxo_m2")
me$get_name()
[1] "hoxo_m2"
4. S3 との共生
R6 オブジェクトを print
するとどうなるだろうか。
me <- Person$new("hoxo_m")
print(me)
<Person>
Public:
get_name: function
initialize: function
set_name: function
ここで、class を確認してみる。
[1] "Person" "R6"
S3 の print
には R6:::print.R6
が定義されており、それが使われているようだ。
ここで、R6:::print.R6
の中身を確認してみる。
> R6:::print.R6
function (x, ...)
{
if (is.function(x$print)) {
x$print()
}
else {
...
どうやら、オブジェクトに print
メソッドが定義されている場合、それが使われる仕様となっているようだ。
Person
を継承して、print
メソッドを持つ PersonWithPrint
をクラスを定義してみよう。
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()
メソッドの定義を書いていない。
これらがちゃんとスーパークラスから継承できているか確認する。
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 書かなくてもいいやつ。
こっちをデフォルトにしてもらいたいですね。