COMMUNITY

Pecan coroutines

Hey all,

pecan is a macro-based library for Haxe that lets you write coroutines, which are functions that can suspend their execution at any point:

var co = pecan.Co.co({
  while (true)
    trace('Hello, ${accept()}!');
}, (_ : String)).run();
co.give("Haxe"); // Hello, Haxe!
co.give("community"); // Hello, community!

It has been around for some time, but I recently rewrote it from scratch, this time basing the transformation around the typed AST instead of the untyped one – as a result, more Haxe syntax is supported, type inference works better, and more.

I’ve also moved the documentation into a nicer manual form.

A summary of the features:

  • suspendable functions written using regular Haxe syntax
  • input and output using accept() and yield(...), in addition to arguments and return values
  • simple way to define suspending functions, e.g. for interop with Promises
  • shorthand syntax for immediately invoked coroutines and class field-like coroutines
  • macro-based, no dependencies on other libraries or plugins
  • labels and goto

I find coroutines incredibly useful for certain kinds of game logic and user interaction in general. Hopefully you find this useful as well! :slight_smile:

18 Likes

I must say I really like this lower-level coroutine implementation approach, as opposed to the popular high-level promise-powered async/await libraries. It enables different use-cases for coroutines, including async/await for different kinds of promise/future primitives as well as different kinds of scripting DSLs like animations or game logic. Very much in the spirit of my initial coroutine proposal for Haxe :slight_smile:

I also think that this is a very important research/exploration/proof-of-concept work for the Haxe’s own coroutines that will eventually come, great job! But what’s especially great is that it’s already here and ready-to-use. I’m going to play with it a bit more and come back with some usage snippets, my first one would be async/await helpers for tink.core Futures.

5 Likes

Thank you, Aurel! Great work not just with code, but also with documentation! :slight_smile:

I’m going to play with it a bit more and come back with some usage snippets, my first one would be async/await helpers for tink.core Futures.

Looking forward to this, Dan. The concept of “interacting with code inside a function” seems a little strange (.give(), .take(). .wakeup() etc). Hard for me to immediately see how this maps to common async use cases, so examples where async/await are handled are very welcome!

1 Like

really nice
there is an async example implementation for example with haxe.timer?

and sorry another question. how is complex the serialization of a coroutine? i imagine the use of a coroutine for example in stateless enviroment as php. that every time request can resume the state and position of a coroutine?

A timer example is in the manual here, if you meant “suspend this coroutine for X milliseconds”.

As for the serialisation, this is not currently possible, but implementing it will be relatively straight-forward. I opened an issue, will get back to this soon.

Hey, here’s what I have so far. The await is just a @:pecan.accept method that adds the given callback as a future handler. And async is a macro that takes a coroutine expression and wraps it into new Future. It’s currently missing some cleanups (like unregistering the await handlers when the coroutine terminates), but it seems to work.

So I don’t really use any of the coroutine API from inside the coroutine itself and I discussed with Aurel that there should be some way to actually not expose it, since it’s not needed for async/await and is can actually break the coroutine, since there’s no real scheduler/runner in this scenario.