1. 48
    1. 3

      Golang will soon be as bloated as any other language that makes bold claims from the start and then spends it’s later years backsliding to try and draw in it’s critics.

      1. 16

        I certainly have that concern too. However, from what I’ve seen, the Go team continues to be judicious and cautious in its choice of what to include. The only language change to speak of has been the addition of generics in 1.18. All other changes are in the standard library and the tooling, which as far as I can tell just keep getting better. It doesn’t have the same design-by-crowd/committee feeling as Python has had in recent years.

      2. 12

        This is an odd criticism of Go in general and this coroutine proposal specifically, I think. It’s proposing a standard library package which happens to lean on runtime support for performance reasons only. Go has an HTTP server in its standard library. Being a batteries-included language seems to be out of favour at present, but this would seem to be an example of Go (quite correctly, imho) swimming against the tide and including batteries anyway.

        I’m not sure what they’ve backslid on. People bring up generics a lot, but prior to the successful generics design, the Go FAQ had said:

        Generics may well be added at some point. We don’t feel an urgency for them, although we understand some programmers do.

        for what, a decade?

        Full disclosure, I don’t think Go is a good language. But I feel like they’ve been remarkably consistent on scope and feature creep.

        1. 3

          Yeah, it’s kind of silly for Go’s critics to claim that Go is a bloated language when the only real piece of bloat that has been added has been generics and its critics positively screamed for that for over a decade.

          1. 6

            The critics who are concerned that Go is becoming bloated, and the critics who screamed for generics, are different people.

            (A lot of social phenomena make a lot more sense when you consider that what looks like one group from the outside is actually several groups, each with different thoughts and motivations.)

      3. 5

        I remember when Go’s critics were arguing that its simplicity claims were easy to make as it was a new language, but “give it 10 years and it will be as bloated as Java/Python/C++/etc”, well it has been 14 years since Go debuted and it remains pretty bloat-free (generics is the only real bit of bloat added to the language, and that was positively demanded by Go’s critics). It’s nice to see that people are still making this same argument 14 years later. :)

      4. 3

        This is hardly bloat. It’s quite close to Lua’s asymmetric coroutines and Python’s (synchronous) yield statement, which are both solid designs with minimal overhead.

      5. 3

        This is a new package in the standard library, not a language feature.

      6. 1

        Contrariwise, it seems they are doing an admirable (if quite slow) job of growing their language. I look forward to the addition of macros, inheritance, gradual typing, and unrestricted compile-time computation.

        1. 2

          I don’t know — Go goes against almost everything in that absolutely great talk. A core tenet of which is that one should be able to write a library that can seamlessly extend a language. Go has real trouble around it with an over reliance on built-ins.

          It’s no accident that Guy Steele worked on Java and later on Scheme (among others). Scheme is a more niche language and while the above core tenet definitely well-applies to that language, I would highlight Java over Go in this this gradual growth regard, given its longer life and extensive use, and if anything, that’s its core strategy: last movers advantage. The team really well evaluates which language features are deemed “successful” after they were tried out by more experimenting PLs.

          1. 7

            A core tenet of which is that one should be able to write a library that can seamlessly extend a language. Go has real trouble around it with an over reliance on built-ins.

            The coroutine library described in the article is a library.

          2. 2

            It’s no accident that Guy Steele worked on Java and later on Scheme (among others).

            I think you have this backwards. He worked on scheme in the 70s and Java was created in the 90s. Not exactly sure when Steele got involved in Java though. I know his big contribution was generics, but I imagine you have to do some work before you get invited to do something so major.

            1. 7

              His contribution was the Java Language Specification, among other things. He was one of the original team of 4 (James Gosling, Guy Steele, Bill Joy, and maybe Arthur van Hoff, the namesake of Java’s AWT package a la “Arthur Wants To”). He’s still at Sun/Oracle Labs (his office was right next to mine).

              He’s also known for Lambda The Ultimate (LTU), a series of papers that he published in the 70s, I think. (There’s a website by that name, inspired by those papers, and focused on PL topics.) And a bunch of other things he’s been involved with over the years at and around MIT. Including square dancing.

              1. 4

                until recently with some significant breakthrough, his name was still on the paper defining how to cast a float to a string precisely (minimal length round trip safe conversion).

                To be fair. This technique was probably known before hand, and the co-author, Dyvbig iirc, probably did more work than him. But still.

                Oh this algorithm is every where. Like nearly all libc.

              2. 1

                He was one of the original team of 4 (James Gosling, Guy Steele, Bill Joy, and maybe Arthur van Hoff, the namesake of Java’s AWT package a la “Arthur Wants To”).

                Ah! That’s interesting! Wikipedia only lists Gosling as the designer. I double checked the wiki page for Java before posting, but I guess it doesn’t tell the whole story. Thank you for the clarification!

            2. 2

              Oh, didn’t realize Scheme is that old of a language! He has made quite some contributions, having been also on the ECMAScript committee.

              1. 2

                Yeah, he also was one of the original authors of emacs. Pretty crazy career when you think about it!

          3. 1

            I guess the sarcasm didn’t quite come through…

            That said, I’m actually somewhat curious—not really knowing either language particularly well—in what respects java is more expressive and orthogonal than go. Both added generics after their initial release, are garbage-collected (though java is much better at it), have some semblance of first-class functions, have some semblance of generic interfaces, and have some cordoned-off low-level primitives. Both are sufficiently expressive and capable that most of their infrastructure is written in itself (contrast with, say, python); hotspot happens to be written in c++, but this seems more a consequence of historical factors, and istr cliff click said that if he were writing hotspot today, he would write it in java. Java has inheritance and exceptions, where go does not; are there other major differences?

            1. 3

              Go has goroutines which Java is soon to get. Go has value types and value/pointer distinction, which Java is maybe getting at some point

              The biggest difference is that Java (in a typical implementation) is a fairly dynamic language, while Go is a fairly static one. Dynamically loading classes into your process is the Java way. Java is open-world, Go is closed-world.

              I feel the last property is actually the defining distinction, as language per se doesn’t matter that much. I do expect Go & Java to converge to more-or-less the same language with two different surface syntaxes.

              1. 2

                Is Java actually going to get this soon? I feel like I’ve been hearing it’s just around the corner for like 5-10 years.

                1. 1

                  I don’t follow too closely, but my understanding is that green threads (the current reincarnation of) are fairly recent (work started 2017), and are almost there (you can already use them, they are implemented, but not yet stabilized).

                  Work on value types I think started in 2014, and it seems to me that there’s no end in sight.

                  1. 2

                    The green threads work started a lot earlier than 2017. Not sure when it got staffed in earnest, but the R&D had been going on for a while before I left in 2015.

                2. 1

                  I think it goes in this year. I’m not a java guy, but I think this is the final JEP.

                  https://openjdk.org/jeps/444

              2. 1

                Interesting. Where can I read more about the Java equivalent of goroutines?

                1. 3

                  It’s called the Loom project. It is already available in preview for a few Java versions, so you can also play with it. The cool thing about it is that it uses the same APIs as the existing Thread library, so in many cases you can just change a single line to make use of it.

        2. 1

          I can’t tell if the sarcasm here is “you think Go is becoming bloated very quickly” or “Go is never going to add these features (and that’s a bad thing)”…

    2. 1

      Hm, this suggests throwing for cancelation. Does it mean that there are now two cancelation mechanisms? One via a panic, and one via Context.Done?

      1. 1

        I don’t think this suggests panics are an ersatz method of cancelation. The proposed implementation translates panics in coroutines to panics in their receivers, but that seems reasonable, and in line with current semantics.

        1. 1

          I am 0.95 sure that that’s exactly what is being proposed, see these bits:

          Calling cancel will be like resume, except that yield panics instead of returning a value.

              cancel = func() {
                  e := fmt.Errorf("%w", ErrCanceled) // unique wrapper
                  cin <- msg[In]{panic: e} // "Throwing" the panic into the coroutine
                  m := <-cout
                  if m.panic != nil && m.panic != e {
                      panic(m.panic)
                  }
              }
              yield := func(out Out) In {
                  cout <- msg[Out]{val: out}
                  m := <-cin
                  if m.panic != nil {
                      panic(m.panic) // Ersatz-cancelling the coroutine by panicking. 
                  }
                  return m.val
              }
          

          That is, panics come up in two places:

          • when we propagate a panic from coro to caller
          • when caller canels coro, it injects a different panic into the core

          This is the bit which untangles the two

                  if m.panic != nil && m.panic != e {
                      panic(m.panic)
                  }
          
          1. 2

            Wow, you’re right.

            Cancel stops the execution of f and shuts down the coroutine. If resume has not been called, then f does not run at all. Otherwise, cancel causes the blocked yield call to panic with an error satisfying errors.Is(err, ErrCanceled).

            That sucks. It fundamentally changes the semantics of panic, in the way you describe.

            edit: if yield needs to be interrupt-able by cancelation, then it should return an error, i.e.

             func New[In, Out any](
            -	f func(in In, yield func(Out) In) Out,
            +	f func(in In, yield func(Out) (In, error)) (Out, error),
             ) (
             	resume func(In) (Out, bool), 
             	cancel func(),
             )
            

            edit 2:

            Panic propagation takes care of telling the caller about an early coroutine exit…

            I’m not sure how. Early coroutine exits certainly aren’t exceptional. Consequently, callers shouldn’t need to recover panics to handle early coroutine exits.

            1. 2

              Yeah, exactly!

              I actually did exactly the same thing for rust-analyzer, and, practically, it worked OK. But that was my specific code-base where I was well-aware that cancellation unwinds. As a general pattern to use across unbounded amount of code throughout the ecosystem, that’s a much tougher sell.

      2. 1

        from Cancellation:

        Panic propagation takes care of telling the caller about an early coroutine exit, but what about telling a coroutine about an early caller exit? Analogous to the stop function in the pull iterator, we need some way to signal to the coroutine that it’s no longer needed, perhaps because the caller is panicking, or perhaps because the caller is simply returning.

        1. 1

          Yes, and the way it actually works is by throwing a panic into the coroutine:

                  m := <-cin
                  if m.panic != nil {
                      panic(m.panic)
                  }
          

          Which means that the code in general needs to be prepared to the possibility that it might get cancelled via panic & unwinding. Which means that code in general now has to be prepared to handle two cancelation signals:

          • on every level of the call-stack, the code needs to check for the Context.Done being closed, and promptly return if that’s case
          • code needs to be prepared for Cancelled exception to be thrown.

          Which sort of raises the question – why not handle the .Done case also via a panic?

          I think either mechanism, cooperative cancelation via checking “is canceled” flag, or forced cancelation via unwinding, is fine. What is surprising is that we need both of them.

          1. 2

            In general I’m all for forcibly killing threads, but you need to know that you won’t ever leave a thing in an invalid state because you got killed unexpectedly. Most languages don’t even guarantee that for their standard library (and don’t have good tools for writing code that does).

            You can only panic a coroutine when it is waiting for you—effectively the call to yield is the cooperative done check—which is a much easier world to live in, especially since the design of this API makes it unlikely that you’ll be cancelled via a callee you didn’t know anything about (how did they get a reference to your yield?).

          2. 1

            I haven’t thought about this enough to have a strong opinion yet, but my off the cuff reaction is that the important thing is that cancellation happens at well defined points (await points in async/await languages, <-ctx.Done() in current Go). If the cancellation happens via panic or an in-band signal seems less important. But it remains to be fully seen.

          3. 1

            I know i am a broken record at this point, but coroutine plus panic side effect start to look a lot like something that could benefit from some effect handlers theory….

            In particular recent work on lexically scoped effect handlers (ala capabilities) and optimisations to nicely inline them.

            Haskell got support for proper delimited continuations in ghc recently which are basically coroutines…