Live data from Hacker News

Zig's new I/O: function coloring is inevitable?

blog.ivnj.org

51–60 of 83 posts

Re: Zig's new I/O: function coloring is inevitable?

#51
post #36

Earlier quoted context omitted.

const Greeter = struct { io: std.Io, pub fn greet(self: Greeter, msg: []const u8) !void { var stdout_writer = self.io.stdout.writer(); try stdout_writer.writeAll(msg); } } Greeter.greet("Look ma, no coloring")

`greet` has Io within its closure, so I’m pretty sure this is consistent with the GP’s point.

If you want to print a string from your function, you must supply it to the function. The fact that your function now receives `str: []u8` doesn't make it str-colored.

On a side note: the entire "coloring" metaphor is extremely unfortunate and confusing IMO. It's somewhere at the top of my list (right beside the shroedinger's cat) of things which could've been explained better without dumbing them down.

Re: Zig's new I/O: function coloring is inevitable?

#52
post #24

Earlier quoted context omitted.

Well said. Honestly I don't understand this "function coloring argument" at all in zig's context. Function is the smallest unit of logic. And function parameter is the fundamental way to control it.so, in theory, __A function parameter is the smallest possible design choice you have to control async vs sync__ There is no way to reduce it further. As you need to encode this somehow, otherwise it will be implicit and v…

> in theory, __A function parameter is the smallest possible design choice you have to control async vs sync__ So we are just going to forget Go exists?

Disclaimer: I have not used golang, so i might be wrong.

as per my understanding, golang has it implicit. Like there is no way for a developer to tell if code will run under async context or not.

Re: Zig's new I/O: function coloring is inevitable?

#53

Earlier quoted context omitted.

> Zig expects you to pass It does no such thing. you could pass a function a vtable and the vtable could have one implementation that calls an io stashed in the parent of the vtable, and a different vtable that doesnt and the function calling the vtable would be none the wiser. what is the color of the function that took the vtable? this is not just academic; it would be for example the basis for mocked integration t…

That's a calling convention, with indirection. You need some kind of capability token for this kind of asynchronicity scheme; it doesn't matter how you get it, but it needs to be there. To be clear, there's nothing wrong with this; it's just another way to encode capabilities/effects. > what is the color of the function that took the vtable? It has the I/O effect.

> It has the I/O effect

it does not, because it can take a vtable that does not call i/o

concretely:

    const VTable = struct {
      f: &fn (*VTable) void,
    };

    const A = struct {
      io: IO,
      v: VTable = .{ .f = &A.uses_io },
      fn uses_io(this: *VTable) void {
        const self: *A = @fieldParentPtr(.v, this);
        self.io.some_io_fn(...);
      }
    };

    const B = struct{v: VTable = .{.f = &void_fn}};
    fn void_fn(_: *VTable) void {}

    \\ WHAT IS THE COLOR OF THIS FUNCTION?
    pub fn calls_vtable(v: VTable) {
      v.f()
    }

Re: Zig's new I/O: function coloring is inevitable?

#54

Earlier quoted context omitted.

Another poster up thread identified the exact problem: async/await contexts are not first-class values, they are second class citizens. If they were values then you could just stick the context in a struct/class and pass that around instead, and avoid having to refactor call chains every time something changes. It's their second class status that forces the "colouring" into the function signature itself at each point…

> async/await contexts are not first-class values Because async/await are not values, they are ways to run/structure your code. That's why they are so infectious. If you don't want the division the only solution is to make everything of one kind. Languages like C make everything sync/blocking, while languages like Go make everything async.

> the only solution is to make everything of one kind

That's not really the case. All you really need is a way to run async code from a sync function; "keep doing this async thing until it's done" is the primitive you need, and some languages/runtimes offer this.

Re: Zig's new I/O: function coloring is inevitable?

#55
post #51

Earlier quoted context omitted.

`greet` has Io within its closure, so I’m pretty sure this is consistent with the GP’s point.

If you want to print a string from your function, you must supply it to the function. The fact that your function now receives `str: []u8` doesn't make it str-colored. On a side note: the entire "coloring" metaphor is extremely unfortunate and confusing IMO. It's somewhere at the top of my list (right beside the shroedinger's cat) of things which could've been explained better without dumbing them down.

That’s because str is not a meaningful effect, so we wouldn’t say that a function is “str-colored.”

Function coloring is indeed confusing, which is why I’ve been trying to ground this on effects instead. Effects are IMO easier to understand, and this is a straightforward example of effect typing.

Re: Zig's new I/O: function coloring is inevitable?

#56

Earlier quoted context omitted.

That's a calling convention, with indirection. You need some kind of capability token for this kind of asynchronicity scheme; it doesn't matter how you get it, but it needs to be there. To be clear, there's nothing wrong with this; it's just another way to encode capabilities/effects. > what is the color of the function that took the vtable? It has the I/O effect.

> It has the I/O effect it does not, because it can take a vtable that does not call i/o concretely: const VTable = struct { f: &fn (*VTable) void, }; const A = struct { io: IO, v: VTable = .{ .f = &A.uses_io }, fn uses_io(this: *VTable) void { const self: *A = @fieldParentPtr(.v, this); self.io.some_io_fn(...); } }; const B = struct{v: VTable = .{.f = &void_fn}}; fn void_fn(_: *VTable) void {} \\ WHAT IS THE COLOR O…

It also has the I/O effect. You don’t need to call or make use of an effect to be “tainted” by it; it just needs to be in the closure (or whatever scope is relevant in the language).

Intuitively: if you mark a function as async, it doesn’t stop being async “colored” just because you don’t actually perform any async operations in it. This is the same thing.

Re: Zig's new I/O: function coloring is inevitable?

#57
post #32

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.

> With Zig I/O you can call i/o functions from non-i/o functions and non-i/o functions from i/o functions Yes, but you would either create a new std.Io just for that, or use a global/semi-global std.Io from somwhere else

Yes… like every other argument one function provides to another that it doesn’t pass directly through from one of its own arguments.

The function color analogy captures a pernicious viral dynamic with async where it allows only one-way composition. That just doesn’t exist with zig i/o.

Re: Zig's new I/O: function coloring is inevitable?

#58

Earlier quoted context omitted.

> async/await contexts are not first-class values Because async/await are not values, they are ways to run/structure your code. That's why they are so infectious. If you don't want the division the only solution is to make everything of one kind. Languages like C make everything sync/blocking, while languages like Go make everything async.

> the only solution is to make everything of one kind That's not really the case. All you really need is a way to run async code from a sync function; "keep doing this async thing until it's done" is the primitive you need, and some languages/runtimes offer this.

Going by that reasoning then Rust solved the colored function problem too: when calling an `async` function from a sync one use `block_on`, while when calling a sync blocking function from an `async` one use `spawn_blocking`, but somehow people are not happy with this.

Re: Zig's new I/O: function coloring is inevitable?

#59

Earlier quoted context omitted.

> It has the I/O effect it does not, because it can take a vtable that does not call i/o concretely: const VTable = struct { f: &fn (*VTable) void, }; const A = struct { io: IO, v: VTable = .{ .f = &A.uses_io }, fn uses_io(this: *VTable) void { const self: *A = @fieldParentPtr(.v, this); self.io.some_io_fn(...); } }; const B = struct{v: VTable = .{.f = &void_fn}}; fn void_fn(_: *VTable) void {} \\ WHAT IS THE COLOR O…

It also has the I/O effect. You don’t need to call or make use of an effect to be “tainted” by it; it just needs to be in the closure (or whatever scope is relevant in the language). Intuitively: if you mark a function as async, it doesn’t stop being async “colored” just because you don’t actually perform any async operations in it. This is the same thing.

[deleted]

Re: Zig's new I/O: function coloring is inevitable?

#60

Earlier quoted context omitted.

> It has the I/O effect it does not, because it can take a vtable that does not call i/o concretely: const VTable = struct { f: &fn (*VTable) void, }; const A = struct { io: IO, v: VTable = .{ .f = &A.uses_io }, fn uses_io(this: *VTable) void { const self: *A = @fieldParentPtr(.v, this); self.io.some_io_fn(...); } }; const B = struct{v: VTable = .{.f = &void_fn}}; fn void_fn(_: *VTable) void {} \\ WHAT IS THE COLOR O…

It also has the I/O effect. You don’t need to call or make use of an effect to be “tainted” by it; it just needs to be in the closure (or whatever scope is relevant in the language). Intuitively: if you mark a function as async, it doesn’t stop being async “colored” just because you don’t actually perform any async operations in it. This is the same thing.

you might be talking about stackless coroutines, which are currently not part of zig, in which case, yes, the compiler might [0] have to instantiate different functions under the hood for the stackless-coro and the non-stackless-coro cases, with some mechanism to drop in an executor at the boundaries. until then its really hard to claim that the functions are colored.

[0] But even in that case there's not really coloring because if you provide a single io implementation there won't be different functions, even at the compiled level.

Post reply on HN