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)
`gen(...) returns an iteror.
yield(x)
returns the same value x.
yieldFrom returns NULL, invisibly.
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:
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:
|> itertools2::take(100) |> as.numeric() |> mean() rwalk
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:
<- gen(function(x) for (i in 1:x) yield(i)) gseq
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:
<- function(x) {force(x); gen(for (i in 1:x) yield(i))} gseq
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:
is legal, because yield
appears within arguments to {}
,
repeat
, and <-
, for which this package has pausable
definitions. However, this:
is not legal, because yield
appears in an argument to +
, which
does not have a pausable definition.
i_chain <- function(...) {
iterators <- list(...)
gen(for (it in iterators) yieldFrom(it))
}