gen({...}) with an expression written in its argument, creates a generator, an object which computes an indefinite sequence.

When written inside a generator expression, yield(expr) causes the generator to return the given value, then pause until the next value is requested.

When running in a generator expression, yieldFrom(it)), given a list or iteror in its argument, will yield successive values from that iteror until it is exhausted, then continue.

gen(
  expr,
  ...,
  split_pipes = FALSE,
  compileLevel = getOption("async.compileLevel")
)

yield(expr)

yieldFrom(it, err)

Arguments

expr

An expression, to be turned into an iterator.

...

Undocumented.

split_pipes

Silently rewrite expressions where "yield" appears in chained calls. See async.

compileLevel

Current levels are 0 (no compilation) or -1 (name munging only).

it

A list, iteror or compatible object.

err

An error handler

Value

`gen(...) returns an iteror.

yield(x) returns the same value x.

yieldFrom returns NULL, invisibly.

Details

On the "inside", that is the point of view of code you write in {...}, is ordinary sequential code using conditionals, branches, loops and such, outputting one value after another with yield(). For example, this code creates a generator that computes a random walk:

rwalk <- gen({
  x <- 0;
  repeat {
    x <- x + rnorm(1)
    yield(x)
  }
})

On the "outside," that is, the object returned by gen(), a generator behaves like an iterator over an indefinite collection. So we can collect the first 100 values from the above generator and compute their mean:

rwalk |> itertools2::take(100) |> as.numeric() |> mean()

When nextOr(rwalk, ...) is called, the generator executes its "inside" expression, in a local environment, until it reaches a call to yield(). THe generator 'pauses', preserving its execution state, and nextElem then returns what was passed to yield. The next time nextElem(rwalk) is called, the generator resumes executing its inside expression starting after the yield().

If you call gen with a function expression, as in:

gseq <- gen(function(x) for (i in 1:x) yield(i))

then instead of returning a single generator it will return a generator function (i.e. a function that constructs and returns a generator.) The above is morally equivalent to:

gseq <- function(x) {force(x); gen(for (i in 1:x) yield(i))}

so the generator function syntax just saves you writing the force call.

A generator expression can use any R functions, but a call to yield may only appear in the arguments of a "pausable" function. The async package has several built-in pausable functions corresponding to base R's control flow functions, such as if, while, tryCatch, <-, {}, || and so on (see pausables for more details.) A call to yield may only appear in an argument of one of these pausable functions. So this random walk generator:

rwalk <- gen({x <- 0; repeat {x <- yield(x + rnorm(1))}})

is legal, because yield appears within arguments to {}, repeat, and <-, for which this package has pausable definitions. However, this:

rwalk <- gen({x <- rnorm(1); repeat {x <- rnorm(1) + yield(x)}})

is not legal, because yield appears in an argument to +, which does not have a pausable definition.

Examples

i_chain <- function(...) {
  iterators <- list(...)
  gen(for (it in iterators) yieldFrom(it))
}