Live data from Hacker News

Zig's new plan for asynchronous programs

lwn.net

241–250 of 274 posts

Re: Zig's new plan for asynchronous programs

#241
post #61
post #2

I like the look of this direction. I am not a fan of the `async` keyword that has become so popular in some languages that then pollutes the codebase.

Async always confused me as to when a function would actually create a new thread or not.

Zig doesn't make it simpler! Now in a single function, using async won't spawn threads, while using sync might.

But I'm digging this abstraction.

Re: Zig's new plan for asynchronous programs

#242

Earlier quoted context omitted.

Let's revisit the original article[1]. It was not about arguments, but about the pain of writing callbacks and even async/await compared to writing the same code in Go. It had 5 well-defined claims about languages with colored functions: 1. Every function has a color. This is true for the new zig approach: functions that deal with IO are red, functions that do not need to deal with IO are blue. 2. The way you call a…

In my opinion you must have function coloring, it's impossible to do async (in the common sense) without it. If you break it down one function has a dependency on the async execution engine, the other one doesn't, and that alone colors them. Most languages just change the way that dependency is expressed and that can have impacts on the ergonomics.

Not necessarily! If you have a language with stackful coroutines and some scheduler, you can await promises anywhere in the call stack, as long as the top level function is executed as a coroutine.

Take this hypothetical example in Lua:

  function getData()
    -- downloadFileAsync() yields back to the scheduler. When its work
    -- has finished, the calling function is resumed.
    local file = downloadFileAsync("http://foo.com/data.json"):await()
    local data = parseFile(file)
    return data
  end

  -- main function
  function main()
    -- main is suspended until getData() returns
    local data = getData()
    -- do something with it
  end
    
  -- run takes a function and runs it as a coroutine
  run(main)
Note how none of the functions are colored in any way!

For whatever reason, most modern languages decided to do async/await with stackless coroutines. I totally understand the reasoning for "system languages" like C++ (stackless coroutines are more efficient and can be optimized by the compiler), but why C#, Python and JS?

Re: Zig's new plan for asynchronous programs

#243
post #231
post #109

Earlier quoted context omitted.

> channels aren't futures and futures aren't channels. In my mind a queue.getOne ~= a I really do appreciate you being strict about the semantics. Tbh the biggest thing I feel fuzzy on in all this is how go/zig actually go about finding the first completed future in a select, but other than that am I missing something? https://ziglang.org/documentation/master/std/#std.Io.Queue.g...

"but other than that am I missing something?" I think the big one is that a futures based system no matter how you swing it lacks the characteristic that on an unbuffered Go channel (which is the common case), successfully sending is also a guarantee that someone else has picked it up, and as such a send or receive event is also a guaranteed sync point. This requires some work in the compiler and runtime to guarantee…

Thanks for taking the time to respond. I will now think of Channels as queue + [mutex/communication guarantee] and not just queue. So in Go's unbuffered case (only?) a Channel is more than a 1-item queue. Also, in Go's select, I now get that channels themselves are hooked up to notify the select when they are ready?

Re: Zig's new plan for asynchronous programs

#244
post #225

Earlier quoted context omitted.

It's not a monad because it doesn't return a description of how to carry out I/O that is performed by a separate system; it does the I/O inside the function before returning. That's a regular old interface, not a monad.

> 1. a description of how to carry out I/O that is performed by a separate system > 2. does the I/O inside the function before returning How do you distinguish those two things? To put my cards on the table, I believe Haskell does 2, and I think my Haskell effect system Bluefin makes this abundantly clear. (Zig's `Io` seems to correspond to Bluefin's `IOE`.) There is a persistent myth in the Haskell world (and beyond…

I'm also pretty sure that its immaterial if Haskell does 1 or not. This is an implementation detail and not at all important to something being a Monad or not.

My understanding is requiring 1 essentially forces you to think of every Monad as being free.

Re: Zig's new plan for asynchronous programs

#245

Overall this article is accurate and well-researched. Thanks to Daroc Alden for due diligence. Here are a couple of minor corrections: > When using an Io.Threaded instance, the async() function doesn't actually do anything asynchronously — it just runs the provided function right away. While this is a legal implementation strategy, this is not what std.Io.Threaded does. By default, it will use a configurably sized th…

> > When using an Io.Threaded instance, the async() function doesn't actually do anything asynchronously — it just runs the provided function right away.

> [...]

Well, yeah, but even if you spin up a thread to run "the provided function right away" it still will only be for some value of "right away" that is not instantaneous. Creating a thread and getting it up and running is often an asynchronous operation -- it doesn't have to be, in that the OS can always simply transfer the caller's time quantum, on-CPU state, and priority to the new thread, taking the caller off the CPU if need be. APIs like POSIX just do not make that part of their semantics. Even if they did then the caller would be waiting to get back on CPU, so thread creation is fundamentally an async operation.

Re: Zig's new plan for asynchronous programs

#246

Earlier quoted context omitted.

And I bet those green threads still need an IO type of some sort to encode anything non-pure, plus usually do-syntax. Comparing merely concurrent computations to I/O-async is just weird. In fact, I suspect that even those green threads already have a "colourful" type, although I can't check right now.

> And I bet those green threads still need an IO type of some sort to encode anything non-pure, plus usually do-syntax. There's no need for the do-syntax at all. The (IO a) is no different to other generic types that can be fmap-ed, pointfree-composed with other expressions, and joined/folded when required. The only difference is the fact that they represent actions that affect the real world, so that ordering of thi…

Right, and there's also no need for await syntax at all, they can be then-ed, ContinueWith-ed or whatever the language calls them, but people keep bringing syntax into a semantics battle, so I had to mention it.

Re: Zig's new plan for asynchronous programs

#247
post #228

Earlier quoted context omitted.

Couldn't have said it better myself. But IIUC Andrew stated that its not a monad because it does not build up a computation and then run. Rather, its as if every function runs a `runIO#` or `runReader` every time the io parameter is used.

Is it necessary that a monad "builds up a computation and then runs"? In fact it's very hard for a monad to do that because the type of bind is (>>=) :: m a -> (a -> m b) -> m b so you can really only make progress if you first build a bit (`m a`), then run it (to get `a`) then build the next bit (applying `a` to `a -> m b`), then run that. So "building" and "running" must necessarily be interleaved. It's an odd myth…

Are you saying "monad" is a synonym of "interface"?

Re: Zig's new plan for asynchronous programs

#248
post #206

Earlier quoted context omitted.

The problem with function coloring is that it makes libraries difficult to implement in a way that's compatible with both sync and async code. In Python, I needed to write both sync and async API clients for some HTTP thing where the logical operations were composed of several sequential HTTP requests, and doing so meant that I needed to implement the core business logic as a Generator that yields requests and accept…

My opinion is that if your library or function is doing IO, it should be async - there is no reason to support "sync I/O". Also, this "sans IO" trend is interesting, but the code boils down to a less ergonomic, more verbose, and less efficient version of async (in Rust). It's async/await with more steps, and I would argue those steps are not great.

> there is no reason to support "sync I/O"

I disagree strongly.

From a performance perspective, asynchronous IO makes a lot of sense when you're dealing concurrently with a large number of tasks which each spend most of their time waiting for IO operations to complete. In this case, running those tasks in a single-threaded event loop is far more efficient than launching off thousands of individual threads.

However, if your application falls into literally any other category, then suddenly you are actually paying a performance penalty, since you need the overhead of running an event loop any time you just want to perform some IO.

Also, from a correctness perspective, non-concurrent code is simply a lot less complex and a lot harder to get wrong than concurrent code. So applications which don't need async also end up paying a maintainability, and in some cases memory safety / thread safety, penalty as well.

Re: Zig's new plan for asynchronous programs

#249
post #228

Earlier quoted context omitted.

Is it necessary that a monad "builds up a computation and then runs"? In fact it's very hard for a monad to do that because the type of bind is (>>=) :: m a -> (a -> m b) -> m b so you can really only make progress if you first build a bit (`m a`), then run it (to get `a`) then build the next bit (applying `a` to `a -> m b`), then run that. So "building" and "running" must necessarily be interleaved. It's an odd myth…

Are you saying "monad" is a synonym of "interface"?

Not a synonym, but `Monad` is one of the commonly used interfaces in Haskell (not the only one).

Re: Zig's new plan for asynchronous programs

#250
post #95

Earlier quoted context omitted.

Pure actions can be run in parallel with https://hackage-content.haskell.org/package/parallel/docs/Co... Impure actions use the IO monad like always in Haskell: https://hackage.haskell.org/package/base-4.21.0.0/docs/Contr... (or the higher-level async library) I suppose an extreme version of the function coloring argument could be that all types are colors.

In a sense, kinda? The function colour problem is that you can't call an async API from a non-async caller without modifying the entire call chain (or blocking the full thread). In async/await languages the conflict comes from changing the return types; the syntax is orthogonal. Maybe in practice some properties of code are better off being whole-program than represented as types, even if they're less modular or hard…

Right. In Haskell we want to maintain the pure/impure distinction so… it’s not a problem I guess?
Post reply on HN