Live data from Hacker News

Zig's new plan for asynchronous programs

lwn.net

221–230 of 274 posts

Re: Zig's new plan for asynchronous programs

#221
post #4

I’m excited to see how this turns out. I work with Go every day and I think Io corrects a lot of its mistakes. One thing I am curious about is whether there is any plan for channels in Zig. In Go I often wish IO had been implemented via channels. It’s weird that there’s a select keyword in the language, but you can’t use it on sockets.

Have you tried Odin? Its a great language thats also a “better C” but takes more Go inspiration than Zig.

Completely replaced Go for me after using Go since inception.

Wonderful language!

Re: Zig's new plan for asynchronous programs

#222
post #27

Earlier quoted context omitted.

Haskell has green threads. Plus nowadays Java also has virtual threads.

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 things suddenly becomes important.

Re: Zig's new plan for asynchronous programs

#223
If I understand this correctly, in this example

    const std = @import("std");
    const Io = std.Io;

    fn saveFile(io: Io, data: []const u8, name: []const u8) !void {
        const file = try Io.Dir.cwd().createFile(io, name, .{});
        defer file.close(io);
        try file.writeAll(io, data);
    }
the phrase “Either way, the operation is guaranteed to be complete by the time writeAll() returns” is too weak. Given that the function can, over time, be called with different implementations of IO and users can implement IO themselves, I think the only way this can work is that the operation is guaranteed to be complete when the defer starts (if not, what part of the code makes sure the createFile must have completed when writeAll starts? (The IO instance could know, but it would either have to allow for only one ‘in flight’ call or have to keep track of in-progress calls and know of dependency between creating a file and writing to it)

But then, how is this really different from a blocking call?

Also, if that’s the case, why is that interface called IO? It looks more like a “do this in a different context” thing than specific to I/O to me (https://ziglang.org/documentation/master/std/#std.Io seems to confirm that. It doesn’t mention I/O at all)

Re: Zig's new plan for asynchronous programs

#224
Isn't this (their async version of Io) essentially the same thing that Go is doing?

I seem to recall reading about some downsides to that approach, e.g. that calling C libraries is relatively expensive (because a real stack has to be allocated) and that circumventing libc to do direct syscalls is fragile and unsupported on some platforms.

Does the Zig implementation improve on Go's approach? Is it just that it makes it configurable, so that different tradeoffs can be made without changing the code?

Re: Zig's new plan for asynchronous programs

#225

Earlier quoted context omitted.

Agreed. the Haskeller in me screams "You've just implemented the IO monad without language support".

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) that Haskell does 1. In fact I think it's hard to make it a true meaningful statement, but I can probably just about concede it is with a lot of leeway on what it means for I/O to be "performed by a separate system", and even then only in a way that it's also true and meaningful for every other language with a run time system (which is basically all of them).

The need to believe that Haskell does 1 comes from the insistence that Haskell be considered a "pure" language, and the inference that means it doesn't do I/O, and therefore the need that "something else" must do I/O. I just prefer not to call Haskell a "pure" language. Instead I call it "referentially transparent", and the problem vanishes. In Haskell program like

    main :: IO ()
    main = do
       foo
       foo

    foo :: IO ()
    foo = putStrLn "Hello"
I would say that "I/O is done inside `foo` before returning". Simple. No mysteries or contradiction.

https://hackage-content.haskell.org/package/bluefin/docs/Blu...

Re: Zig's new plan for asynchronous programs

#226
post #198

Earlier quoted context omitted.

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.

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.

Re: Zig's new plan for asynchronous programs

#227
post #198

Earlier quoted context omitted.

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.

And for comparison, here's Haskell's (or rather Bluefin's) equivalent of Zig's `Io` parameter:

https://hackage-content.haskell.org/package/bluefin/docs/Blu...

Re: Zig's new plan for asynchronous programs

#228
post #198

Earlier quoted context omitted.

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.

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 that "Haskell's IO purely builds an impure computation to run".

Re: Zig's new plan for asynchronous programs

#229
post #92

Earlier quoted context omitted.

The function coloring problem actually comes up when you implement the async part using stackless coroutines (e.g. in Rust) or callbacks (e.g. in Javascript). Zig's new I/O does neither of those for now, so hence why it doesn't suffer from it, but at the same time it didn't "solve" the problem, it just sidestepped it by providing an implementation that has similar features but not exactly the same tradeoffs.

How are the tradeoffs meaningfully different? Imagine that, instead of passing an `Io` object around, you just had to add an `async` keyword to the function, and that was simply syntactic sugar for an implied `Io` argument, and you could use an `await` keyword as syntactic sugar to pass whatever `Io` object the caller has to the callee. I don't see how that's not the exact same situation.

[deleted]

Re: Zig's new plan for asynchronous programs

#230
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…

> suspend/resume

special @asyncSuspend and @asyncResume builtins, they will be the low level detail you can build an evented io with.

new Io is an abstraction over the higher level details that are common between sync, threaded, and evented, so you shouldn't expect the suspension mechanism to be in it.

Post reply on HN