I want to implement an inset method for my class myClass
for the internal generic [<-
(~ help(Extract)
).
This method should run a bunch of tests, before passing on the actual insetting off to [<-
via NextMethod()
.
I understand that:
NextMethod()
call does not usually need any arguments (though supplying them manually doesn't seem to help either).Here's my reprex:
x <- c(1,2)
class(x) <- c("myClass", "numeric")
`[<-.myClass` <- function(x, i, j, value, foo = TRUE, ...) {
if (foo) {
stop("'foo' must be false!")
}
NextMethod()
}
x[1] <- 3 # this errors out with *expected* error message, so dispatch works
x[1, foo = FALSE] <- 3 # this fails with "incorrect number of subscripts
What seems to be happening is that NextMethod()
also passes on foo
to the internal generic [<-
, which mistakes foo
for another index, and, consequently errors out (because, in this case, x
has no second dimension to index on).
I also tried supplying the arguments explicitly no NextMethod()
, but this also fails (see reprex below the break).
How can I avoid choking up NextMethod()
with additional arguments to my method?
(Bonus: Does anyone know good resources for building methods for internal generics? @Hadleys adv-r is a bit short on the matter).
Reprex with explicit arguments:
x <- c(1,2)
class(x) <- c("myClass", "numeric")
`[<-.myClass` <- function(x, i = NULL, j = NULL, value, foo = TRUE, ...) {
if (foo) {
stop("'foo' must be false!")
}
NextMethod(generic = "`[<-`", object = x, i = i, j = j, value = value, ...)
}
x[1] <- 3 # this errors out with expected error message, so dispatch works
x[1, foo = FALSE] <- 3 # this fails with "incorrect number of subscripts
I don't see an easy way around this except to strip the class (which makes a copy of x
)
`[<-.myClass` <- function(x, i, value, ..., foo = TRUE) {
if (foo) {
cat("hi!")
x
} else {
class_x <- class(x)
x <- unclass(x)
x[i] <- value
class(x) <- class_x
x
}
}
x <- structure(1:2, class = "myClass")
x[1] <- 3
#> hi!
x[1, foo = FALSE] <- 3
x
#> [1] 3 2
#> attr(,"class")
#> [1] "myClass"
This is not a general approach - it's only needed for [
, [<-
, etc because they don't use the regular rules for argument matching:
Note that these operations do not match their index arguments in the standard way: argument names are ignored and positional matching only is used. So
m[j = 2, i = 1]
is equivalent tom[2, 1]
and not tom[1, 2]
.
(from the "Argument matching" section in ?`[`
)
That means your x[1, foo = FALSE]
is equivalent to x[1, FALSE]
and then you get an error message because x
is not a matrix.
Approaches that don't work:
Supplying additional arguments to NextMethod()
: this can only increase the number of arguments, not decrease it
Unbinding foo
with rm(foo)
: this leads to an error about undefined foo
.
Replacing foo
with a missing symbol: this leads to an error that foo
is not supplied with no default argument.
Here's how I understand it, but I don't know so much about that subject so I hope I don't say too many wrong things.
From ?NextMethod
NextMethod invokes the next method (determined by the class vector, either of the object supplied to the generic, or of the first argument to the function containing NextMethod if a method was invoked directly).
Your class vector is :
x <- c(1,2)
class(x) <- "myClass" # note: you might want class(x) <- c("myClass", class(x))
class(x) # [1] "myClass"
So you have no "next method" here, and [<-.default
, doesn't exist.
What would happen if we define it ?
`[<-.default` <- function(x, i, j, value, ...) {print("default"); value}
x[1, foo = FALSE] <- 3
# [1] "default"
x
# [1] 3
If there was a default method with a ...
argument it would work fine as the foo
argument would go there, but it's not the case so I believe NextMethod
just cannot be called as is.
You could do the following to hack around the fact that whatever is called doesn't like to be fed a foo
argument:
`[<-.myClass` <- function(x, i, j, value, foo = FALSE, ...) {
if (foo) {
stop("'foo' must be false!")
}
`[<-.myClass` <- function(x, i, j, value, ...) NextMethod()
args <- as.list(match.call())[-1]
args <- args[names(args) %in% c("","x","i","j","value")]
do.call("[<-",args)
}
x[1, foo = FALSE] <- 3
x
# [1] 3 2
# attr(,"class")
# [1] "myClass"
Another example, with a more complex class :
library(data.table)
x <- as.data.table(iris[1:2,1:2])
class(x) <- c("myClass",class(x))
x[1, 2, foo = FALSE] <- 9999
# Sepal.Length Sepal.Width
# 1: 5.1 9999
# 2: 4.9 3
class(x)
# [1] "myClass" "data.table" "data.frame"
This would fail if the next method had other arguments than x
, i
, j
and value
, in that case better to be explicit about our additional arguments and run args <- args[! names(args) %in% c("foo","bar")]
. Then it might work (as long as arguments are given explicitly as match.call
doesn't catch default arguments). I couldn't test this though as I don't know such method for [<-
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With