R Reference Class issue - r

I am trying to create a simple reference class in R. Here is my code (R beginner):
MyClass <- setRefClass("MyClass",
fields = list(a = "numeric",
b = "numeric"),
methods = list(
initialize <- function(){
print("Initializing")
a <<- 1
b <<- 2
},
printValues <- function(){
print(a)
print(b)
}
)
)
a <- MyClass$new()
a$printValues()
This produces the following error for the last line, a$printValues:
Error in envRefInferField(x, what, getClass(class(x)), selfEnv) :
"printValues" is not a valid field or method name for reference class “MyClass”
Also, the initializer method is not being called ?
Can someone point me to where the issue lies here ? Many thanks in advance.

The methods argument to setRefClass needs to be a named list. The problem is you are using the assign operator <- instead of = when defining your list. See the difference between
list(a = 1, b = 2)
# $a
# [1] 1
#
# $b
# [1] 2
which returns a named list and
list(a <- 1, b <- 2)
# [[1]]
# [1] 1
#
# [[2]]
# [1] 2
which creates a and b in your environment and returns an unnamed list.
So when passing your methods, you need to use =:
methods = list(initialize = function [...],
printValues = function [...]

Related

Can a method be added to the combine function to handle arguments of different classes?

I've made a new class, cvb:
setClass("cvb", slots = c(name = "character", dargs = "list"))
cvb <- function(name, ...) {
out <- list(name = name, dargs = list(...))
class(out) <- "cvb"
out
}
And I'd like to be able to call a method to the "combine" function, c(...), with arguments that are of class cvb, numeric, and character. I can make a method like so (not even needing to bother with S4 actually) if the first argument to c(...) is of class cvb:
c.cvb <- function(...) "In cbv c method"
which will return whatever "combination" of arguments my method defines. But I want to be able to deploy this method if any of the arguments are of class cvb, and use the default method otherwise. If the first argument to c(...) is not of class cvb I get the following:
> v <- cvb("i", 1:3)
> c(1, 2, v)
[[1]]
[1] 1
[[2]]
[1] 2
$name
[1] "i"
$dargs
$dargs[[1]]
[1] 1 2 3
Which combines two numeric types and a list type into a list, even though the first argument isn't a list. This suggests to me that there is some kind of dispatch logic that determines that if any argument is a list argument, combine all arguments into a list. I'd like it to use my c.cvb method instead of whatever is being dispatched here. Any thoughts?
I am not that familiar with S4 dispatch but am somewhat familiar with S3 dispatch. So there is likely a better way, but one option would be to supply your own dispatch logic by overriding c():
# cvb generator
cvb <- function(name, ...) {
out <- list(name = name, dargs = list(...))
class(out) <- "cvb"
out
}
# instance of cvb
v <- cvb("i", 1:3)
# new S3 c method for cvb object
c.cvb <- function(...) "In cbv c method"
# dispatch to new S3 method gives desired output
c(v, 1, 2)
#> [1] "In cbv c method"
# but will only work if the first object is of class cvb
c(1, 2, v)
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] 2
#>
#> $name
#> [1] "i"
#>
#> $dargs
#> $dargs[[1]]
#> [1] 1 2 3
# S3 dispatch pre-hook
c <- function(...) {
if("cvb" %in% unlist(lapply(list(...), class)))
c.cvb(...)
else
base::c(...)
}
# now we get c.cvb if any argument has class cvb
c(1, 2, v)
#> [1] "In cbv c method"
Created on 2021-02-22 by the reprex package (v1.0.0)

How to define options programmatically

I am trying to define options programmatically by using a string vector as shown below. However, the option does not get defined and it returns a NULL value. Are there any best practices or functions for this?
f <- "z"
options(f = TRUE)
getOption("z")
# returns NULL
According to the docs:
Options can also be passed by giving a single unnamed argument which is a named list
So you can do
f <- list(z = TRUE)
options(f)
getOption("z")
#> [1] TRUE
Or, if you want to be able to use the input format in your question, you can use the following function:
prog_options <- function(...)
{
mc <- as.list(match.call()[-1])
names(mc) <-
sapply(names(mc), function(x) eval(as.name(x), envir = parent.frame()))
options(mc)
}
Which allows the following:
f <- "z"
g <- "y"
prog_options(f = TRUE, g = "Yes")
getOption("z")
#> [1] TRUE
getOption("y")
#> [1] "Yes"

Dynamically naming objects to be updated

I have defined an S4 Class with a slot that is a list. I have written a method (based on Genolini's introduction to S4 - section 10.2) to append a new entry to that list:
setClass("MyClass",
slots = c(entries = "list")
)
a1 <- new("MyClass", entries = list(1))
setGeneric(name="MyAppend",
def=function(.Object, newEntry)
{
standardGeneric("MyAppend")
}
)
setMethod(f = "MyAppend",
signature = "MyClass",
definition = function(.Object, newEntry){
nameObject <- deparse(substitute(.Object))
newlist <- .Object#entries
n <- newlist %>% length
newlist[[n + 1]] <- newEntry
.Object#entries <- newlist
assign(nameObject, .Object, envir = parent.frame())
return(invisible)
}
)
If I then run
MyAppend(a1, 2)
a1
I get
R>a1
An object of class "MyClass"
Slot "entries":
[[1]]
[1] 1
[[2]]
[1] 2
which is just as it should be.
But in my application I will be generating the names of the objects to be updated dynamically:
ObjectName <- paste0("a", 1)
then I can turn that name into the object itself with
Object <- ObjectName %>% sym %>% eval
and then str(Object) returns
Formal class 'MyClass' [package ".GlobalEnv"] with 1 slot
..# entries:List of 3
.. ..$ : num 1
.. ..$ : num 2
which, again, is just as it should be.
But when I run
MyAppend(Object, 3)
Object
a1
I get the following that shows that while Object has been updated a1 has not been.
R>Object
An object of class "MyClass"
Slot "entries":
[[1]]
[1] 1
[[2]]
[1] 2
[[3]]
[1] 3
R>
R>a1
An object of class "MyClass"
Slot "entries":
[[1]]
[1] 1
[[2]]
[1] 2
What am I doing wrong, please?
The problem is that this line:
Object <- ObjectName %>% sym %>% eval
Doesn't do what you think it does. The right hand side evaluates to the object a1, so it is no different to doing
Object <- a1
But this creates a copy of a1, it does not create a reference or a pointer or a synonym for a1.
It is possible to create a reference (of sorts) by passing the unevaluated name of the object you wish to append to your generic method. If you leave out the eval part of ObjectName %>% sym %>% eval then Object gets assigned the name a1, which can be passed as a reference to the object a1.
However, this leaves you with a new problem: MyAppend doesn't know what to do with an object of class name. You therefore need to write a suitable method for dealing with names:
setMethod(f = "MyAppend",
signature = "name",
definition = function(.Object, newEntry){
stopifnot(class(eval(.Object)) == "MyClass")
objname <- as.character(.Object)
.Object <- eval(.Object)
.Object#entries <- append(.Object#entries, newEntry)
assign(as.character(objname), .Object, envir = parent.frame())
}
)
Now let's see how this would work:
a1 <- new("MyClass", entries = list(1))
a1
#> An object of class "MyClass"
#> Slot "entries":
#> [[1]]
#> [1] 1
MyAppend(a1, 2)
a1
#> An object of class "MyClass"
#> Slot "entries":
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] 2
Object <- paste0("a", 1) %>% sym()
MyAppend(Object, 3)
a1
#> An object of class "MyClass"
#> Slot "entries":
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] 2
#>
#> [[3]]
#> [1] 3
I think this was what you intended. You may wish to consider having a method that dispatches character strings to make this workflow easier (you would use get inside the method to retrieve the object from the name passed as a character string)
Note that I altered your own function as well; you shouldn't do return(invisible), since this returns the body of the built-in function invisible. Just leave the return statement out altogether. You can also make use of the built-in function append, to make your method for MyClass a bit simpler:
setMethod(f = "MyAppend",
signature = "MyClass",
definition = function(.Object, newEntry){
nameObject <- deparse(substitute(.Object))
.Object#entries <- append(.Object#entries, newEntry)
assign(nameObject, .Object, envir = parent.frame())
}
)
As the accepted answer has mentioned, the line
Object <- ObjectName %>% sym %>% eval
does not function as you intend.
If you want to work with dynamically generated names, the two functions you need to use are get (get an object associated with a name) and assign (assign into an object whose name you compute). Both have comprehensive help pages.
Pretty much nothing else will work (except for eval(parse(text=paste0(...))) but that is not recommended programming practice for a number of reasons.

How to let print() pass arguments to a user defined print method in R?

I have defined an S3 class in R that needs its own print method. When I create a list of these objects and print it, R uses my print method for each element of the list, as it should.
I would like to have some control over how much the print method actually shows. Therefore, the print method for my class takes a few additional arguments. However, I have not found a way to make use of these arguments, when printing a list of objects.
To make this more clear, I give an example. The following code defines two objects of class test, a list that contains both objects and a print method for the class:
obj1 <- list(a = 3, b = 2)
class(obj1) <- "test"
obj2 <- list(a = 1, b = 5)
class(obj2) <- "test"
obj_list <- list(obj1, obj2)
print.test <- function(x, show_b = FALSE, ...) {
cat("a is", x$a, "\n")
if (show_b) cat("b is", x$b, "\n")
}
Printing a single object works as expected:
print(obj1)
## a is 3
print(obj2, show_b = TRUE)
## a is 1
## b is 5
When I print obj_list, my print method is used to print each object in the list:
print(obj_list)
## [[1]]
## a is 3
##
## [[2]]
## a is 1
But I would like to be able to tell print() to show b also in this situation. The following (a bit naive...) code does not produce the desired result:
print(obj_list, show_b = TRUE)
## [[1]]
## a is 3
##
## [[2]]
## a is 1
Is it possible to print obj_list and at the same time pass the argument show_b = TRUE to print.test()? How?
Following Josh's suggestion, I found a way to avoid print.default() being called when printing a list. I simply wrote a print method for lists, since none seems to exist as part of base R:
print.list <- function(x, ...) {
list_names <- names(x)
if (is.null(list_names)) list_names <- rep("", length(x))
print_listelement <- function(i) {
if (list_names[i]=="") {
cat("[[",i,"]]\n", sep="")
} else {
cat("$", list_names[i], "\n", sep="")
}
print(x[[i]], ...)
cat("\n")
}
invisible(lapply(seq_along(x), print_listelement))
}
The relevant part is that ... is passed on to print, when the objects inside the list are printed. So now, coming back to the example in the question, printing a list of test objects works together with show_b =TRUE:
print(obj_list, show_b = TRUE)
## [[1]]
## a is 3
## b is 2
##
## [[2]]
## a is 1
## b is 5
However, I am a bit uncomfortable with defining print.list myself. Chances are that it is not working as well as the built-in printing mechanism for lists.

Check the number of arguments before a certain argument in R

Suppose we want our function to be able to deal with two scenarios:
somefun = function(x, y, method, ...) {
res = dowith(x, y)
res
}
somefun = function(z, method, ...) {
x = z$v1
y = z$v2
res = dowith(x, y)
res
}
How can we make somefun aware of the difference between these two situations?
If you can guarantee that x is never going to be a list, you could just use is.list(x) to determine which version of the function is being called. Otherwise, you can use missing:
somefun<-function(x,y,method,...){
if(missing(y)){
cat("Using List version\n")
y<-x$y
x<-x$x
}
else{
cat("Using normal version\n")
}
c(x,y)
}
> somefun(list(x=1,y=2),method="a method")
Using List version
[1] 1 2
> somefun(1,2,method="a method")
Using normal version
[1] 1 2
>
However, be aware that if you do this, and you want to use the list version of the function, then method and everything after it have to be passed in by name, otherwise R is going to bind them to y:
> somefun(list(x=1,y=2),"a method")
Using normal version
$x
[1] 1
$y
[1] 2
[[3]]
[1] "a method"
> somefun(list(x=1,y=2),method="a method",5)
Using normal version
$x
[1] 1
$y
[1] 2
[[3]]
[1] 5
> somefun(list(x=1,y=2),method="a method",q=5)
Using List version
[1] 1 2
I don't know of an automatic way to do this, but when dealing with these types of situations, it is sometimes helpful to use switch. Here's a basic example:
somefun <- function(x, y = NULL, type = c("DF", "vecs"), method = NULL, ...) {
switch(type,
DF = sum(x[["v1"]], x[["v2"]]),
vecs = sum(x, y),
stop("'type' must be either 'DF' or 'vecs'"))
}
somefun(x = 10, y = 3, type="vecs")
# [1] 13
somefun(x = data.frame(v1 = 2, v2 = 4), type="DF")
# [1] 6
somefun(x = data.frame(v1 = 2, v2 = 4), type = "meh")
# Error in somefun(x = data.frame(v1 = 2, v2 = 4), type = "meh") :
# 'type' must be either 'DF' or 'vecs'
In the above, we're expecting that the user must enter a type argument where the acceptable values are "DF" or "vecs", and where a different set of operations has been defined for each option.
Of course, I would also script out a set of different scenarios and use some condition checking at the start of the function to make sure things will be working as expected. For instance, if you expect that most of the times, people will be inputting a data.frame, you could do something like if (is.null(y) & is.null(type)) temp <- "DF" (or insert a try type statement in there). At the end of the day, it also comes down to whether you can predict a sensible set of default values.
If your functions are complicated, you might want to separate out the steps that go into the switches into separate functions as this would probably lead to more readable (and more easily reusable) code.

Resources