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.
An expression, to be turned into an iterator.
Undocumented.
Silently rewrite expressions where "yield" appears in chained calls. See async.
Current levels are 0 (no compilation) or -1 (name munging only).
A list, iteror or compatible object.
An error handler
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.
i_chain <- function(...) {
iterators <- list(...)
gen(for (it in iterators) yieldFrom(it))
}
Run the code above in your browser using DataLab