Private Members in R Reference Class
This answer doesn't work with R > 3.00, so don't use it!
As has been mentioned, you can't have private member fields. However, if you use the initialize method, then the balance isn't displayed as a field. For example,
Account = setRefClass("ref_Account",
fields = list(number = "character"),
methods = list(
initialize = function(balance, number) {
.self$number = number
.self$balance = balance
})
As before, we'll create an instance:
tb <- Account$new(balance=50.75, number="baml-0029873")
##No balance
tb
Reference class object of class "ref_Account"
Field "number":
[1] "baml-0029873"
As I mentioned, it isn't truly private, since you can still do:
R> tb$balance
[1] 50.75
R> tb$balance = 12
R> tb$balance
[1] 12
I came across a similar problem and implemented it this way using Base R. I tend to make things harder on myself by not using third party packages like R6. To solve this problem I access the environment where the object methods are defined and store variables that way.
In this example, I am trying to implement a MinMaxScaler like that found in scikit learn:
## Base reference class
setRefClass(
"Transformer",
contains = "VIRTUAL",
methods = list(
fit = function(data) stop("Must implement"),
transform = function(data) stop("Must implement"),
fit_transform = function(data) {
fit(data)
transform(data)
}
))
Concrete implementation of the Transformer API. In the fit
method, I access the environment where fit
is defined. I then use that environment to store whatever variables I need for intermediate calculations and to update the object in place -- just like sklearn.
MinMaxScaler <-setRefClass(
"MinMaxScaler",
contains = "Transformer",
fields = c(feature_range = "numeric"),
methods = list(
fit = function(data) {
env <- environment(fun = .self$fit)
rng <- range(data, na.rm=TRUE)
env$data_range_ <- diff(range(data, na.rm=TRUE))
env$data_min_ <- rng[[1]]
env$data_max_ <- rng[[2]]
},
transform = function(data) {
env <- environment(fun = .self$transform)
scalef <- diff(range(feature_range))
scalef * (data - env$data_min_) / env$data_range_ + min(feature_range)
}
)
)
To demonstrate this pattern, I will create two scalers and fit them separately:
> ## Dummy data
> set.seed(123)
> z <- rnorm(1e4)
> summary(z)
Min. 1st Qu. Median Mean 3rd Qu. Max.
-3.845320 -0.667969 -0.011089 -0.002372 0.673347 3.847768
>
> scaler1 <- MinMaxScaler(feature_range=c(0, 50))
> summary(scaler1$fit_transform(z))
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.00 20.65 24.92 24.98 29.37 50.00
>
> scaler2 <- MinMaxScaler(feature_range=c(-100, 100))
> summary(scaler2$fit_transform(z))
Min. 1st Qu. Median Mean 3rd Qu. Max.
-100.00000 -17.39725 -0.32011 -0.09347 17.47344 100.00000
>
> ## to show the scalers are distinct and not sharing private vars
> summary(scaler1$transform(z))
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.00 20.65 24.92 24.98 29.37 50.00
> summary(scaler2$transform(z))
Min. 1st Qu. Median Mean 3rd Qu. Max.
-100.00000 -17.39725 -0.32011 -0.09347 17.47344 100.00000
To solve the issue of privacy I create an own class, "Private", which has new methods to access the object, i.e. $
and [[
. These methods will throw an error if the client tries to access 'private' member. Private member are identified by the name (leading period). As reference Objects are environments in R one can work around this, but it is my solution at this time and I think more convenient to use get/set methods provided by the class. So this is more the 'hard-to-set-from-outside-the-class' solution to the question.
I have organised this inside a R-package so the following code makes use of that package and modifies the above example such that an assignment to tb$.balance
produces an error. I also use the function Class
which is just a wrapper around setRefClass
so this is still in the scope of R's reference classes provided by the methods package and used in the question.
devtools::install_github("wahani/aoos")
library("aoos")
Account <- defineRefClass({
Class <- "Account"
contains <- "Private"
number <- "character"
.balance <- "numeric"
deposit <- function(amount) {
if(amount < 0) stop("deposits must be positive")
.balance <<- .balance + amount
}
withdraw <- function(amount) {
if(amount < 0) stop("withdrawls must be positive")
.balance <<- .balance - amount
}
})
tb <- Account(.balance = 50.75, number = "baml-029873")
tb$.balance # error
tb$.balance <- 12 # error