14 comments

[ 4.7 ms ] story [ 45.1 ms ] thread
I really don’t agree with the idea that this is functional colouring. Then we have to start talking about function colouring in a whole bunch of new contexts like with Zigs explicit passing of allocator. Or any other parameter that needs to be explicitly passed to use some kind of interface.

I think we should stick to talking about colouring when there is special calling conventions or syntax, which has the consequence of having to write separate libraries/modules for async code and non-async code.

That is the significant problem we have been seeing with many async implementation, and the one which Zig apparently fully solves.

(comment deleted)
Look, either you move the program counter to a different place in memory (function call) or you push a task into an event loop. Even if you somehow elide all these differences, they're so different under the hood you'll always have to know in some circumstances. It's honestly wild we conflate them at all.
If you want to go down that route, any function that has, or doesn't have, any given resource is colored then.

   fn foo(db: *Db) !void { ... }
   fn bar() !void { ... }
Would you consider `foo` a blue function and `bar` a red function? That doesn't seem particularly helpful to me.

The virality of async await is that once you mark a function async, then you can only call it from another async function, which forces you to mark more functions async, which in turn means that if you want to use blocking I/O APIs then you just can't because it's incompatible with your execution model because by daring to express asynchronicity of operations, you were forcefully opted into stackless coroutines.

That's what Zig solves, and that's what is real function coloring. People have written reimplementations of the same libraries multiple times because of it.

https://github.com/redis/redis-py https://github.com/jonathanslenders/asyncio-redis

Just as an example. Note also how, coincidentally, this duplication of effort resulted in asyncio-redis being semi-abandoned and looking for maintainers. And you have to have both libraries because the asyncio one can't do blocking, and vice versa the other one can't do async.

Would you write two instances of essentially the same library just because one is missing an argument that gives it access to an `Io` interface? No, because you would just pass that extra argument around and nothing else would have to change.

One way or another, I like how this is implemented. Explicitely passing dependencies like this, with a tight syntax, makes things easy to understand and write.

I haven't written any Zig but these demonstrations give me strong vibes of how I felt when I picked up Go more than 10y ago.

Function colouring created a lot of angst when it first came about, particularly because of the difficulties of calling a function of one colour from another. Whether that was possible, what it actually meant, wasn't really well defined.

As other comments have said, there's nothing special about "colouring"; sync/async functions are a case where those above problems are tough, but simpler versions of the problem are everywhere and we don't freak out about them e.g. call a fallible function from an infallible function.

It really all turns on how easy it is to ultimately make the call to the other "function" colour. In Zig's case, if its easy to get an Io in a function that didn't take an Io, it's a non-issue. Likewise for the "fallible function call from infallible function": if it fails, do something that doesn't result in the infallible function failing (do something else? Terminate? Anything will do).

I am actually really excited about this.

The issues I've had with function colouring had to do with trying to compose code using (or expecting) blocking effects with those using async effects in NodeJS - if one library has a higher-order function that expects a non-async function and you have functionality which is provided to you as async, it can be very difficult to plumb them together! And if it's the other way around, it can be quite the performance killer (think how much faster better-sqlite3 is than alternatives). Zig's approach eliminates this problem, AFAICT.

If I had to choose between having to pass through an effect handler like `io` or write `async` everywere, the former seems like a better use of my time. It's explicit, but that can be good.

It also fits Zig well with the allocator. Code can expect an allocator or perhaps an allocator and `io`, or perhaps neither. It's analogous to Rust code that is core vs alloc/nostd vs std.

I am slightly amused that a "C-but-better" language is going to have an `io` passed through non-pure functions much like Haskell. It's that idea combined with Rust's pluggable async runtimes (and stackless concurrency) combined with Roc's "platforms" - but for systems programmers. Quite amazing.

I think this is a good kind of function coloring. It would avoid some scars I have from:

- seemingly harmless functions that unexpectedly end up writing four different files to disk.

- Packages that do I/O or start threads when you simply import them.

I don't care about the color of the function. What matters is if you'll have to write two versions of the function, and this seems to solve that.
If we apply your logic, then every mainstream language has the coloring problem with not just two, but a multitude of colors.

  // OMG we can't call this without passing the service
  // This function is people-colored
  public Person findPersonByName(PeopleService service, String name) {
    // OMG we can't find without the service
    service.find(name)
  }


EDIT: formatting
With Zig I/O you can call i/o functions from non-i/o functions and non-i/o functions from i/o functions.

In the analogy of “What color is your function”, you can call blue functions from red functions and red functions from blue functions.

The pernicious viral nature of function coloring doesn’t apply.

i never got warm with this async/await style of calling things. i much more prefer using threads/fibers/protothreads instead. Sure you have to be more expicit on creating and controlling these and also passing around data may get more complex, but at least they do not gloss over the underlying problems async and await tries to cover up
I believe Zig's new I/O is neither colored nor not colored, instead the design is orthogonal to that. You won't be able to make code magically async by just swapping the implementation of Io, instead you'll need some kind of runtime that allows functions to wait without blocking OS threads. Depending on how this is implemented (if it's possible at all) you'll have colored functions or not.