- The
iterorclass has been extracted to a new packageiterors, which also includes ports of all functionality fromiterators,itertools, anditertools2. - New vignette
vignette("spider")shows how to control several concurrent downloads usingasync/await. - Improved memory usage: Coroutines which exit drop references to their evaluation environments, allowing them to be garbage collected.
debugAsyncnow accepts atraceargument to control printing of execution traces to console. Execution traces now print node addresses.- Various inspection methods
getNode,getState,getEnv,getOrighave been consolidated into the methodsummary.coroutine. ihasNexthas been removed (in the process of extracting a separateiterorspackage.)- S3 method argument names have been made consistent.
Version 0.3 of async contains a number of new features, usability and performance improvements.
-
Coroutines now support single step debugging. Use
debugAsync(gen, TRUE)to open a browser at each point the coroutine reaches an R expression. UsedebugAsync(gen, internal=TRUE)to step through at a lower level, inside theasyncpackage implementation. -
There is now a function
goto(branch)that works inside ofswitchstatements, allowing you to jump to another branch; whilegoto()with no arguments will jump back re-evaluate the switch argument. -
Coroutines now support exit handlers; handlers are registered with
on.exitand will be run when the coroutine reaches the end of its code, either normally or by error. -
There is now an experimental implementation of
channeldatatype, which is like a combination of an iterator and a promise; a channel represents a sequence of values yet to be determined. You can treat a channel as an iterator that returns a series of promises, or to be slightly more streamlined you can usenextThen(onNext, onError, onClose), providing callbacks to receive the next value. Anasynccan retrieve values from a channel usingawaitNextor aforloop. The implementation of channel includes queues on both input and output, which allows flexibility for different types of data sources. -
To go along with channels there is now a
streamcoroutine. A stream implements thechannelinterface, and in its expression you can use bothawaitandyield. Additionally a stream can com in two flavors,lazy=TRUE(where the stream begins in a paused state, and will not do anything until it has at least one listener request) andlazy=FALSE(the stream starts executing immediately, and will continue executing afteryield, even with no listeners, queuing up output values until it reaches anawait). For comparison, this package'sgencoroutines are lazy in this sense, whileasyncare eager. -
Generators are now displayed with a label corresponding to where in their code they were last paused. The function
getNode(g)queries this label. For example:
> g <- gen(for (i in 1:10) {yield(i); if (x %% 2 == 0) yield("even.") else yield("odd!")})
> g
gen(for (i in 1:10) {
yield(i)
if (x%%2 == 0)
yield("even.")
else yield("odd!")
})
<environment: R_GlobalEnv>
<Generator [yielded at `.for2.R__eval_`]>
> nextElem(g)
[1] 1
> getNode(g)
[1] ".for3.{1;__;"
> nextElem(g)
[1] "odd!"
> getNode.generator(g)
[1] ".for__again"
To unpack the above, on creation the generator is paused at node ".for2.R__eval_" i.e. the R expression in the second argument of for, which in this case is the expression 1:10. After executing the first yield, the generator shows it is paused at ".for3.{1;__;", that is, at the "semicolon" following the first statement in the braces in the third argument of for; so the next thing the generator will execute is the subsequent if statement. After the next yield, the label ".for__again" means that the generator's next action will be to return to the beginning of the for loop.
-
Under the hood, the implementation has been refactored heavily in order to enable compilation; the package now includes the back half of a compiler. To create a compiled generator you can write
gen({...}, compileLevel=-1); this will take a bit of CPU time but shouldn't change the functionality. (Or speed; making it faster will be the job of the front half.) -
A side benefit of compilation work is that coroutines can draw a picture of their graph structure,
graphAsync(myGen)collects a coroutine's structure and writes a Graphviz DOT file (which by default would be namedmyGen.dotin the current directory.) If the Graphvizdotcommand is visible on your$PATHit will then be run to render a PDF file. -
There is now a syntax for "generator functions" (as well as async functions, and stream functions). If the argument is a function expression,
genwill construct a function that constructs a generator. So these two calls are nearly equivalent:
g <- gen(function(x, y) for (i in x:y) yield(i))
g <- function(x, y) {force(list(x, y)); gen(for (i in x:y) yield(i))}
(so really it just saves you from remembering to force your arguments.)
- Re-generate documentation for R-devel
Fixes:
- Namespacing issue in test vs check
Changes:
- Generators and asyncs work in a localy created scope.
tryCatchfor error handlingsplit_pipeshelps useawaitin pipelines- Cranwhales Shiny app demo
traceoption for logging of async operationspausableslists all pausable functionsdelayfor sleeping anasync
Changes:
- Rename from "generators" to "async"
- Async/await blocks implementing the promise interface
- Performance improvements from building the call graph ahead of time
- Initial Github release.