Reputation: 8064
I'd like to write custom iterators with in project. I can do this from scratch, but I prefer to do it the standard way, so the code would be easier to read by others. The iterators
package, which seems to be a standard approach to iterators, is written using S3. Unfortunately I don't have much expertise with S3 objects (especially the part how to work with S3 objects in S4 object framework).
The iterators I plan to write would support the hasNext
method for assessing if the next iteration is possible, and some of them reset
, that would reset the iteration to the beginning.
According to the vignette, the "standard" iterators defined in the iterators
don't have this functionality, so I would need to provide it.
The question: what is the best practice in my scenario?
setOldClass
) to make them S3-compatible. Obviously it would be very nice to see some working foreach
-compatible S4 iterator example with custom methods, but at least please provide me a direction where to invest my effort to solve my problem.
Upvotes: 1
Views: 289
Reputation: 46856
Following the iforever
iterator in vignette("writing", package="iterators")
, one could formalize the S3 class as
setOldClass(c("abstractiter", "iter"))
and then create a derived class
.IForever <- setClass("IForever", contains=c("list", "abstractiter"))
with a constructor
IForever <- function(x)
{
nextEl <- function() x
obj <- list(nextElem=nextEl)
.IForever(obj)
}
and use
> nextElem(it)
[1] 42
> nextElem(it)
[1] 42
> unlist(as.list(it, n=6))
[1] 42 42 42 42 42 42
I'm not sure that this is getting you that much, though. The iterator is using a closure to provide state, and this part of the puzzle is being provided by the plain IForever
function rather than some aspect of the S4 class system. The formalism of S4 might lead to a better-defined API, e.g., methods defined on an AbstractIterator base class, but that's already only implicit in the S3 implementation of abstractiter.
A different (better) starting point would be a reference class specifying a required method
.RIterator <- setRefClass("RIterator",
contains="abstractiter",
methods=list(nextElem=function() stop("not implemented")))
Iterators could be implemented on top of that,
LimitIterator <- setRefClass("SleepIterator",
fields=list(times="integer", .curr="integer"),
contains="RIterator",
methods=list(
initialize=function(...) initFields(..., .curr=1L),
nextElem=function() {
if (!hasNext())
stop("StopIteration")
.curr <<- .curr + 1L
invisible(NULL)
}, hasNext=function() .curr <= times))
so
> system.time(foreach(LimitIterator(times=8L)) %do% Sys.sleep(1L))
user system elapsed
0.052 0.000 8.057
> system.time(foreach(LimitIterator(times=8L)) %dopar% Sys.sleep(1L))
user system elapsed
0.084 0.436 1.261
Upvotes: 3
Reputation: 18437
I think you should take a look to the itertools
package, which has a lot of functions to deal with iterators but is mostly S3.
But I don't think that it will be difficult to write an S4 version of those functions.
You have many options based on the design and api you want for your package.
More info here
Just for example, there is a hasNext
function (or method for you).
require(itertools)
it <- ihasNext(1:3)
hasNext(it)
## [1] TRUE
nextElem(it); nextElem(it); nextElem(it)
## [1] 1
## [1] 2
## [1] 3
hasNext(it)
## [1] FALSE
Upvotes: 0