Python っぽい感じでうまくクラスの概念を使ってR を書きたい

写経しながらR で書き換えつつやってみている。
素のR ユーザーはクラスとかそういう概念に触れない or 真面目に勉強しないままきているのでよくわからなくなってる。
ここらへんを参考にしてクラスっぽいものを作ってみる。
R6 class さっそく調査 #rstatsj - Qiita
[翻訳]R6 vignette: R6クラス入門 - Qiita
Rでオブジェクト指向っぽくクラスを使ってみる(R6メソッド) - Qiita
 
ことの発端としては、コンストラクタを作ってそれに対して関数を使ったとき、もとのコンストラクタが更新されて値を持っているという事態に遭遇したことである。
例として、上本のPython コードを出す。

# Python
class MulLayer:
  def __init__(self):
    self.x = None
    self.y = None
  
  def forward(self, x, y):
    self.x = x
    self.y = y                
    out = x * y
    return out
  
  def backward(self, dout):
    dx = dout * self.y
    dy = dout * self.x
    return dx, dy

apple = 100
apple_num = 2
tax = 1.1

# layer
mul_apple_layer = MulLayer()   ### 1
mul_tax_layer = MulLayer()

# forward
apple_price = mul_apple_layer.forward(apple, apple_num)   ### 2
price = mul_tax_layer.forward(apple_price, tax)

# backward
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)

MulLayer というクラスが全体に定義されて、その中で__init__, forward, backward という関数(?) が定義されている。
###1 の部分で、mul_apple_layer というオブジェクトがMulLayer() で作られるが、MulLayer そのものは関数ではなくクラスであって、MulLayer() でオブジェクトが作られる意味がわからない。
これは、__init__ というものがself というものを勝手に作るようにできていて、MulLayer() によって .x と .y によって呼び出せるものが出来上がる、というようになっている。確かに、

mul_apple_layer.x
mul_apple_layer.y

は、エラーをはかない(かわりに何も持ってないので出力もない)が、

mul_apple_layer.z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MulLayer' object has no attribute 'z'

と、持っていない .z についてはエラーをはく。
 
さてこれを何も考えずにR で書くとこうなる。

MulLayer <- function(){
  self <- list(x=NULL, y=NULL)
  return(self)
}

forward <- function(self, x, y){
  self$x <- x
  self$y <- y
  out <- x * y
  return(list(self=self, out=out))
}

backward <- function(self, dout){
  dx <- dout * self$y
  dy <- dout * self$x
  return(list(dx=dx, dy=dy))
}

apple <- 100
apple_num <- 2

# layer
mul_apple_layer <- MulLayer()

# forward
tmp <- forward(mul_apple_layer, apple, apple_num)
apple_price <- tmp$out
mul_apple_layer <- tmp$self   ### 更新作業が必要

MulLayer という関数を作っただけなので、引数なしで、出力としてself というものを返す。
ただし、forward 関数では引数としてのself は、関数内でごそごそいじっても入力としてのself は変更されない。たぶん <<- とか使うといいのかと思ったけどこれでも .x と .y にあたる $x, $y は変更されない。
というわけで、tmp という中間変数的なオブジェクトを作って、再度オブジェクトを作る、というような二度手間をやるはめになる。
 
そこで、R6 というパッケージを使って、クラスなるものを定義して書いてみる。
上のPython と同じように書くと、こうなる。

library(R6)

MulLayer = R6Class("MulLayer",
  public = list(
    x = NULL,
    y = NULL,
    initialize = function(){
      self$x = NA
      self$y = NA
    },
    forward = function(x, y){
      self$x <- x
      self$y <- y
      out <- self$x*self$y
      return(out)
    },
    backward = function(dout){
      dx <- dout * self$y
      dy <- dout * self$x
      return(list(dx=dx, dy=dy))
    }
  )
)

m <- MulLayer$new()
<MulLayer>
  Public:
    backward: function (dout) 
    clone: function (deep = FALSE) 
    forward: function (x, y) 
    initialize: function () 
    x: NA
    y: NA

いま、MulLayer というクラスができて、それで作られたオブジェクトには、$x (.x と同じ) と$y (.y と同じ)という変数が付属している。また、MulLayer に適応できる関数として、forward とbackward という関数がある。
initialize は、これだけを発動すると、$x と$y を持つオブジェクトができる。

m$x
[1] NA
m$y
[1] NA

である。
さてここで、Python っぽいことをしてみよう。MulLayer には.forward という関数が使えて、これの引数は(self, x, y) だった。ただし、self はself.forward とすることで、self の値が x, y に応じて変更されながら、return out するという機能がある。
R6 ではこれをm$forward() という書き方でできて、

m$forward(100, 2)
[1] 200

と、確かにx * y の値が出力される。このとき、m は

m
<MulLayer>
  Public:
    backward: function (dout) 
    clone: function (deep = FALSE) 
    forward: function (x, y) 
    initialize: function () 
    x: 100
    y: 2

となっていて、確かにm$x (m.x と同じ)、m$y (m.y と同じ)とすると、それぞれx, y の値が入って更新されたm が出力される。
というわけで、tmp を作っていちいち更新、という作業がなくなる、と思われる。
 
R6 の書き方を教えてくれたSK 氏に感謝。