1. 14
  1.  

  2. 13

    I probably won’t express this quite right, but to me this is one of the Big Ideas of golang, which is talked about a bit less than - say - channels or lack of generics :-)

    We write code, with an implicit sequence. “perform this request to get a response, do this operation on it and return it to our caller”.

    That works fine in a single-threaded world. Until we want to do other things at the same time, and we realise we’ve made a blocking I/O request to fetch the result.

    So we don’t want to do that. So various syntactic sugar is invented for “ok, schedule this request and then get on with other work. When the request arrives back, run this bit of code and then arrange things so we can return our calculated value to our caller, whom you will have resumed”.

    That sugar might be async/await, it might be a chain of callbacks, it might be promises. Heaven help you, it might be a continuation. But what they are all striving to convey is that simple “do this, get that, return something”, in a sequence.

    The golang runtime allows you to write exactly this. Behind the scenes, it will spawn an OS thread to sit in the blocking syscall, while the runtime gets on with your other work. There is no “colour of function” problem.

    I know it is simply “green threads and OS threads with a managed thread pool”. I also know that “threading is hard”. Golang encourages promiscuous use of green threads (goros) and it seems to work. Sometimes something which seems like a difference in degree is actually a difference in kind.

    But this idea - that the programmer simply expresses “this, then that” and the runtime removes most of the obstacles, is - I think - very powerful.

    1. 2

      I’m on board with this.

      Python already had, before async was added, a green threading system that worked kinda okay within Python’s well known limitations such as the existence of a GIL.

      import gevent
      gevent.monkey_patch_all()
      import thread # and go use threads
      

      PyPy made gevent reasonably faster by bringing some of the perf critical bits in to the interpreter AIUI.

    2. 8

      In Rust and JS, using .map/.then instead of async syntax was literally the status quo until both ecosystems decided that that was way too painful to write and understand. I’m not sure what the benefit here would be to go back to that world.

      1. 2

        In Rust, this had the second side-effect that you couldn’t borrow into async types.

      2. 6

        When all you have is a hammer everything looks like a nail. Python is not Haskell. It’s not designed around .map crap everywhere. Callbacks are evil.

        1. 4

          Callbacks are evil

          Couldn’t stress this enough. We’re slowly migrating from Twisted callbacks to python asyncio (or Trio) and it has been pure pleasure to see all of the callback spaghetti disappear down the toilet.

          1. 1

            Reading the Trio documentation makes me literally (and I do literally mean literally) feel warm and fuzzy inside.

        2. 5

          The main problem here is that there are competing memes to smear different approaches. If you suggest something like what Python, etc. have done with async/await, the meme to smear you with is “what color is your function”. If you suggest something like this article, the meme to smear you with is “callback hell” (since what this article is really pushing is a callback-style approach to async code). There’s no way out of that and no productive discussion to be had when it devolves into these memes.

          1. 4

            I have been doing a bit of JavaScript lately, and my thought so far is that the sync/async divide is a mistake, exposing details which shouldn’t matter in general. Seems like either code should always appear synchronous (and the runtime should do what’s necessary in the background) or it should always be asynchronous. But maybe I am missing something!

              1. 7

                C# solved this by doing both. Named async functions that return a value need a Task<> type. The async keyword is there for anonymous functions (which have inferred types).

                The await keyword avoids “callback hell” but because you’re dealing with a reified Task and not just a colored function you can also call other functions on it and define strategies and such.

                  1. 1

                    Great article! I think that’s part of why writing JavaScript has been so painful for me: I normally write in Go, which as Bob points out does all this right, and just works.

                  2. 6

                    the sync/async divide is a mistake

                    It is. Of course, the problem is, everyone puts up with this async/sync divide because of backwards compatibility.

                    An ideal distillation of this would involve an async-only runtime, with a programming model that is sync by default. If you wanted to do async-style things, you could, but it’d be opt-in, and only for the type of work that was traditionally async, such as receiving from N sockets at once. Think of something along the lines of Win32’s WaitForMultipleObjects() as a primitive.

                  3. 2