Question

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?

  • Make my iterators from scratch anyway I like in S4 object framework and use some magic (unknown to me at the moment, but has something to do with setOldClass) to make them S3-compatible.
  • Maybe there is another standard S4-compatible iterator package, that I just couldn't find?

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.

Was it helpful?

Solution

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 

OTHER TIPS

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top