Hacker News new | ask | show | jobs
by chrsig 1066 days ago
Coroutines are one thing that i'd probably prefer language support for rather than a library.

    x := co func(){
        var z int
        for {
            z++
            yield z
        }
    }

    y := x()

    for y := range x {
        ...
    }

or something to that effect. It's cool that it can be done at all in pure go, and I can see the appeal of having a standard library package for it with an optimized runtime instead of complecting the language specification. After all, if it's possible to do in pure go, then other implementations can be quickly bootstrapped.

My $0.02, as someone that uses go at $work daily: I'd be happy to have either, but I'd prefer it baked into the language. Go's concurrency primitives have always been a strength, just lean into it.

3 comments

you need language support for coroutines - what happens when the stack runs out in one of the coroutine? With a library solution you would have to kill the co-routine, or kill the program. If you want the stack to grow transparently, then that can only be done by the generated code, it has to watch stack usage and then grow the stack on demand (i think you have something like that with goroutines)

Well, maybe a library solution could possibly have a guard page at the end of the stack. When that one is reached, then you could try to grow the stack in an error handler. (but that would probably not work, if you have taken a pointer to some stack variable)

You don't need language support for coroutines if you have language support for relocatable/chunkable stack, which Go has.
I would prefer to add a `yield` param to functions so it can better interact with the current type system.

This means that for a function to yield, it needs to have the so called `yield` param and you can only yield inside a yield function functions with the same yield signature or does not contain a signature at all.

So the example would be rewritten into something like:

  x := func(:z int) {
    for {
      z++
      :- z
    }
  }

  for y := range x() {
    ...
  }
The : adds the yield signature and :< yields the value.

Functions that only yield a value `X` would just have the `: X` or `: name X`. To accept a value of type `Y` in the resume the signature would change to `:[Y] X` or `:[Y] name X`.

Accepting is weak, so if something expects a yielding function that resumes with Y and yield X, yielding functions that only yield X without a resume should also be accepted.

Consider the following code example:

  func filter[T comparable](it func(:T), f func(T) bool, :T) {
    for v := range it {
      if f(v) { :- v }
    }
  }

  func map[T](arr []T, :T) {
    for _, v := range arr {
      :- v
    }
  }

  for v := range filter(map({1, 2, 3}), func(x) { return x < 3 }) {
    print(v)
  }
Special functions in a co package could give resume and New functionality to keep the go style.

A basic counter could be written like this:

  func counter(:[bool]c int) {
    for {
      if ! :- c { break }
      c += 1
    }
  }
A func that counts twice could be as simple as:

  func countTwice(:[bool]c int) {
    count()
    count()
  }
To interact with the resume value the range syntax could be expanded to something like:

  for c := range count() {
    if c > 10 { -: false }
    -: true
    print(c)
  }
Range would resume with a default zero value if the range does not pass a value to -:.
Sure, as long as I can close over a yield object. There is no reason for filter and map to know that the last function parameter takes a continuation.
But then, how could you guarantee type safety?

This yield is not an object, it is handling the execution context to another stack.

It is a delimited one-shot continuation. Delimited continuations are first class objects like any other and it should be possible to close over them.
wow that is horrible. none of that is intuitive, which is one of Go strength. you would need to specific learn the Go semantic of coroutine to have any chance of writing or even reading code like this.
Goroutines and channels aren't intuitive either. You learn them and become familiar with them over time, at which point they become intuitive __for you__.
actually I get it now. the issue is that OP was giving TWO different examples of use, pulling a single value versus multiple. they should have clarified.
Yes, on review, I should have made two examples instead of combining them. Thanks for persevering through my brevity and reaching understanding :)
They're totally intuitive if you were an Occam programmer.
So it's intuitive because you are familiar with those concepts. I don't see how that goes against what I said.
Yes, you need to learn language features in order to use the language, shocker!
I've written go for a year or so. That looks close to go code I look at every day. I wouldn't be surprised if it was in the language.