Live data from Hacker News

Zig's new plan for asynchronous programs

lwn.net

191–200 of 274 posts

Re: Zig's new plan for asynchronous programs

#191
post #26

I find this example quite interesting: var a_future = io.async(saveFile, .{io, data, "saveA.txt"}); var b_future = io.async(saveFile, .{io, data, "saveB.txt"}); const a_result = a_future.await(io); const b_result = b_future.await(io); In Rust or Python, if you make a coroutine (by calling an async function, for example), then that coroutine will not generally be guaranteed to make progress unless someone is waiting f…

is it not the case that in zig, the execution happens in a_future.await? I presume that: io.async 1 stores in io "hey please work on this" io.async 2 stores in io "hey also please work on this" in the case where io is evented with some "provided event loop": await #1 runs through both 1 and 2 interleavedly, and if 2 finishes before 1, it puts a pin on it, and then returns a_result when 1 is completed. await #2 "no-ex…

There’s a material distinction. In Zig (by my reading of the article — I haven’t tried it), as you say:

> await #1 runs through both 1 and 2 interleavedly, and if 2 finishes before 1, it puts a pin on it, and then returns a_result when 1 is completed.

In Rust or Python, awaiting a future runs that future and possibly other tasks, but it does not run other non-task futures. The second async operation would be a non-task future and would not make progress as a result of awaiting the first future.

It looks like Zig’s io.async sometimes creates what those other languages call a task.

Re: Zig's new plan for asynchronous programs

#192

Earlier quoted context omitted.

Yes.

Can you explain for those of us less familiar with Haskell (and monads in general)?

A Monad is a _super_ generic interface that can be implemented for a whole bunch of structures/types. When people talk about "monads", they are usually referring to a specific instance. In this case, the Reader monad is a specific instance that is roughly equivalent to functions that take an argument of a particular type and return a result of any type. That is, any function that looks like this (r -> a) where `r` is fixed to some type, and `a` can be anything.

Functions of that form can actually implement the Monad interface, and can make use of Haskells syntax support for them.

One common use-case for the reader monad pattern is to ship around an interface type (say, a struct with a bunch of functions or other data in it). So, what people are saying here is that passing around a the `Io` type as a function argument is just the "reader monad" pattern in Haskell.

And, if you hand-wave a bit, this is actually how Haskell's IO is implemented. There is a RealWorld type, which with a bit of hand waving, seems to pretty much be your `Io` type.

Now, the details of passing around that RealWorld type is hidden in Haskell behind the IO type, So, you don't see the `RealWorld` argument passed into the `putStrLn` function. Instead, the `putStrLn` function is of type `String -> IO ()`. But you can, think of `IO ()` as being equivalent to `RealWorld -> ()`, and if you substitute that in you see the `String -> RealWorld -> ()` type that is similar to how it appears you are doing it in Zig.

So, you can see that Zig's Io type is not the reader monad, but the pattern of having functions take it as an argument is.

Hopefully that helps.

---

Due to Haskell's laziness, IO isn't actually the reader monad, but actually more closely related to the state monad, but in a strict language that wouldn't be required.

Re: Zig's new plan for asynchronous programs

#193
post #191

Earlier quoted context omitted.

is it not the case that in zig, the execution happens in a_future.await? I presume that: io.async 1 stores in io "hey please work on this" io.async 2 stores in io "hey also please work on this" in the case where io is evented with some "provided event loop": await #1 runs through both 1 and 2 interleavedly, and if 2 finishes before 1, it puts a pin on it, and then returns a_result when 1 is completed. await #2 "no-ex…

There’s a material distinction. In Zig (by my reading of the article — I haven’t tried it), as you say: > await #1 runs through both 1 and 2 interleavedly, and if 2 finishes before 1, it puts a pin on it, and then returns a_result when 1 is completed. In Rust or Python, awaiting a future runs that future and possibly other tasks , but it does not run other non-task futures. The second async operation would be a non-t…

i am not familiar with rust and i gave up on python async years ago so i have no frame of reference here. but im really not sure why theres a need to distinguish between tasks and non tasks?

importantly in zig the execution isnt just limited to #1 and #2. if the caller of this function initiated a #3 before all of this it could also get run stuffed in that .await, for example.

Re: Zig's new plan for asynchronous programs

#194
post #62

One thing the old Zig async/await system theoretically allowed me to do, which I'm not certain how to accomplish with this new io system without manually implementing it myself, is suspend/resume. Where you could suspend the frame of a function and resume it later. I've held off on taking a stab at OS dev in Zig because I was really, really hoping I could take advantage of that neat feature: configure a device or sub…

I suspect the end goal is to have suspend/resume (or some analogue) be userspace standard library functions used to implement Io.Evented. It'll end up being a userspace implementation like you see with many of the C coroutine libraries floating around on GitHub (minicoro, llco, etc).

Edit: Looking at the working prototype of Io.Evented, this may not be true actually. Perhaps this is the domain of a 3rd-party library except with stackless coroutines?

Another thing you may want to pay attention to, however, are the current proposals for getting evented IO working on WASM -- namely, stackless coroutines as a language feature: https://github.com/ziglang/zig/issues/23446

Re: Zig's new plan for asynchronous programs

#195
post #135

Earlier quoted context omitted.

This creates the drill-down issue we see with React props where we have to pass objects around in the call chain just so that somewhere down the line we can use it. React gets around this with the context hook and which you can access implicitly if it has been injected at a higher level. Do you know if Zig supports something of the sort?

It doesn't and likely never will. This has been a non-issue for years with Allocator. I fail to see why it will be a problem with IO.

Well it's not a "problem" in the sense that it's a blocker. But it's also not an improvement over standard async await in other languages. Which is not bad, don't get me wrong.

Re: Zig's new plan for asynchronous programs

#196
post #186

Earlier quoted context omitted.

Having used zig a bit as a hobby. Why is it more ergonomic? Using await vs passing a token have similar ergonomics to me. The one thing you could say is that using some kind of token makes it dead simple to have different tokens. But that's really not something I run into often at all when using async.

Making it dead simple to have different tokens is exactly the goal. A smattering of examples recently on my mind: As a background, you might ask why you need different runtimes ever. Why not just make everything async and be done with it, especially if the language is able to hide that complexity? 1. In the context of a systems language that's not an option. You might be writing an OS, embedded code, a game with atyp…

The case I'm making is not that different Io context are good. The point I'm making is that mixing them is almost never what is needed. I have seen valid cases that do it, but it's not in the "used all the time" path. So I'm more then happy with the better ergonomics of traditional async await in the style of Rust , that sacrifices super easy runtime switching. Because the former is used thousands of times more.

Re: Zig's new plan for asynchronous programs

#197

Earlier quoted context omitted.

If calling the same function with a different argument would be considered 'function coloring', every function in a program is 'colored' and the word loses its meaning ;) Zig actually also had solved the coloring problem in the old and abandondend async-await solution because the compiler simply stamped out a sync- or async-version of the same function based on the calling context (this works because everything is a…

> If calling the same function with a different argument would be considered 'function coloring', than every function in a program is 'colored' and the word loses its meaning ;) I mean, the concept of "function coloring" in the first place is itself an artificial distinction invented to complain about the incongruent methods of dealing with "do I/O immediately" versus "tell me when the I/O is done"--two methods of I/…

My understanding of this design is that you can write the logic separately from the decision to "do I/O immediately" versus "tell me when the I/O is done"

You can write a parser thats outputs a DOM and run it on a stream, or write a parser with a streaming API and run it synchronously on a buffer. You should pick the optimal tool for the situation, but there is no path dependence anymore.

Re: Zig's new plan for asynchronous programs

#198

Earlier quoted context omitted.

Reader monad is a fancy way of saying ‘have the ability to read some constant value throughout the computation’. So here they mean the io value that is passed between functions.

Well I don't think that fits at all. In Zig, an Io instance is an interface, passed as a parameter. You can draw some connections between what Zig is doing and what Haskell is doing but it's not a monad. It's plain old interfaces and parameters, just like Allocator.

Passing an interface as a parameter is a monad. (Io -> _) is an instance of Monad in Haskell.

Haskell just has syntax to make using (any) monad much nicer. In this case, it let's you elide the `Io` parameter in the syntax if you are just going to be passing the same Io to a bunch of other functions. But it still is there.

Re: Zig's new plan for asynchronous programs

#199

Earlier quoted context omitted.

Let's see if I can do it without going too far off the deep end. I think your description of the _IO type_ as "a description of how to carry out I/O that is performed by a separate system" is quite fair. But that is a property of the IO type, not of monads. A monad in programming is often thought of as a type constructor M (that takes and returns a type), along with some functions that satisfy certain conditions (cal…

This is pretty concise, but is still really technical. That aside, I think the actual bone of contention is that Zig’s IO is not a Reader-esque structure. The talks and articles I’ve read indicate that function needing the IO ‘context’ must be passed said context as an argument. Excepting using a global variable to make it available everywhere, but as I said in a sibling comment, that’s just global state not a monad.…

A series of functions all passing the same `io: IO` value around exhibit exactly the behavior of the reader monad.

Re: Zig's new plan for asynchronous programs

#200

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…

What a really like about concurrent(), is that it improves readability and expressiveness, making it clear when writing and reading that "this code MUST run in parallel".
Post reply on HN