Hacker News new | ask | show | jobs
by anonymoushn 2222 days ago
Can you expand on your notion of what is and is not a colored function?

I might use this test: When I wish to add a call to a function that yields control other than by returning to the middle of a regular function, so that the latter portion of the regular function can use the results of yielding, do I then have to change the function declaration and every single call site?

You clearly disagree. What test should we use?

1 comments

It's like calling classes colored functions because you can both call them. Classes are not functions. They are classes.

Coroutines are not functions, they are coroutines. Different primitives.

A function has a single exit entry point and a single exit point and no state. If you call a function, you run its body.

A coroutine has several entry points and exit points, and an internal state. If you call a coroutine, you make an instanciation (the body doesn't run).

You can argue than using coroutine for async handling has drawbacks, but colored functions is not the proper analogy and make people very confused about the whole thing.

The fact the syntax to define them is so similar doesn't help, and I see most people difficulties comming from their attempt to reconciliate models that have no reason to be.

Just like a hashmap and an array are 2 different things despite you can index both, functions and coroutines must be understood as separated concepts.

> A function has a single exit entry point and a single exit point and no state. If you call a function, you run its body.

> A coroutine has several entry points and exit points, and an internal state. If you call a coroutine, you make an instanciation (the body doesn't run).

Sort of, in some languages, but it doesn't have to work that way.

First, let's note that "blues" do have state, and they put it on the stack. So blues can only be entered once and store state on the stack. Reds store state in an object, and they can yield out of the middle and be reentered.

Then let's note that our top-level code has to be "red", or it would be impossible to ever run "red" code.

Now let's go on a journey.

1. Make it so calling a red from a red will start running the body right away, by default.

2. When calling a red from a red and running the body, we're already inside a coroutine instance. Instead of making a new one, keep using the same one. Grow it and store the new state at the end.

3. When calling a blue, if we're inside a coroutine instance, put the blue's stack inside it. And since our top-level code is red, the we always are inside a coroutine instance.

4. Since all our stack frames are safely stored inside coroutine instances, it's safe to call from a blue into a red! The entire stack can be saved for later, blue and red alike.

5. At this point the only difference between blue and red is that a blue function cannot yield, it can only have something deeper on the stack yield. For fun, you could make 'yield' into a runtime-provided function. Now functions written in the language are all the same. There is no difference between blue and red.

-

So there is still a distinction between "coroutines" and "functions". But in this setup, a coroutine is merely a container that you run functions inside of. There is only one kind of code you run, and it is a function.

Some language work this way. I wish javascript worked this way.

Yes but this require the entire runtime to be designed that way from the start.

It cannot be applied to legacy languages.

Just like you could not add a borrow checker to C without creating 2 worlds in the C community. But you can design Rust with this in mind.

A design always has a context.

Also, make a giant coroutine has a performance price, because any line is a potiential context switching.

You can redo a runtime while keeping the actual language the same.

The reason you wouldn't do it in C is that there are so many implementations and only a couple of them will actually update. In most languages the extra difficulty for retrofitting it would probably be less than the difficulty of designing and implementing it in the first place.

> Also, make a giant coroutine has a performance price, because any line is a potiential context switching.

A notable one? You need a stack anyway, and you don't really have to change anything to make it switchable.

You have to avoid taking a mutex and then calling into arbitrary code, but that was already a terrible idea.

Alright. One reason for my confusion is that I assumed you would post "async functions (and generators?) are not colored functions in JavaScript" at the top level if you meant that, since the article is about async functions in JavaScript. So I wondered, what's the distinction between async functions in JavaScript and in Python that makes one "colored functions" but not the other? But this is not what you intended. Thanks for the clarification.

Some programming languages have stackless async functions and generators that work the way you said, and some other languages have stackful coroutines. In the languages I use the most, functions that switch to other parts of the program are functions (according to the type system and the calling convention, but not in the sense that they have only one entry point and one exit point and no state). This is pretty great, because things are composable in precisely the way the article complains that they are not. Given that these things exist, it's fine to say "coroutines and functions are two different things" but maybe saying "a function which jumps to a different stack and which can then be jumped back into is not a function" will just make people very confused about the whole thing.

If you have this, uh, code fragment:

  function read_five(socket)
    local data, err = socket:recv(5)
    if not err then
      return data
    end
    return nil, err
  end
perhaps we shouldn't say "the value declared by the code fragment is a function or is not a function depending on whether recv nonlocally switches only to the kernel or whether it can also switch to an event loop in the same process"