Instantiation of reference classes within reference classes - problems with lock() and immutability - r

I have come across some behaviour from R reference classes I would like to work around. In the following code, reference class B has two fields of reference class A in it.
These fields in B appear to be instantiated (possibly twice) with a zero-argument (default) versions of reference class A before B's initialize() method is called. These instances are then replaced with the correct versions of instance A during B's initialization process. The problem is that if I use lock() from B's instance generator, the initial empty instantiation's of A cannot be replaced in B. Another problem is that reference class A needs a default value in initialize [or a missing(c) test].
Help - suggestions - etc. appreciated.
A <- setRefClass('A',
fields = list(
count = 'numeric'
),
methods = list(
initialize = function (c=0) {
cat('DEBUG: A$initialize(c); where c='); cat(c); cat('\n')
count <<- c
}
)
)
instance.of.A <- A$new(10)
str(instance.of.A)
B <- setRefClass('B',
field = list(
a = 'A',
b = 'A'
),
methods = list(
initialize = function(c) {
a <<- instance.of.A
b <<- getRefClass('A')$new(c)
}
)
)
instance.of.b <- B$new(100)
str(instance.of.b)

Here are two possible solutions:
Don't set fields attribute:
B <- setRefClass('B',
methods = list(
initialize = function(c) {
.self$a = instance.of.A
.self$b =getRefClass('A')$new(c)
}
)
)
Set fields, but use the ANY class:
B <- setRefClass('B',
field = (a="ANY", b="ANY"),
methods = list(
initialize = function(c) {
a <<- instance.of.A
b <<- getRefClass('A')$new(c)
}
)
)
The downside of both these solutions is the type isn't enforced in a and b, i.e.
B$a = "Fred"
is now possible.

Drawing on the above, the solution I am using is (a little long because of the type checking):
A <- setRefClass('A',
fields = list(
count = function(x) {
if (!missing(x)) {
if(class(x) != 'numeric')
stop('Class A: count set by non-number')
.self$count.private <- x
}
.self$count.private
}
),
methods = list(
initialize = function (c=0) {
cat('DEBUG: A$initialize(c); where c='); cat(c); cat('\n')
count <<- c
}
)
)
instance.of.A <- A$new(10)
str(instance.of.A)
B <- setRefClass('B',
field = list(
a = function(x) {
if (!missing(x)) {
if(!inherits(x, 'envRefClass') || class(x)[1] != 'A')
stop('Class B: expecting instance of class A')
.self$a.private <- x
}
.self$a.private
},
b = function(x) {
if (!missing(x)) {
if(!inherits(x, 'envRefClass') || class(x)[1] != 'A')
stop('Class B: expecting instance of class A')
.self$b.private <- x
}
.self$b.private
}
),
methods = list(
initialize = function(c) {
a <<- instance.of.A
b <<- getRefClass('A')$new(c)
}
)
)
instance.of.b <- B$new(100)
str(instance.of.b)

Related

Creating Binary Search Tree in R

I have this code for creating a Binary Search Tree in a R6 class.
Creating a Node & BST class. In BST class, I am defining insert_recur function to create the BST by appropriately inserting the data.
library(R6)
Node <- R6Class(
classname = 'Node',
public = list(
val = NULL,
left = NULL,
right = NULL,
initialize = function(val = NULL, left = NULL, right = NULL){
self$val <- val
self$left <- left
self$right <- right
}
)
)
BST <- R6Class(
classname = 'BST',
public = list(
root = NULL,
# node = NULL,
insert = function(data){
if(is.null(self$root)){
self$node <- Node$new(data)
}else{
self$insert_recur(data, self$root)
}
},
insert_recur = function(data, cur_node){
if(data < cur_node$val){
if(is.null(cur_node$self)){
cur_node$left <- Node$new(data)
}else{
insert_recur(data, cur_node$left)
}
}else if(data > cur_node$val){
if(is.null(cur_node$self)){
cur_node$right <- Node$new(data)
}else{
insert_recur(data, cur_node$right)
}
}else{
print('value already in tree')
}
},
get_height = function(cur_node){
if(is.null(cur_node$val)){
return(-1)
}else{
return(max(self$get_height(cur_node$left),self$get_height(cur_node$right))+1)
}
}
)
)
bst <- BST$new()
bst$insert(3)
bst$insert(2)
bst$insert(1)
bst$insert(5)
bst$insert(6)
bst$insert(4)
bst$insert(7)
However I am getting this error -
Error in self$node <- Node$new(data) : cannot add bindings to a locked environment
If I put node <- NULL in the BST class, then the recursion fails & all nodes are NULL.
What will be the correct implementation?
Your Node implementation is fine. The BST isn't quite right though. It should have a NULL root node only. The problem lies in your insert_recur function. It's not possible for cur_node$self to ever be NULL, and the logic would seem to indicate that your if statements should be checking for the absence of cur_node$left and cur_node$right instead. Also, you need to remember to use self$insert_recur. The logic of your get_height argument doesn't seem right to me either. The following implementation seems to work as expected:
BST <- R6Class(
classname = 'BST',
public = list(
root = NULL,
insert = function(data) {
if(is.null(self$root)) {
self$root <- Node$new(data)
} else {
self$insert_recur(data, self$root)
}
},
insert_recur = function(data, cur_node) {
if(data < cur_node$val) {
if(is.null(cur_node$left)) {
cur_node$left <- Node$new(data)
} else {
self$insert_recur(data, cur_node$left)
}
} else if(data > cur_node$val){
if(is.null(cur_node$right)){
cur_node$right <- Node$new(data)
}else{
self$insert_recur(data, cur_node$right)
}
}else{
print('value already in tree')
}
},
get_height = function(cur_node){
if(is.null(cur_node$left) & is.null(cur_node$right)){
return(0)
}else{
return(max(self$get_height(cur_node$left),
self$get_height(cur_node$right)) + 1)
}
}
)
)
This allows
bst <- BST$new()
bst$insert(3)
bst$insert(2)
bst$insert(1)
bst$insert(5)
bst$insert(6)
bst$insert(4)
bst$insert(7)
bst$get_height(bst$root)
#> [1] 3
bst$get_height(bst$root$right)
#> [1] 2
Created on 2022-09-24 with reprex v2.0.2

R, R6, Formals for Public Method

How can I get the formals for a method definition in an R6 class definition?
A = R6Class("MyClass",inherit=NULL,
public = list(
fun = function(a,b,c){
# Do Something
}
)
)
So for example, in the above, I would like to get the formals for the fun definition, in the same way one can execute, for example, formals(lm)
You can do this by creating an instance of the class:
A = R6Class("MyClass",
inherit=NULL,
public = list(
a = NA,
initialize = function(a){
self$a <- a
},
fun = function(a,b,c){
# Do Something
}
)
)
B <- A$new(5)
formals(B$fun)
or by accessing the public methods of the class
formals(A$public_methods$fun)

Testing private methods in R6 classes in R

I am currently using R6 classes in a project.
I would like to write unit tests that also test the functionality of private methods that I am using (preferably by not going through the more complicated public methods that are using these private methods).
However, I can't access seem to access the private methods.
How do I best do that?
Thanks!
Here is a solution that does not require environment hacking or altering the class you want to test, but instead creating a new class that does the testing for you.
In R6, derived classes have access to private Methods of their base classes (unlike in C++ or Java where you need the protected keyword to archieve the same result). Therefore, you can write a TesterClass that derives from the class you want to test. For example:
ClassToTest <- R6::R6Class(
private = list(
privateMember = 7,
privateFunction = function(x) {
return(x * private$privateMember)
}
)
)
TesterClass <- R6::R6Class(
inherit = ClassToTest,
public = list(
runTest = function(x = 5) {
if (x * private$privateMember != private$privateFunction(x))
cat("Oops. Somethig is wrong\n")
else
cat("Everything is fine\n")
}
)
)
t <- TesterClass$new()
t$runTest()
#> Everything is fine
One advantage of this approach is that you can save detailed test results in the TesterClass.
There is currently a way to access the private environment of an R6 object. However, since this is reaching into an object in an undocumented way, it may break in the future... I don't think that will happen any time soon though.
# Gets private environment from an R6 object
get_private <- function(x) {
x[['.__enclos_env__']]$private
}
A <- R6::R6Class("A",
private = list(x = 1)
)
a <- A$new()
get_private(a)$x
# [1] 1
you can add a helper method get to your class:
...
A <- R6::R6Class(
"A",
private = list(
private_print = function(){print("Ola")}
),
public = list(
get = function(name=NULL){
# recursion
if( length(name)>1 ){
tmp <- lapply(name, self$get)
names(tmp) <- name
return(tmp)
}
if(is.null(name)){
self$message("no input, returning NULL")
return(NULL)
}
# self
if(name=="self"){
return(self)
}
# in self
if( name %in% names(self) ){
return(base::get(name, envir=self))
}
# private or in private
if( exists("private") ){
if(name=="private"){
return(private)
}else if(name %in% names(private) ){
return(base::get(name, envir=private))
}
}
# else
self$message("name not found")
return(NULL)
}
)
)
...
Than use it like this:
a <- A$new()
a$get("private_print")()
## [1] "Ola"

Reference class fields disappearing

I decided to give Reference Classes another shot, but my first hello world is already giving me issues. What is going wrong here?
> memory <- setRefClass(
+ Class = "memory",
+ fields = list(state="vector"),
+ methods = list(
+ get = function() { return(state) },
+ set = function(x) { state <<- x }
+ )
+ )$new()
> memory$set(123)
> print(memory)
Reference class object of class "memory"
Field "state":
[1] 123
> memory$get()
[1] 123
> print(memory)
Reference class object of class "memory"
Field "state":
Error in methods::show(field(fi)) :
error in evaluating the argument 'object' in selecting a method for function 'show': Error in get(name, envir = .self) :
unused argument(s) (name, envir = .self)
I'm not very experienced with Reference Classes but according to the help page (?ReferenceClasses), I think that you have to add a show method to your class to be able to print automaticaly your object.
memory <- setRefClass(
Class = "memory",
fields = list(state="vector"),
methods = list(
get = function() { return(state) },
set = function(x) { state <<- x },
show = function() {methods::show(state)}
)
)$new()
memory$set(123)
print(memory)
#[1] 123
memory$get()
#[1] 123
print(memory)
#[1] 123
Hope this help

How to patch an S4 method in an R package?

If you find a bug in a package, it's usually possible to patch the problem with fixInNamespace, e.g. fixInNamespace("mean.default", "base").
For S4 methods, I'm not sure how to do it though. The method I'm looking at is in the gWidgetstcltk package. You can see the source code with
getMethod(".svalue", c("gTabletcltk", "guiWidgetsToolkittcltk"))
I can't find the methods with fixInNamespace.
fixInNamespace(".svalue", "gWidgetstcltk")
Error in get(subx, envir = ns, inherits = FALSE) :
object '.svalue' not found
I thought setMethod might do the trick, but
setMethod(".svalue", c("gTabletcltk", "guiWidgetsToolkittcltk"),
definition = function (obj, toolkit, index = NULL, drop = NULL, ...)
{
widget = getWidget(obj)
sel <- unlist(strsplit(tclvalue(tcl(widget, "selection")),
" "))
if (length(sel) == 0) {
return(NA)
}
theChildren <- .allChildren(widget)
indices <- sapply(sel, function(i) match(i, theChildren))
inds <- which(visible(obj))[indices]
if (!is.null(index) && index == TRUE) {
return(inds)
}
if (missing(drop) || is.null(drop))
drop = TRUE
chosencol <- tag(obj, "chosencol")
if (drop)
return(obj[inds, chosencol, drop = drop])
else return(obj[inds, ])
},
where = "package:gWidgetstcltk"
)
Error in setMethod(".svalue", c("gTabletcltk", "guiWidgetsToolkittcltk"), :
the environment "gWidgetstcltk" is locked; cannot assign methods for function ".svalue"
Any ideas?
How about the old-school way of getting the source, applying the change and rebuilding?
you can first get the generic out, and then fix the generic by setMethod in your global environment, and then assign it back to that namespace
.svalue <- gWidgetstcltk:::.svalue
setMethod(".svalue", c("gTabletcltk", "guiWidgetsToolkittcltk"),
definition = function (obj, toolkit, index = NULL, drop = NULL, ...)
{
widget = getWidget(obj)
sel <- unlist(strsplit(tclvalue(tcl(widget, "selection")),
" "))
if (length(sel) == 0) {
return(NA)
}
theChildren <- .allChildren(widget)
indices <- sapply(sel, function(i) match(i, theChildren))
inds <- which(visible(obj))[indices]
if (!is.null(index) && index == TRUE) {
return(inds)
}
if (missing(drop) || is.null(drop))
drop = TRUE
chosencol <- tag(obj, "chosencol")
if (drop)
return(obj[inds, chosencol, drop = drop])
else return(obj[inds, ])
}#,
#where = "package:gWidgetstcltk"
)
assignInNamespace(".svalue", .svalue, ns = "gWidgetstcltk")

Resources