LoginSignup
14
15

More than 5 years have passed since last update.

R6 class さっそく調査 #rstatsj

Last updated at Posted at 2015-02-22

昨日の 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パッケージの紹介―機能と実装

14
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
15