Comment by haolez

5 days ago

I was pretty surprised when I learned recently that the Java alternative for green threads doesn't use colored functions. It put Java in a higher place in my perception.

It doesn't completely solve function coloring though. Causing carrier threads to get pinned is still not good, similarly as calling blocking function from async function is not good in colored systems.

  • There are not a lot of cases left that still cause pinning. FFI is the main one.

What are colored functions?

  • Any time you have a barrier between one function being able to call another. The original article on this called them red functions and green functions. A green function can call a red function but a red function can't call a green function.

    In terms of async, it's when you have to have a function with "async" attached to it and making it so that only other async functions can call async functions.

    It ends up creating a weird circumstance where you can end up with a lot of duplicated APIs, particularly in libraries, because you are providing both async and non-async versions of functions.

    • The coloring is a property of concurrency safety and whether the language enforces it.

      For instance, if you resolve a future in the wrong context you'll still have problems - the coloring is just a compile time error that you are doing things wrong, rather than a runtime deadlock.

      1 reply →

  • The term comes from an old blog post [0] about different kinds of effect systems. Every function has a color, and every colored function can only call functions that are compatible with it, usually of the same color. The net result is that you end up either duplicating a lot of your common code so you have compatible interfaces for all the different colors (let's call that "separate but equal" if we're feeling spicy), or you end up shoving round pegs into the square holes of your dominant function color.

    [0] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...

  • https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...

    The terminology is used to talk about languages that have async and sync functions where you declare (or color) the function as either async or sync.

    In these languages it's pretty common for the language to enforce a constraint that async functions can only call other async functions. Javascript / Typescript, Python are popular examples of languages with colored functions.

  • [flagged]

    • No, it refers to a function that has constraints on how it can be called and composed. A classic example is functions tagged `async` in languages like Javascript or Rust.

      (Technically, that's the symptom - the underlying cause is that it's a function that involves some effect, like asynchronicity, or, in some functional languages, IO.)

Are you perhaps confusing green threads with stackless async models, like async/await? Green threads don't imply colored functions.

  • They said "Java alternative for green threads" so they're talking about not green threads.

    • What alternative would they be referring to? Green threads were only (re-)introduced to Java in version 21 in 2023.

      I think what they're trying to say is that Java's green thread implementation has special support for async I/O. Threads that block on I/O aren't polled for completion by the runtime, instead they use OS async features under the hood.

      This allows Java's green threads to compete performance-wise with async/await solutions, but with cleaner code that doesn't need colored functions.

      In older green thread implementations in other languages, I/O can actually cause significant CPU overhead due to polling threads that are blocked by I/O requests.

No need of colored functions because that Java green thread returns a Future<Value> not Value like colored functions

"green threads" is generally how I see these systems identify as "non-colored but with async-like performance" fwiw. or "fibers". otherwise it's "async" or "coroutines".

  • There are different types of coroutines. The C++ type are sometimes called "stackless coroutines". With stackless coroutines you can't yield from a nested function call. Stackless coroutines are basically generators where you can pass arguments through resume, and async/await is effectively a form of stackless coroutines with yield/resume semantics that aren't fully generalized as coroutines, but oriented toward some bespoke notion of concurrency rather than as an abstract control flow operator.

    "Stackful coroutines" allow yielding from any arbitrary point. They're basically fibers, except with the explicit control transfer and value passing yield and resume operators; there's no hidden or implicit control transfer like with green threads. Though, some people would argue allowing any function to yield without announcing this in their type signature is tantamount to hidden control transfer. Personally, I don't see how that's different than allowing any function to call other functions, or to loop, but in any event languages are free to layer on additional typing constraints--constraints that can be tailored to the desired typing semantics, rather than dictated by implementation details.

    Stackless coroutines are typically implemented as a special kind of function whose state is allocated and instantiated by the caller. In contrast, stackful coroutines are typically implemented by reifying the stack, similar to threads. The "stack" may or not be the same as the system's ABI stack.

    In stackful coroutines, unless there are additional typing constraints imposed by the language for hygiene reasons, any function can typically be called as a coroutine or use yield and resume. There's no need to compile functions into special alternative forms as call frame management works the same whether invoked from a coroutine context or not.