Unary plus for S4 class in R - r

I am experimenting with S4 classes in R and I was trying to define a plus (+) operator for my objects, i.e. overload the plus operator. I managed to overload the binary +, but I cannot figure out how to overload the unary plus. Here is a minimal working (unary operator not working) example for what I am trying to achieve:
setClass("HWtest",
representation(expr = "character"),
prototype = list(expr = NA_character_)
)
H <- new("HWtest", expr="Hello")
W <- new("HWtest", expr="World")
setMethod("+", signature(e1="HWtest", e2="HWtest"),
function(e1,e2){
new("HWtest",
expr = paste(e1#expr," + ",e2#expr))
}
)
Now I can use the + operator and it works smoothly:
H+W
An object of class "HWtest"
Slot "expr":
[1] "Hello + World"
Now the unary plus of course doesn't work, so that has to be overloaded
+H
Error in +H : invalid argument to unary operator
So I tried to overload it in the following way:
setMethod("+", signature(e="HWtest"),
function(e){
new("HWtest",
expr = paste("+ ",e#expr))
}
)
But this generates the error:
Error in match.call(fun, fcall) :
argument 1 matches multiple formal arguments
Is it possible to overload the unary plus? If so, how would I do it for this minimal example?

Try adding this (in addition to your binary overload):
setMethod("+", signature(e1 = "HWtest", e2 = "missing"),
function(e1){
new("HWtest",
expr = paste("+ ", e1#expr))
}
)
R> +H
An object of class "HWtest"
Slot "expr":
[1] "+ Hello"
R> H + W
An object of class "HWtest"
Slot "expr":
[1] "Hello + World"
Citing the help file ?groupGeneric,
[...] when a unary operator is encountered the Ops method is called
with one argument and e2 is missing.
And as Frank points out, the ?setMethod help file contains some useful information regarding the use of missing as a method signature (emphasis added):
Two additional special class names can appear: "ANY", meaning that
this argument can have any class at all; and "missing", meaning that
this argument must not appear in the call in order to match this
signature.

Related

How to set an S4 method for 2+ classes?

I would like to set a method for to 2+ classes, and also enable to pass additional argument.
The 2 classes :
# Classes
methods::setClass(
Class = "A",
slots = list(
x = "character"
)
)
methods::setClass(
Class = "B",
slots = list(
x = "character"
)
)
The method dispatch should be done only for one object (the object argument, which is set as the signature of the generic). The Generic :
# Generic
methods::setGeneric(
"method1",
def = function(object, additionalArg) base::standardGeneric("method1"),
signature = "object"
)
The method should make use of the object slot, and also the additionalArg if it is passed. The Method :
# Method
methods::setMethod(
"method1",
signature = c(
"A",
"B"
),
definition = function(object, additionalArg = NULL) {
print(glue::glue("Object slot : {object#x}"))
if (!is.null(additionalArg)) {
print(glue::glue("Additional argument : {additionalArg}"))
}
}
)
However, the setMethod fails, even though the signature is set to the object argument, and both the generic and the method implementation for objects of class A and B contain the object and additionalArg arguments.
The error :
Error in matchSignature(signature, fdef) :
more elements in the method signature (2) than in the generic signature (1) for function ‘method1’
Edit:
This happens also with the generic and the method having only "object" as argument, so it is not due to having more than 1 argument.
You can only set one method at a time. If you make two separate calls to setMethod (one for A and one for B), using the same function definition each time, then you will have no problem.
Alternatively, you can call setClassUnion to create a class union of A and B, and use that class union in your call to setMethod.

When does initialize check for object validity?

From Chambers' (excellent) Extending R (2016):
A validity method will be called automatically from the default method for initialize(). The recommended form of an initialize method ends with a callNextMethod() call, to ensure that subclass slots can be specified in a call to the generator for the class. If this convention is followed, initialization will end with a call to the default method, and the validity method will be called after all initialization has occurred.
I thought I understood, but the behavior I am getting does not seem to follow this convention.
setClass("A", slots = c(s1 = "numeric"))
setValidity("A", function(object) {
if (length(object#s1) > 5) {
return("s1 longer than 5")
}
TRUE
})
setMethod("initialize", "A", function(.Object, s1, ...) {
if (!missing(s1)) .Object#s1 <- s1 + 4
callNextMethod(.Object, ...)
})
A <- new("A", rep(1.0, 6))
A
# An object of class "A"
# Slot "s1":
# [1] 5 5 5 5 5 5
validObject(A)
# Error in validObject(A) : invalid class “A” object: s1 longer than 5
I expected the validity checking to be done by adding callNextMethod() to the end of the initialize method. Adding an explicit validObject(.Object) before callNextMethod() works, but I am clearly not understanding something here.
Obviously, I can also do all the same checks in the validity method, but ideally all of the validity checking would occur within setValidity so future edits live in one place.
Changing the initialize function slightly gives the desired result -- is there a reason to use one approach over the other? Chambers seems to prefer using .Object#<- whereas I have seen the following method elsewhere (Gentlemman & Hadley).
setMethod("initialize", "A", function(.Object, s1, ...) {
if (!missing(s1)) s1 + 4
else s1 <- numeric()
callNextMethod(.Object, s1 = s1, ...)
})
Perhaps the best guide comes from initialize itself — if you inspect the code for the default method
getMethod("initialize",signature(.Object="ANY"))
then you see that it does indeed contain an explicit call to validObject at the end:
...
validObject(.Object)
}
.Object
}
so if you define your own initialize method, the most similar thing you could do would be to call it at the end of your method, right before you call callNextMethod.
In your case, when you call callNextMethod, that is only checking that the slot you have created is a valid numeric object (which it is), rather than checking the validity of the larger object (which requires the s1 slot to be no longer than 5 elements)

Partial matching confusion when arguments passed through dots ('...')

I've been working on an R package that is just a REST API wrapper for a graph database. I have a function createNode that returns an object with class node and entity:
# Connect to the db.
graph = startGraph("http://localhost:7474/db/data/")
# Create two nodes in the db.
alice = createNode(graph, name = "Alice")
bob = createNode(graph, name = "Bob")
> class(alice)
[1] "node" "entity"
> class(bob)
[1] "node" "entity"
I have another function, createRel, that creates a relationship between two nodes in the database. It is specified as follows:
createRel = function(fromNode, type, toNode, ...) {
UseMethod("createRel")
}
createRel.default = function(fromNode, ...) {
stop("Invalid object. Must supply node object.")
}
createRel.node = function(fromNode, type, toNode, ...) {
params = list(...)
# Check if toNode is a node.
stopifnot("node" %in% class(toNode))
# Making REST API calls through RCurl and stuff.
}
The ... allows the user to add an arbitrary amount of properties to the relationship in the form key = value. For example,
rel = createRel(alice, "KNOWS", bob, since = 2000, through = "Work")
This creates an (Alice)-[KNOWS]->(Bob) relationship in the db, with the properties since and through and their respective values. However, if a user specifies properties with keys from or to in the ... argument, R gets confused about the classes of fromNode and toNode.
Specifying a property with key from creates confusion about the class of fromNode. It is using createRel.default:
> createRel(alice, "KNOWS", bob, from = "Work")
Error in createRel.default(alice, "KNOWS", bob, from = "Work") :
Invalid object. Must supply node object.
3 stop("Invalid object. Must supply node object.")
2 createRel.default(alice, "KNOWS", bob, from = "Work")
1 createRel(alice, "KNOWS", bob, from = "Work")
Similarly, if a user specifies a property with key to, there is confusion about the class of toNode, and stops at the stopifnot():
Error: "node" %in% class(toNode) is not TRUE
4 stop(sprintf(ngettext(length(r), "%s is not TRUE", "%s are not all TRUE"),
ch), call. = FALSE, domain = NA)
3 stopifnot("node" %in% class(toNode))
2 createRel.node(alice, "KNOWS", bob, to = "Something")
1 createRel(alice, "KNOWS", bob, to = "Something")
I've found that explicitly setting the parameters in createRel works fine:
rel = createRel(fromNode = alice,
type = "KNOWS",
toNode = bob,
from = "Work",
to = "Something")
# OK
But I am wondering how I need to edit my createRel function so that the following syntax will work without error:
rel = createRel(alice, "KNOWS", bob, from = "Work", to = "Something")
# Errors galore.
The GitHub user who opened the issue mentioned it is most likely a conflict with setAs on dispatch, which has arguments called from and to. One solution is to get rid of ... and change createRel to the following:
createRel = function(fromNode, type, toNode, params = list()) {
UseMethod("createRel")
}
createRel.default = function(fromNode, ...) {
stop("Invalid object. Must supply node object.")
}
createRel.node = function(fromNode, type, toNode, params = list()) {
# Check if toNode is a node.
stopifnot("node" %in% class(toNode))
# Making REST API calls through RCurl and stuff.
}
But, I wanted to see if I had any other options before making this change.
Not really an answer, but...
The problem is that the user-provided argument 'from' is being (partially) matched to the formal argument 'fromNode'.
f = function(fromNode, ...) fromNode
f(1, from=2)
## [1] 2
The rules are outlined in section 4.3.2 of RShowDoc('R-lang'), where named arguments are exact matched, then partial matched, and then unnamed arguments are assigned by position.
It's hard to know how to enforce exact matching, other than using single-letter argument names! Actually, for a generic this might not be as trite as it sounds -- x is a pretty generic variable name. If 'from' and 'to' were common arguments to ... you could change the argument list to "fromNode, , ..., from, to", check for missing(from) in the body of the function, and act accordingly; I don't think this would be pleasant, and the user would invariable provide an argument 'fro'.
While enforcing exact matching (and errors, via warn=2) by setting global options() might be helpful in debugging (though by then you'd probably know what you were looking for!) it doesn't help the package author who is trying to write code to work for users in general.
It might be reasonable to ask on the R-devel mailing list whether it might be time for this behavior to be changed (on the 'several releases' time scale); partial matching probably dates as a 'convenience' from the days before tab completion.

S4 method with a scalar(non vector) return value

I want to define an S4 method that return a scalar return value. Here I mean by scalar value , the contrary of a vector.
setGeneric("getScalar", function(value, ...)
standardGeneric("getScalar")
)
setMethod("getScalar",
signature(value = "ANY"),
def = function(value, ...) getScalar(value,...), ## call external function
valueClass = "atomic" ### atomic is false, what should I do ?
)
I can't override the method by its output , I mean I can't define many function having the same signature with a different return valueClass :numeric , integer , character ,..
So How can I do this?
EDIT to give more context :
I think is atomic is confusing here. I mean by scalar a numeric value or a boolean or a character, of length one. To give more context I will have 3 functions in my package:
dbGetQuery :return a list/data.frame : i.e some table rows
dbGetScalar :return a scalar value : i.e count(*),table_name,..
dbGetNoQuery :return nothing : update/insert actions
It is an extension to DBI interface.
EDIT2
We can assume that scalar is a vector of length 1. But I can't express this condition using S4. in c# or c, I would write
double[] // vector
double // scalar
Maybe I should just change the name of my function.
One possibility is to check the value of the return type after method dispatch
setGeneric("getScalar", function(x, ...) {
value <- standardGeneric("getScalar")
if (!is.atomic(value) || length(value) != 1L)
stop("not a scalar atomic vector")
value
})
setMethod(getScalar, "ANY", function(x, ...) x)
Another possibility is to define a 'Scalar' class, with a validity check on the base class that enforces the constraint
.Scalar <- setClass("Scalar", contains="ANY", validity=function(object) {
if (length(object) != 1L)
"non-scalar object"
else TRUE
}, prototype=NA)
or controlling scalar types more strongly with a small hierarchy based on a virtual class
setClass("Scalar", validity=function(object) {
if (length(object) != 1L)
"non-scalar object"
else TRUE
})
.ScalarInteger <- setClass("ScalarInteger",
contains=c("Scalar", "integer"),
prototype=prototype(NA_integer_))
This is the approach taken in Bioconductor's Biobase package, with a mkScalar constructor.

overloading operators when using setOldClass not works as wanted compared to an S4 class

Can someone explain why overloading an operator on an old-style S3 class which is registered does not work as expected while when defining a new class and overloading the operators does work.
As shown in the following examples.
This does not work.
require(ff)
setOldClass(Classes=c("ff_vector"))
setMethod(
f="*",
signature = signature(e1 = c("ff_vector"), e2 = c("ff_vector")),
definition = function (e1, e2){
print("S3 setOldClass")
e1[] * e2[]
}
)
ff(1:10) * ff(1:10)
Error in ff(1:10) * ff(1:10) : non-numeric argument to binary operator
But this works.
setClass("myff_vector", representation(x="ff_vector"))
setMethod(
f="*",
signature = signature(e1 = c("myff_vector"), e2 = c("myff_vector")),
definition = function (e1, e2){
print("S4 setOldClass")
e1#x[] * e2#x[]
}
)
new("myff_vector", x = ff(1:10)) * new("myff_vector", x = ff(1:10))
[1] "S4 setOldClass"
[1] 1 4 9 16 25 36 49 64 81 100
Attempt at a partial answer:
In help('Methods'), in the Generic Functions section, it is stated:
Methods may be defined for most primitives, and corresponding metadata
objects will be created to store them. Calls to the primitive still go
directly to the C code, which will sometimes check for applicable
methods. The definition of “sometimes” is that methods must have been
detected for the function in some package loaded in the session and
isS4(x) is TRUE for the first argument (or for the second argument, in
the case of binary operators).
Back to your problem, * is a primitive, and:
library(ff)
setOldClass("ff_vector")
isS4(ff(1:10))
[1] FALSE
So from what I understand, it is not possible to define a method for primitive functions on S3 classes, even if you use setOldClass().
It's not really clear from the question whether this counts as an answer, but for the record, the operator could be overloaded in plain and simple S3-style, without any setOldClass or S4:
`*.ff_vector` <- function(x, y) {
print("hi")
x[] * y[]
}
> ff(1:10) * ff(1:10)
[1] "hi"
[1] 1 4 9 16 25 36 49 64 81 100

Resources