How to define a new class method in R? - r

Defining a new function is straight forward - e.g. myfunct <- function(x) { x * 2}
How can we define a new class method in R, such that obj$newmethod calls method newmethod on the object obj?
Desired output
How can we define a method on obj so that it can be called like so
obj <- 3
obj$newmethod
[1] 6
What I tried so far
newmethod <- function(x) {
x * 2
}
obj$newmethod
# Error in obj$newmethod : $ operator is invalid for atomic vectors
Examples of existing class methods
RSelenium package uses a lot of class methods, for example remDr$closeServer() calls the method closeServer() on the object remDr (which is of class remoteDriver) - there are many more examples under the Fields section of the manual (pages 9 - 13).

1) Reference Classes RSelenium uses Reference Classes which is an OO system that is included with R. Rselenium defines 3 reference classes: errorHandler, remoteDriver and webElement.
In terms of the example in the question we can use the code below. No packages are used in the code here.
For more information on reference classes see ?ReferenceClasses.
# define class, properties/fields and methods
Obj <- setRefClass("Obj",
fields = list(obj = "numeric"),
methods = list(
newmethod = function() 2 * obj
)
)
# instantiate an object of class Obj
obj1 <- Obj$new(obj = 3)
# run newmethod
obj1$newmethod()
## [1] 6
2) proto The example code in the question suggests that you may be looking for an object based (rather than class based) system. In that case the proto package provides one and your example works with a slight change in syntax.
library(proto)
p <- proto(obj = 0, newmethod = function(.) 2 * .$obj)
p$obj <- 3
p$newmethod()
## [1] 6
3) local If you don't need inheritance and other features of object orientation you could just do this. No packages are used.
Obj <- local({
obj <- 0
newmethod <- function() 2 * obj
environment()
})
Obj$obj <- 3
Obj$newmethod()
## [1] 6
4) S3 S3 is included with R and is the most widely used OO system in R; however, it is different than conventional OO systems being based on the ideas of the dylan language so it may not correspond exactly to what you are looking for.
# constructor
obj <- function(x) structure(x, class = "obj")
# method
newmethod <- function(x, ...) UseMethod("newmethod")
newmethod.obj <- function(x, ...) 2 * x
# create object obj3 of class "obj" and apply newmethod to it.
obj3 <- obj(3)
newmethod(obj3)
## [1] 6
5) Other Other OO systems are S4 (included in R), the R6 package and the R.oo package. Also try demo("scoping") for another approach.

For simple cases, you could do
f = function(x, newmethod = 2 * x) {
list(x = x, newmethod = newmethod)
}
obj = f(3)
obj$newmethod

Related

Create closure using the base R function constructor

Using base R, is it possible to construct a closure from its 3 components directly? All I could manage so far was the slightly verbose
val <- 3L
fun_a <- function(x = 1L) val + x
fun_b <- function(x = 2L) val * x
fun_c <- function(){}
formals(fun_c) <- formals(fun_a)
body(fun_c) <- body(fun_b)
environment(fun_c) <- list2env(list(val = 5L))
fun_c()
#> [1] 5
Additionally, I cannot seem to figure out how to call function(). Some of the things I have tried:
`function`(formals(fun_a), body(fun_b))
#> Error: invalid formal argument list for "function"
`function`(as.pairlist(formals(fun_a)), body(fun_b))
#> Error: invalid formal argument list for "function"
do.call(`function`, c(formals(fun_a), body(fun_b)))
#> Error in do.call("function", c(formals(fun_a), body(fun_b))) :
#> invalid formal argument list for "function"
I'm aware of rlang::new_function() but here I'm looking for base R solutions.
I don't think you can do it in R code. The function implementing function is meant to be called from the parser, where
function(x = 3) { x }
is translated into something like
`function`( pairlist(as.name(x) = 3), quote({ x }))
but the line above is not legal in R: there's no way to say that the tag on an element of a list should be a name instead of a character value. You could write your function in C, or stick with your verbose solution of creating a template and replacing parts of it one at a time.

Replacement function as R6 class member function

I have been playing around with R6 ab bit and tried to implement a replacement function (similar in spirit to base::`diag<-`()). I wasn't hugely surprised to learn that the following does not work
library(R6)
r6_class <- R6Class("r6_class",
public = list(
initialize = function(x) private$data <- x,
elem = function(i) private$data[i],
`elem<-` = function(i, val) private$data[i] <- val
),
private = list(
data = NULL
)
)
test <- r6_class$new(1:5)
test$elem(2)
#> [1] 2
test$elem(2) <- 3
#> Error in test$elem(2) <- 3 :
#> target of assignment expands to non-language object
What does this correspond to in prefix notation? All of the following work as expected, so I guess it's none of these
test$`elem<-`(2, 3)
`$`(test, "elem<-")(2, 3)
I'm less interested in possible workarounds, but more in understanding why the above is invalid.
You are allowed to have nested complex assignments, e.g.
names(x)[3] <- "c"
but
test$elem(2) <- 3
is not of that form. It would be legal syntax as
elem(test,2) <- 3
which would expand to
*tmp* <- test
test <- `elem<-`(*tmp*, 2, 3)
but in the original form it would have to expand to
*tmp* <- 2
2 <- `test$elem<-`(*tmp*, 3)
(I've used test$elem<- in backticks to suggest it's the assignment version of the function returned by test$elem. That's not really right, there is no such thing.) The main problem is that the object being modified is 2, so you get the error message you saw: you're not allowed to modify 2.
If you want to do this in R6, I think you could do it something like this. Define a global function
`elem<-` <- function(x, arg, value) x$`elem<-`(arg, value)
and change the definition of your class elem<- method to
`elem<-` = function(i, val) { private$data[i] <- val; self }
Not all that convenient to need two definitions for every assignment method, but it appears to work.

Updating S3 methods calls

I am trying to update a call for a new function I developed with a new class. The developing is pretty similar to linmod found in Leish's article "Creating R packages".
Inside the function, the call is stored with match.call().
When I try to update the call, as follows:
library(MASS)
fit <- linmod(Hwt~Bwt*Sex, data=cats)
update(fit, subset = -1)
I got the following error message:
Error in eval(expr, envir, enclos) :
could not find function "linmod.formula"
The problem seems to be that match.call() saves the full S3 method name (linmod.formula), instead of just the generic function name (linmod), which would work perfectly.
Anyone could help me how to solve this problem?
The easiest way I know for fixing this is exporting the method. For this, you need to add #export linmod.formula. Of course, it is generally not recommended to export methods.
Another option is creating a method for update. The following is a copy of update.default with one additional line:
#' #export
update.linmod <- function (object, formula., ..., evaluate = TRUE)
{
if (is.null(call <- getCall(object)))
stop("need an object with call component")
extras <- match.call(expand.dots = FALSE)$...
#call generic instead of method:
call[[1]] <- quote(linmod)
if (!missing(formula.))
call$formula <- update.formula(formula(object), formula.)
if (length(extras)) {
existing <- !is.na(match(names(extras), names(call)))
for (a in names(extras)[existing]) call[[a]] <- extras[[a]]
if (any(!existing)) {
call <- c(as.list(call), extras[!existing])
call <- as.call(call)
}
}
if (evaluate)
eval(call, parent.frame())
else call
}
I dislike both options and would avoid having methods for the linmod function. Your default method seems useless to me. Note how, e.g., lm is not an S3 generic.
PS: update doesn't have a subset parameter.
Since this hasn't been mentioned here yet, and it is the approach explicitly recommended in ?update: write a method for getCall. From ?update:
“Extracting the call” in update() and similar functions uses getCall() which itself is a (S3) generic function with a default method that simply gets x$call. Because of this, update() will often work (via its default method) on new model classes, either automatically, or by providing a simple getCall() method for that class.
So, in your package, if you have:
#' #export
f <- function(x) {
UseMethod("f")
}
#' #export
f.bar <- function(x) {
structure(list(x = x, call = match.call()), class = "fbar")
}
#' #export
#' #importFrom stats getCall
getCall.fbar <- function(x) {
x$call[[1L]] <- quote(f) # replacing `f.bar`
x$call
}
Then, in your script, you could do:
x1 <- structure(1, class = "bar")
x2 <- structure(2, class = "bar")
fx1 <- f(x = x1)
fx2 <- update(fx1, x = x2)
fx1
# $x
# [1] 1
# attr(,"class")
# [1] "bar"
#
# $call
# f.bar(x = x1)
#
# attr(,"class")
# [1] "fbar"
fx2
# $x
# [1] 2
# attr(,"class")
# [1] "bar"
#
# $call
# f.bar(x = x2)
#
# attr(,"class")
# [1] "fbar"

Make S4 object act as an S3 class?

I would like to write an S4 object such that it can be passed to methods that only take an S3 object. (It seems like setOldClass() might be related to this but it's not clear to me from the documentation?)
e.g. for a minimal example imagine I have the S3 class and function:
myS3 <- list(a = 1, b = 2)
class(myS3) <- "myS3class"
myS3function.myS3class <- function(x) x$a + x$b
and I have the S4 object
setClass("myS4class", representation(a = "numeric", b = "numeric"))
obj <- new("myS4class", a = 1, b = 2)
Is there anything I can do such that
myS3function.myS3class(obj)
gives me same thing as
myS3function.myS3class(myS3)
by modifying only the S4 class?
Edit My rationale for this approach is to take advantage of all existing methods for the S3 class (that may generally come from other packages, etc) without having to rewrite them. I realize one approach is simply to write a coercion method (setAs()) that would turn my S4 object into and S3 object, but then a user would always have to perform this step manually. (While it works, I'm also a bit unclear whether it is bad practice to use setAs() to take an S4 class to an S3 class, rather than mapping between S4 classes).
From the way I read the documentation of setOldClass, it sounds like this can make S3 objects act like S4 objects? Is that correct? If so, my question then is if it possible to do the reverse (maybe by setting the prototype in the S4 class?).
If this is not possible, how about an explanation of the rationale why this would be a bad idea?
Add a method to your S4 class to convert it to being an S3 class.
setGeneric(
"as.myS3class",
function(object)
{
standardGeneric("as.myS3class")
}
)
setMethod(
"as.myS3class",
signature(object = "myS4class"),
function(object)
{
structure(list(a = object#a, b = object#b), class = "myS3class")
}
)
Then you can call the S3 method like this:
myS3function(as.myS3class(obj))
A successful solution is indeed buried in the documentation of setOldClass:
## Examples of S3 classes with guaranteed attributes
## an S3 class "stamped" with a vector and a "date" attribute
## Here is a generator function and an S3 print method.
## NOTE: it's essential that the generator checks the attribute classes
stamped <- function(x, date = Sys.time()) {
if(!inherits(date, "POSIXt"))
stop("bad date argument")
if(!is.vector(x))
stop("x must be a vector")
attr(x, "date") <- date
class(x) <- "stamped"
x
}
print.stamped <- function(x, ...) {
print(as.vector(x))
cat("Date: ", format(attr(x,"date")), "\n")
}
## Now, an S4 class with the same structure:
setClass("stamped4", contains = "vector", representation(date = "POSIXt"))
## We can use the S4 class to register "stamped", with its attributes:
setOldClass("stamped", S4Class = "stamped4")
selectMethod("show", "stamped")
## and then remove "stamped4" to clean up
removeClass("stamped4")
someLetters <- stamped(sample(letters, 10),
ISOdatetime(2008, 10, 15, 12, 0, 0))
st <- new("stamped", someLetters)
st
# show() method prints the object's class, then calls the S3 print method.
stopifnot(identical(S3Part(st, TRUE), someLetters))
# creating the S4 object directly from its data part and slots
new("stamped", 1:10, date = ISOdatetime(1976, 5, 5, 15, 10, 0))
Note that the S4 object can use the S3 print method. What surprised me is that this works for other methods that are defined for the S3 class but not the S4 class even without additional calls to selectMethod. I illustrate this with a more detailed example about my use case with ape::phylo object here: http://carlboettiger.info/2013/10/07/nexml-phylo-class-extension.html
If you want to reuse the one function for both S3 and S4 classes, and not change it, you can write your own definition for $:
f <- function(x, name)
slot(x, name)
setMethod("$", signature=c(x="myS4class"), definition=f)
myS3function.myS4class(obj)
# [1] 3
This seems rather dubious to me, though. For starters, you'll probably also need a similar method for [[, since a function could reference a list element either way:
setMethod("[[", signature=c(x="myS4class", i="character"),
definition=function(x, i) slot(x, i))
And you'll need methods for assignment as well:
setMethod("$<-", signature=c(x="myS4class", value="numeric"),
definition=function(x, name, value) `slot<-`(x, name, check=TRUE, value))
setMethod("[[<-", signature=c(x="myS4class", i="character", value="numeric"),
definition=function(x, i, value) `slot<-`(x, i, check=TRUE, value))
But then you have the problem of referencing by number:
obj[[1]]
# Error in obj[[1]] : this S4 class is not subsettable
So you need yet another method:
g <- function(x, i)
{
slots <- names(getClass("myS4class")#slots)
slot(x, slots[i])
}
setMethod("[[", signature=c(x="myS4class", i="numeric"), g)
All up, it seems like a lot of work for not much gain.

Using "[[ ]]" notation for reference class methods

While experimenting with the new reference classes in R I noticed some odd behaviour if you use the "[[ ]]" notation for methods (X[["doSomething"]] instead of X$doSomething). This notation works for fields, but I initially thought it wouldn't work for methods until I found that if you execute "class(X$doSomething)" you can then use "[[ ]]" afterwards. The simple example below illustrates the point.
setRefClass("Number",
fields = list(
value = "numeric"
),
methods = list(
addOne = function() {
value <<- value + 1
}
)
)
X <- new("Number", value = 1)
X[['value']] # 1
X[["addOne"]]() # Error: attempt to apply non-function
class(X[["addOne"]]) # NULL
class(X$addOne) # "refMethodDef"
# Now the following works!
X[["addOne"]]() # sets X$value = 2
class(X[["addOne"]]) # "refMethodDef"
The reason I encountered this is because I want to group my objects together in a list and create an "applyMethod" function which applies a specified method on each of the objects within. Therefore, I need to specify the method as a string. Does anyone have any ideas how I can achieve this?
Here's a class
.A <-
setRefClass("A",
fields=list(x="numeric"),
methods=list(foo=function() x))
If I had an instance a and wanted to construct a call to the 'foo' method using '$' I could
eval(substitute(a$FUN(), list(FUN="foo")))
So I'll create a class Alist that is meant to have a list of elements of class A (this could be enforced programmatically), and that has a .delegate method that'll apply an arbitrary method to all elements of the list. I'll then add a method that delegates foo.
.delegate <- function(FUN, ...)
{
lapply(elts, function(elt, ...) {
eval(substitute(elt$FUN(...), list(FUN=FUN, ...)))
})
}
.Alist <-
setRefClass("Alist",
fields=list(elts="list"),
methods=list(
initialize = function(...) callSuper(elts=list(...)),
.delegate = .delegate,
foo=function() .delegate("foo")))
And then use it
> aList <- .Alist$new(.A$new(x=1), .A$new(x=2))
> aList$foo()
[[1]]
[1] 1
[[2]]
[1] 2
basically R5 ref class does not cache the method until it is necessary. This is probably a kind of delayed evaluation.
And the caching takes place when you access the method via $.
So, AFAIK, there is no way to access the method via [[string]]
But you can find a workaround using .dollarForEnvRefClass like this:
> X <- new("Number", value = 1)
> ls(X#.xData)
[1] "value" # no methods named "addOne" before caching
> X[["addOne"]]
NULL
> methods:::.dollarForEnvRefClass(X, "addOne") # cache it
Class method definition for method addOne()
function ()
{
value <<- value + 1
}
<environment: 0x116a4aa00>
> ls(X#.xData)
[1] "addOne" "value" # you can find it
> X$value # value is 1
[1] 1
> X[["addOne"]]() # call the method
> X$value # the method works
[1] 2
if you are interested in more detail, see the implementation:
http://svn.r-project.org/R/trunk/src/library/methods/R/refClass.R
Maybe there is more straightforward way.
Report as bug on r-devel so John Chambers can fix it.

Resources