R6パッケージの紹介:機能と実装
Tokyo.R #46 2015-02-21
@__nakamichi__
• R6パッケージとは
• R6を使う
– 既存のオブジェクト指向システム(S3, S4, RC)
– R6の機能
• R6の中身をのぞいてみる
– Rにおける環境
– R6Class()の実装
– $new()の実装
• まとめ
1
• R6パッケージとは
• R6を使う
– 既存のオブジェクト指向システム(S3, S4, RC)
– R6の機能
• R6の中身をのぞいてみる
– Rにおける環境
– R6Class()の実装
– $new()の実装
• まとめ
2
R6パッケージとは
• Rの新しいオブジェクト指向システム
• 作者:Winston Changさん@RStudio
• R言語のコアではなくパッケージとして実装
• 主にRStudio関係のパッケージで
利用されている
– dplyr
– httr
– shiny
3
R6パッケージとは
• 既に3つもOOシステムがあるのに,なぜまたもう1つ?
– S3, S4, RC (Reference Class)
• R6の特徴
– より「普通の」オブジェクト指向っぽい
• 「普通の」= Java, C++, Python, etc.
• RCと同様のreference semantics
• フィールド,メソッドにpublic / private が指定可能
• パッケージをまたいだ継承が可能
– RCより速くて軽い
– 設計・実装が単純
• Rの基本的な機能だけを使って実装されている
4
• R6パッケージとは
• R6を使う
– 既存のオブジェクト指向システム(S3, S4, RC)
– R6の機能
• R6の中身をのぞいてみる
– Rにおける環境
– R6Class()の実装
– $new()の実装
• まとめ
5
6
既存のOOシステム
• S3
– クラスはclass属性として個別のオブジェクトに対して設定
• フォーマルなクラス定義はない
• 継承もclass属性で行う
– メソッドはジェネリック関数
john <- list(name = "John", age = 40)
class(john) <- c("Employee", "Person")
# printはジェネリック関数
print
#> function (x, ...)
#> UseMethod("print")
# メソッド定義
print.Person <- function(x) {
paste0("こんにちは,", x$name, "です.")
}
# printで実際に呼ばれるのはprint.Person
print(john)
#> [1] "こんにちは,Johnです."
7
既存のOOシステム
• S4
– S3よりフォーマルで厳密なクラス定義
– スロット(フィールド)には@でアクセス
# クラス定義
setClass("Person",
slots = list(name = "character", age = "numeric"))
# containsで継承
setClass("Employee", contains = "Person"
slots = list(boss = "Person"))
# new()でオブジェクト作成
alice <- new("Person", name = "Alice", age = 40)
john <- new("Employee", name = "John", age = 20, boss = alice)
# @でスロットにアクセス
alice@age
#> [1] 40
# クラス定義にないスロットは作れない
alice@age <- '40'
#> Error in checkAtAssignment("Person", "age", "character") : ...
alice@sex <- "female"
#> Error in checkAtAssignment("Person", "sex", "character") : ...
8
既存のOOシステム
• S4
– メソッドはジェネリック関数
– copy-on-modify semantics: オブジェクトは変更不能(immutable)
# ジェネリック関数
setGeneric("greet", function(x) {
standardGeneric("greet")
})
# メソッドの実装
setMethod("greet",
signature = c(x = "Person"),
definition = function(x) {
paste0("こんにちは,", x@name, "です.")
})
greet(john)
#> [1] "こんにちは,Johnです."
# aliceとalice2は別人
alice2 <- alice
alice2@age <- 50
alice2@age
#> [1] 50
alice@age
#> [1] 40
age=50
name=“alice”
age=40
name=“alice”
Person alice
Person alice2
copy on modify
9
既存のOOシステム
• RC (Reference Class)
– メソッドはオブジェクトに属する
– フィールドとメソッドには$でアクセス
# クラス定義
Person <- setRefClass("Person",
# フィールド定義
fields = list(name = “character”,
age = "numeric", hair = "character"),
# メソッド定義
methods = list(
# オブジェクトの初期化関数
# フィールドへの代入は<<-か.selfを使う
initialize = function(name, age, hair) {
name <<- name
.self$age <- age
.self$hair <- hair
},
set_hair = function(hair) {
.self$hair <- hair
},
greet = function() {
paste0("こんにちは,", name, "です.")
})
)
# オブジェクト作成
alice <- Person$new("Alice", 40, "red")
# $でメソッドアクセス
alice$greet()
# 継承はcontains引数で
Employee <- setRefClass("Employee",
contains = "Person",
methods = list(
# メソッドオーバーライド
greet = function() {
paste0("こんにちは,会社員の",
name, "です.")
})
)
john <- Employee$new("John", 25, "black")
john$greet()
#> [1] "こんにちは,会社員のJohnです."
10
既存のOOシステム
• RC (Reference Class)
– reference semantics: オブジェクトは変更可能(mutable)
– 「普通の」OOP言語っぽい
name = “john”
age = 25
hair = “green”
Employee john
Employee john2
john$hair
#> [1] "black"
# johnとjohn2は同じ人
john2 <- john
john2$set_hair("green")
john$hair
#> [1] "green“
• R6パッケージとは
• R6を使う
– 既存のオブジェクト指向システム(S3, S4, RC)
– R6の機能
• R6の中身をのぞいてみる
– Rにおける環境
– R6Class()の実装
– $new()の実装
• まとめ
11
12
R6の機能
• クラス定義
– syntaxはRCに類似しているが,より簡潔
– フィールドとメソッドを分けずに全てpublic引数に記述すればよい
– RCと異なり,フィールドの型は指定できない
# クラス定義
Person <- R6Class("Person",
# publicでフィールドとメソッドを両方設定する
public = list(
# RCと異なり,変数の型ではなくデフォルト値を設定
name = NA,
hair = "black",
# オブジェクトの初期化関数
# オブジェクト自身にはselfでアクセス
initialize = function(name, hair) {
self$name <- name
self$hair <- hair
},
set_hair = function(hair) {
self$hair <- hair
},
greet = function() {
paste0("こんにちは,", self$name, "です.")
})
)
13
R6の機能
• オブジェクト作成
– $new()メソッドで作成
– reference semantics
# オブジェクト作成
alice <- Person$new("Alice", “black")
# フィールド,メソッドには$でアクセス
alice$hair
#> [1] "black"
alice$greet()
#> こんにちは,Aliceです.
alice$set_hair("red")
# クラス定義にないフィールドは作れない
alice$age <- 40
#> Error in alice$age <- 40 : ...
# aliceとalice2は同一人物
alice2 <- alice
alice2$set_hair("blue")
alice$hair
#> [1] "blue"
name=“alice”
hair=“blue”
Person alice
Person alice2
14
R6の機能
• public/private
– R6ではprivateフィールド,メソッドを作ることができる
• RCにはない機能
• オブジェクト自身のメソッドからしかアクセスできない
• (selfではなく)privateを通じてアクセスする
# privateを持つクラス
Person <- R6Class("Person",
private = list(age = NA, # 年齢はprivate
# 年齢のgetter
get_age = function() private$age),
public = list(name = NA, hair = "black",
initialize = function(name,hair,age) {
# 中略
# メソッドからはprivateフィールドが見える
private$age <- age
},
set_hair = function(hair) { #略 },
greet = function() { #略 },
# 外向け年齢はサバを読む
show_age = function() {
private$get_age() - 5
})
)
# オブジェクト作成
alice <- Person$new("Alice", "red", 40)
# age, get_age()は外からは見えない
alice$age
#> NULL
alice$get_age()
#> Error: attempt to apply non-function
alice$show_age()
#> [1] 35
15
R6の機能
• 継承
– スーパークラスを inherit 引数で指定
– スーパークラスのメソッドはsuperを通じて呼べる
– パッケージをまたいだ継承が可能(portable class, RCではできない)
# inheritで継承
Employee <- R6Class("Employee",
inherit = Person,
private = list(tsurami = 0), # つらみ
public = list(
# メソッドオーバーライド
initialize = function(name, hair, age, tsurami) {
# スーパークラスはsuperで呼ぶ
super$initialize(name, hair, age)
private$tsurami <- tsurami
},
work = function() { private$tsurami <- private$tsurami + 1},
greet = function() {
if(private$tsurami > 5) paste0("社畜の", self$name, "です!")
else super$greet()
})
)
# Johnは社畜
john <- Employee$new("John", "black", 35, tsurami = 10)
john$greet()
#> 社畜のJohnです!
16
R6の機能
• パフォーマンス
– R6はRCより速くて軽い
# RCとR6のパフォーマンス比較
library(microbenchmark)
# 適当なクラスを定義
R6 <- R6Class("R6", public = list(
x = NULL,
initialize = function(x = 1) self$x <- x,
inc = function(n = 1) self$x <- self$x + n)
)
RC <- setRefClass("RC",
fields = list(x = "numeric"),
methods = list(
initialize = function(x = 1) x <<- x,
inc = function(n = 1) x <<- x + n)
)
# オブジェクト生成
microbenchmark(
r6 <- R6$new(x = 100),
rc <- RC$new(x = 100))
#> Unit: microseconds
#> expr median neval
#> r6 <- R6$new(x = 100) 84.7825 100
#> rc <- RC$new(x = 100) 405.3950 100
# メソッド呼び出し
microbenchmark(r6$inc(), rc$inc())
#> Unit: microseconds
#> expr median neval
#> r6$inc() 16.065 100
#> rc$inc() 62.918 100
# オブジェクトサイズ
pryr::object_size(r6)
#> 3.94 kB
pryr::object_size(rc)
#> 465 kB
17
R6の機能
• ここまでのまとめ
• R6はRCと同様, reference semantics のOOシステム
• R6は「普通の」オブジェクト指向っぽい
– 簡潔なsyntax
– public/privateの区別
– パッケージをまたいだ継承
• R6はRCより速くて軽い
• R6パッケージとは
• R6を使う
– 既存のオブジェクト指向システム(S3, S4, RC)
– R6の機能
• R6の中身をのぞいてみる
– Rにおける環境
– R6Class()の実装
– $new()の実装
• まとめ
18
19
R6の中身をのぞいてみる
• R6の仕組みを知りたい
– R6Class()や$new()って何?
– public / private の仕組みは?
• 実はR6の実装は意外と簡単
– Rの基本的な機能だけで実装できる
• 要は,reference semantics を持つデータ構造を
「普通の」OOPのオブジェクトらしく見えるように
仕立てあげればよい
• reference semanticsを持つデータ構造
=「環境」
20
Rにおける環境
• 環境(environment) = 関数を構成する要素のひとつ
– Rの関数の3つの構成要素
• formals: 引数
• body: 関数本体
• environment: 変数(名前)をオブジェクトに結びつけるデータ構造
g
x 1
g
x 2
environment(g) <- e
e
f <- function() {
x <- 1
function() {
x + 1
}
}
g <- f()
environment(g)
#> <environment: 0x0000000013ddc1b8>
# gは親環境にxを探しに行く
g()
#> [1] 2
# gの環境を変更する
e <- new.env()
e$x <- 2
environment(g) <- e
g()
#> [1] 3
<environment: 0x0000000013ddc1b8>
21
Rにおける環境
• 環境は2つの要素からなる
– フレーム:名前をオブジェクトに結びつける
– 親環境(parent environment)
• 関数は親環境を辿っていって名前を探す
y <- 2
f <- function() {
x <- 1
function() {
x + y
}
}
h <- f()
h()
#> [1] 3
environment(h)
#> <environment: 0x000000001424c380>
parent.env(environment(h))
#> <environment: R_GlobalEnv>
h x 1
<environment: 0x000000001424c380>
y 2
globalenv()
parent.env()
22
Rにおける環境
• 環境 ≃ reference semantics を持つリスト
– 関数とは無関係に,便利なデータ構造としても使える
– 環境がだんだん「オブジェクト」に見えてきたような…
# 環境e1を作成
e1 <- new.env()
e1$x <- 1
e1$x
#> [1] 1
# e2を変更するとe1も変わる
e2 <- e1
e2$x <- 2
e1$x
#> [1] 2
# 関数に渡した環境自体が変更される
g <- function(e) {
e$y <- 3
}
g(e1)
e1$y
#> [1] 3
# リストl1を作成
l1 <- list()
l1$x <- 1
l1$x
#> [1] 1
# l2を変更してもl1はそのまま
l2 <- l1
l2$x <- 2
l1$x
#> [1] 1
# 関数に渡したリスト自体は不変
f <- function(l) {
l$y <- 3
}
f(l1)
l1$y
#> NULL
• R6パッケージとは
• R6を使う
– 既存のオブジェクト指向システム(S3, S4, RC)
– R6の機能
• R6の中身をのぞいてみる
– Rにおける環境
– R6Class()の実装
– $new()の実装
• まとめ
23
24
R6Class()の実装
• R6Class()の戻り値は?
Person <- R6Class("Person",
private = list(age = NA,
get_age = function() private$age),
public = list(name = NA, hair = "black",
initialize = function(name, hair, age) {
self$name <- name
self$hair <- hair
private$age <- age
},
set_hair = function(hair) {
self$hair <- hair
},
greet = function() {
paste0("こんにちは,", self$name, "です.")
},
show_age = function() {
private$get_age() - 5
})
)
Person
#> <Person> object generator
#> Public:
#> name: NA
#> hair: black
#> initialize: function
#> set_hair: function
#> greet: function
#> show_age: function
#> Private:
#> age: NA
#> get_age: function
#> Parent env: <environment: R_GlobalEnv>
#> Lock: TRUE
#> Portable: TRUE
25
R6Class()の実装
• R6Class() の戻り値=環境
– R6Class()の引数やnew()を要素に持つgenerator環境がR6クラスの実体
# encapsulate()はR6Classの親環境をcapsuleにする
R6Class <- encapsulate(function(classname = NULL,
public = list(), private = NULL, inherit = NULL, ...) {
# ジェネレータオブジェクトの作成
generator <- new.env(parent = capsule)
generator$self <- generator # selfはgenerator自身
generator$classname <- classname
generator$public_fields <- get_nonfunctions(public)
generator$private_fields <- get_nonfunctions(private)
generator$public_methods <- get_functions(public)
generator$private_methods <- get_functions(private)
# generator_funsはnewを含む関数のリスト
# generator_funcs$newの環境をgeneratorにする
generator_funs <- assign_func_envs(generator_funs, generator)
# generator_funs$newをgenerator$new にコピー
list2env2(generator_funs, generator)
class(generator) <- "R6ClassGenerator"
generator
})
26
R6Class()の実装
generator [Person]
R6Class
self
classname
public_fields
public_methods
private_methods
private_fields
new function
“Person”
R6パッケージ内部の
ユーティリティ関数群
capsule
list(name=NA, ...)
list(initialize =
function(name, ...),
...)
NULL
list(age=NA, ...)
Person <- R6Class()
R6Class()の引数
• R6パッケージとは
• R6を使う
– 既存のオブジェクト指向システム(S3, S4, RC)
– R6の機能
• R6の中身をのぞいてみる
– Rにおける環境
– R6Class()の実装
– $new()の実装
• まとめ
27
28
$new()の実装
• $new()の戻り値=環境
– R6オブジェクトの実体はpublic_bind_env環境
– generatorからpublic_bind_envにフィールドとメソッドをコピーする
# publicフィールド・メソッドのみ,継承なしの場合
generator_funs$new <- function(...) {
# publicオブジェクトの束縛環境を作成
public_bind_env <- new.env(parent = emptyenv(), hash = FALSE)
# メソッドの環境を作成
enclos_env <- new.env(parent = parent_env, hash = FALSE)
# selfの実体はpublic_bind_env
enclos_env$self <- public_bind_env
# generator$public_methodsの環境をenclos_envにして
# public_bind_envにコピー
public_methods <- assign_func_envs(public_methods, enclos_env)
# フィールドとメソッドをpublic環境にコピー
list2env2(public_methods, envir = public_bind_env)
list2env2(public_fields, envir = public_bind_env)
# オブジェクト初期化
public_bind_env$initialize(...)
public_bind_env
}
29
$new()の実装
public_fieldsと
public_methodsのコピー
generator [Person]
self
classname
public_fields
public_methods
private_methods
private_fields
new
function
alice <- Person$new()
initialize
set_hair
name
greet
hair
function
function
function
“black”
“Alice”
enclos_env
public_bind_env
[alice]
self
emptyenv()
30
$new()の実装
• privateがある場合:privateをpublicと別の環境として作成
enclos_env
public_fieldsと
public_methodsのコピー
initialize
set_hair
name
greet
hair
function
function
function
“black”
“Alice”
public_bind_env
[alice]
self
emptyenv()
private_fieldsと
private_methodsのコピー
get_age
age
function
40
private_bind_env
private
31
$new()の実装
• スーパークラスがある場合:superをpublicとは別の環境として作成
enclos_env
public_fieldsと
public_methodsのコピー
initialize
set_hair
name
greet
hair
function
function
function
“black”
“Alice”
public_bind_env
[john]
self
emptyenv()
super_bind_env
super
super_enclos_env
self
super
greetfunction
スーパークラスの
スーパークラスを
参照
スーパークラスの
public_fieldsと
public_methodsのコピー
• R6パッケージとは
• R6を使う
– 既存のオブジェクト指向システム(S3, S4, RC)
– R6の機能
• R6の中身をのぞいてみる
– Rにおける環境
– R6Class()の実装
– $new()の実装
• まとめ
32
まとめ
• R6は新しいオブジェクト指向システム
• R6の特徴
– 「普通の」オブジェクト指向っぽい
• reference semantics
• public / private
– RCより速くて軽い
• R6の実装
– 環境を活用したシンプルな実装
• 参考
– Introduction to R6 Classes
• http://cran.r-project.org/web/packages/R6/vignettes/Introduction.html
– Advanced R – Environments
• http://adv-r.had.co.nz/Environments.html
33

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

  • 1.
  • 2.
    • R6パッケージとは • R6を使う –既存のオブジェクト指向システム(S3, S4, RC) – R6の機能 • R6の中身をのぞいてみる – Rにおける環境 – R6Class()の実装 – $new()の実装 • まとめ 1
  • 3.
    • R6パッケージとは • R6を使う –既存のオブジェクト指向システム(S3, S4, RC) – R6の機能 • R6の中身をのぞいてみる – Rにおける環境 – R6Class()の実装 – $new()の実装 • まとめ 2
  • 4.
    R6パッケージとは • Rの新しいオブジェクト指向システム • 作者:WinstonChangさん@RStudio • R言語のコアではなくパッケージとして実装 • 主にRStudio関係のパッケージで 利用されている – dplyr – httr – shiny 3
  • 5.
    R6パッケージとは • 既に3つもOOシステムがあるのに,なぜまたもう1つ? – S3,S4, RC (Reference Class) • R6の特徴 – より「普通の」オブジェクト指向っぽい • 「普通の」= Java, C++, Python, etc. • RCと同様のreference semantics • フィールド,メソッドにpublic / private が指定可能 • パッケージをまたいだ継承が可能 – RCより速くて軽い – 設計・実装が単純 • Rの基本的な機能だけを使って実装されている 4
  • 6.
    • R6パッケージとは • R6を使う –既存のオブジェクト指向システム(S3, S4, RC) – R6の機能 • R6の中身をのぞいてみる – Rにおける環境 – R6Class()の実装 – $new()の実装 • まとめ 5
  • 7.
    6 既存のOOシステム • S3 – クラスはclass属性として個別のオブジェクトに対して設定 •フォーマルなクラス定義はない • 継承もclass属性で行う – メソッドはジェネリック関数 john <- list(name = "John", age = 40) class(john) <- c("Employee", "Person") # printはジェネリック関数 print #> function (x, ...) #> UseMethod("print") # メソッド定義 print.Person <- function(x) { paste0("こんにちは,", x$name, "です.") } # printで実際に呼ばれるのはprint.Person print(john) #> [1] "こんにちは,Johnです."
  • 8.
    7 既存のOOシステム • S4 – S3よりフォーマルで厳密なクラス定義 –スロット(フィールド)には@でアクセス # クラス定義 setClass("Person", slots = list(name = "character", age = "numeric")) # containsで継承 setClass("Employee", contains = "Person" slots = list(boss = "Person")) # new()でオブジェクト作成 alice <- new("Person", name = "Alice", age = 40) john <- new("Employee", name = "John", age = 20, boss = alice) # @でスロットにアクセス alice@age #> [1] 40 # クラス定義にないスロットは作れない alice@age <- '40' #> Error in checkAtAssignment("Person", "age", "character") : ... alice@sex <- "female" #> Error in checkAtAssignment("Person", "sex", "character") : ...
  • 9.
    8 既存のOOシステム • S4 – メソッドはジェネリック関数 –copy-on-modify semantics: オブジェクトは変更不能(immutable) # ジェネリック関数 setGeneric("greet", function(x) { standardGeneric("greet") }) # メソッドの実装 setMethod("greet", signature = c(x = "Person"), definition = function(x) { paste0("こんにちは,", x@name, "です.") }) greet(john) #> [1] "こんにちは,Johnです." # aliceとalice2は別人 alice2 <- alice alice2@age <- 50 alice2@age #> [1] 50 alice@age #> [1] 40 age=50 name=“alice” age=40 name=“alice” Person alice Person alice2 copy on modify
  • 10.
    9 既存のOOシステム • RC (ReferenceClass) – メソッドはオブジェクトに属する – フィールドとメソッドには$でアクセス # クラス定義 Person <- setRefClass("Person", # フィールド定義 fields = list(name = “character”, age = "numeric", hair = "character"), # メソッド定義 methods = list( # オブジェクトの初期化関数 # フィールドへの代入は<<-か.selfを使う initialize = function(name, age, hair) { name <<- name .self$age <- age .self$hair <- hair }, set_hair = function(hair) { .self$hair <- hair }, greet = function() { paste0("こんにちは,", name, "です.") }) ) # オブジェクト作成 alice <- Person$new("Alice", 40, "red") # $でメソッドアクセス alice$greet() # 継承はcontains引数で Employee <- setRefClass("Employee", contains = "Person", methods = list( # メソッドオーバーライド greet = function() { paste0("こんにちは,会社員の", name, "です.") }) ) john <- Employee$new("John", 25, "black") john$greet() #> [1] "こんにちは,会社員のJohnです."
  • 11.
    10 既存のOOシステム • RC (ReferenceClass) – reference semantics: オブジェクトは変更可能(mutable) – 「普通の」OOP言語っぽい name = “john” age = 25 hair = “green” Employee john Employee john2 john$hair #> [1] "black" # johnとjohn2は同じ人 john2 <- john john2$set_hair("green") john$hair #> [1] "green“
  • 12.
    • R6パッケージとは • R6を使う –既存のオブジェクト指向システム(S3, S4, RC) – R6の機能 • R6の中身をのぞいてみる – Rにおける環境 – R6Class()の実装 – $new()の実装 • まとめ 11
  • 13.
    12 R6の機能 • クラス定義 – syntaxはRCに類似しているが,より簡潔 –フィールドとメソッドを分けずに全てpublic引数に記述すればよい – RCと異なり,フィールドの型は指定できない # クラス定義 Person <- R6Class("Person", # publicでフィールドとメソッドを両方設定する public = list( # RCと異なり,変数の型ではなくデフォルト値を設定 name = NA, hair = "black", # オブジェクトの初期化関数 # オブジェクト自身にはselfでアクセス initialize = function(name, hair) { self$name <- name self$hair <- hair }, set_hair = function(hair) { self$hair <- hair }, greet = function() { paste0("こんにちは,", self$name, "です.") }) )
  • 14.
    13 R6の機能 • オブジェクト作成 – $new()メソッドで作成 –reference semantics # オブジェクト作成 alice <- Person$new("Alice", “black") # フィールド,メソッドには$でアクセス alice$hair #> [1] "black" alice$greet() #> こんにちは,Aliceです. alice$set_hair("red") # クラス定義にないフィールドは作れない alice$age <- 40 #> Error in alice$age <- 40 : ... # aliceとalice2は同一人物 alice2 <- alice alice2$set_hair("blue") alice$hair #> [1] "blue" name=“alice” hair=“blue” Person alice Person alice2
  • 15.
    14 R6の機能 • public/private – R6ではprivateフィールド,メソッドを作ることができる •RCにはない機能 • オブジェクト自身のメソッドからしかアクセスできない • (selfではなく)privateを通じてアクセスする # privateを持つクラス Person <- R6Class("Person", private = list(age = NA, # 年齢はprivate # 年齢のgetter get_age = function() private$age), public = list(name = NA, hair = "black", initialize = function(name,hair,age) { # 中略 # メソッドからはprivateフィールドが見える private$age <- age }, set_hair = function(hair) { #略 }, greet = function() { #略 }, # 外向け年齢はサバを読む show_age = function() { private$get_age() - 5 }) ) # オブジェクト作成 alice <- Person$new("Alice", "red", 40) # age, get_age()は外からは見えない alice$age #> NULL alice$get_age() #> Error: attempt to apply non-function alice$show_age() #> [1] 35
  • 16.
    15 R6の機能 • 継承 – スーパークラスをinherit 引数で指定 – スーパークラスのメソッドはsuperを通じて呼べる – パッケージをまたいだ継承が可能(portable class, RCではできない) # inheritで継承 Employee <- R6Class("Employee", inherit = Person, private = list(tsurami = 0), # つらみ public = list( # メソッドオーバーライド initialize = function(name, hair, age, tsurami) { # スーパークラスはsuperで呼ぶ super$initialize(name, hair, age) private$tsurami <- tsurami }, work = function() { private$tsurami <- private$tsurami + 1}, greet = function() { if(private$tsurami > 5) paste0("社畜の", self$name, "です!") else super$greet() }) ) # Johnは社畜 john <- Employee$new("John", "black", 35, tsurami = 10) john$greet() #> 社畜のJohnです!
  • 17.
    16 R6の機能 • パフォーマンス – R6はRCより速くて軽い #RCとR6のパフォーマンス比較 library(microbenchmark) # 適当なクラスを定義 R6 <- R6Class("R6", public = list( x = NULL, initialize = function(x = 1) self$x <- x, inc = function(n = 1) self$x <- self$x + n) ) RC <- setRefClass("RC", fields = list(x = "numeric"), methods = list( initialize = function(x = 1) x <<- x, inc = function(n = 1) x <<- x + n) ) # オブジェクト生成 microbenchmark( r6 <- R6$new(x = 100), rc <- RC$new(x = 100)) #> Unit: microseconds #> expr median neval #> r6 <- R6$new(x = 100) 84.7825 100 #> rc <- RC$new(x = 100) 405.3950 100 # メソッド呼び出し microbenchmark(r6$inc(), rc$inc()) #> Unit: microseconds #> expr median neval #> r6$inc() 16.065 100 #> rc$inc() 62.918 100 # オブジェクトサイズ pryr::object_size(r6) #> 3.94 kB pryr::object_size(rc) #> 465 kB
  • 18.
    17 R6の機能 • ここまでのまとめ • R6はRCと同様,reference semantics のOOシステム • R6は「普通の」オブジェクト指向っぽい – 簡潔なsyntax – public/privateの区別 – パッケージをまたいだ継承 • R6はRCより速くて軽い
  • 19.
    • R6パッケージとは • R6を使う –既存のオブジェクト指向システム(S3, S4, RC) – R6の機能 • R6の中身をのぞいてみる – Rにおける環境 – R6Class()の実装 – $new()の実装 • まとめ 18
  • 20.
    19 R6の中身をのぞいてみる • R6の仕組みを知りたい – R6Class()や$new()って何? –public / private の仕組みは? • 実はR6の実装は意外と簡単 – Rの基本的な機能だけで実装できる • 要は,reference semantics を持つデータ構造を 「普通の」OOPのオブジェクトらしく見えるように 仕立てあげればよい • reference semanticsを持つデータ構造 =「環境」
  • 21.
    20 Rにおける環境 • 環境(environment) =関数を構成する要素のひとつ – Rの関数の3つの構成要素 • formals: 引数 • body: 関数本体 • environment: 変数(名前)をオブジェクトに結びつけるデータ構造 g x 1 g x 2 environment(g) <- e e f <- function() { x <- 1 function() { x + 1 } } g <- f() environment(g) #> <environment: 0x0000000013ddc1b8> # gは親環境にxを探しに行く g() #> [1] 2 # gの環境を変更する e <- new.env() e$x <- 2 environment(g) <- e g() #> [1] 3 <environment: 0x0000000013ddc1b8>
  • 22.
    21 Rにおける環境 • 環境は2つの要素からなる – フレーム:名前をオブジェクトに結びつける –親環境(parent environment) • 関数は親環境を辿っていって名前を探す y <- 2 f <- function() { x <- 1 function() { x + y } } h <- f() h() #> [1] 3 environment(h) #> <environment: 0x000000001424c380> parent.env(environment(h)) #> <environment: R_GlobalEnv> h x 1 <environment: 0x000000001424c380> y 2 globalenv() parent.env()
  • 23.
    22 Rにおける環境 • 環境 ≃reference semantics を持つリスト – 関数とは無関係に,便利なデータ構造としても使える – 環境がだんだん「オブジェクト」に見えてきたような… # 環境e1を作成 e1 <- new.env() e1$x <- 1 e1$x #> [1] 1 # e2を変更するとe1も変わる e2 <- e1 e2$x <- 2 e1$x #> [1] 2 # 関数に渡した環境自体が変更される g <- function(e) { e$y <- 3 } g(e1) e1$y #> [1] 3 # リストl1を作成 l1 <- list() l1$x <- 1 l1$x #> [1] 1 # l2を変更してもl1はそのまま l2 <- l1 l2$x <- 2 l1$x #> [1] 1 # 関数に渡したリスト自体は不変 f <- function(l) { l$y <- 3 } f(l1) l1$y #> NULL
  • 24.
    • R6パッケージとは • R6を使う –既存のオブジェクト指向システム(S3, S4, RC) – R6の機能 • R6の中身をのぞいてみる – Rにおける環境 – R6Class()の実装 – $new()の実装 • まとめ 23
  • 25.
    24 R6Class()の実装 • R6Class()の戻り値は? Person <-R6Class("Person", private = list(age = NA, get_age = function() private$age), public = list(name = NA, hair = "black", initialize = function(name, hair, age) { self$name <- name self$hair <- hair private$age <- age }, set_hair = function(hair) { self$hair <- hair }, greet = function() { paste0("こんにちは,", self$name, "です.") }, show_age = function() { private$get_age() - 5 }) ) Person #> <Person> object generator #> Public: #> name: NA #> hair: black #> initialize: function #> set_hair: function #> greet: function #> show_age: function #> Private: #> age: NA #> get_age: function #> Parent env: <environment: R_GlobalEnv> #> Lock: TRUE #> Portable: TRUE
  • 26.
    25 R6Class()の実装 • R6Class() の戻り値=環境 –R6Class()の引数やnew()を要素に持つgenerator環境がR6クラスの実体 # encapsulate()はR6Classの親環境をcapsuleにする R6Class <- encapsulate(function(classname = NULL, public = list(), private = NULL, inherit = NULL, ...) { # ジェネレータオブジェクトの作成 generator <- new.env(parent = capsule) generator$self <- generator # selfはgenerator自身 generator$classname <- classname generator$public_fields <- get_nonfunctions(public) generator$private_fields <- get_nonfunctions(private) generator$public_methods <- get_functions(public) generator$private_methods <- get_functions(private) # generator_funsはnewを含む関数のリスト # generator_funcs$newの環境をgeneratorにする generator_funs <- assign_func_envs(generator_funs, generator) # generator_funs$newをgenerator$new にコピー list2env2(generator_funs, generator) class(generator) <- "R6ClassGenerator" generator })
  • 27.
  • 28.
    • R6パッケージとは • R6を使う –既存のオブジェクト指向システム(S3, S4, RC) – R6の機能 • R6の中身をのぞいてみる – Rにおける環境 – R6Class()の実装 – $new()の実装 • まとめ 27
  • 29.
    28 $new()の実装 • $new()の戻り値=環境 – R6オブジェクトの実体はpublic_bind_env環境 –generatorからpublic_bind_envにフィールドとメソッドをコピーする # publicフィールド・メソッドのみ,継承なしの場合 generator_funs$new <- function(...) { # publicオブジェクトの束縛環境を作成 public_bind_env <- new.env(parent = emptyenv(), hash = FALSE) # メソッドの環境を作成 enclos_env <- new.env(parent = parent_env, hash = FALSE) # selfの実体はpublic_bind_env enclos_env$self <- public_bind_env # generator$public_methodsの環境をenclos_envにして # public_bind_envにコピー public_methods <- assign_func_envs(public_methods, enclos_env) # フィールドとメソッドをpublic環境にコピー list2env2(public_methods, envir = public_bind_env) list2env2(public_fields, envir = public_bind_env) # オブジェクト初期化 public_bind_env$initialize(...) public_bind_env }
  • 30.
    29 $new()の実装 public_fieldsと public_methodsのコピー generator [Person] self classname public_fields public_methods private_methods private_fields new function alice <-Person$new() initialize set_hair name greet hair function function function “black” “Alice” enclos_env public_bind_env [alice] self emptyenv()
  • 31.
  • 32.
  • 33.
    • R6パッケージとは • R6を使う –既存のオブジェクト指向システム(S3, S4, RC) – R6の機能 • R6の中身をのぞいてみる – Rにおける環境 – R6Class()の実装 – $new()の実装 • まとめ 32
  • 34.
    まとめ • R6は新しいオブジェクト指向システム • R6の特徴 –「普通の」オブジェクト指向っぽい • reference semantics • public / private – RCより速くて軽い • R6の実装 – 環境を活用したシンプルな実装 • 参考 – Introduction to R6 Classes • http://cran.r-project.org/web/packages/R6/vignettes/Introduction.html – Advanced R – Environments • http://adv-r.had.co.nz/Environments.html 33