1. 32
  1.  

  2. 12

    Came in expecting an anti-Go screed, left excited to read an alternative suggestion. :)

    1. 7

      Context should go away for Go 2

      I (mostly) agree with this overall thesis, but wish to add some more ahem context.

      idea that context should carry a map of meaningless objects to meaningless objects

      It’s actually a pretty clever idea. Coupled with unexported types and the fact that contexts are not enumerable, it lets you hide some context keys to prevent people from reading from that.

      type foo struct{}
      var fooKey = &foo{}
      
      function WithFoo(ctx context.Context, s string) context.Context {
        return context.WithValue(ctx, fooKey, s)
      }
      
      function GetFoo(ctx context.Context) string {
        return ctx.Value(fooKey).(string)
      }
      

      The exported WithFoo/GetFoo functions are just examples. If you don’t export anything, your context key can safely pass through other people’s code and they will never be able to access your private context data.

      it’s prone to name collisions.

      This is impossible if you use the pattern I showed.

      That’s the only problem the “context” package really solves (or attempts to solve)

      It’s also generally useful for any thing you might use dynamic scope or thread locals for in languages that support those. Yes, I know it’s technically lexical, but it tends to be used in a dynamically-scoped way.

      For example: Threading request id or current user all the way through for logging contexts.

      Go 2 should explicitly address the cancelation problem

      I agree with that. I’m a fan of Martin Sustrik’s structured concurrency idea, but I’m not sure how it would work out when retrofitted in to Go. Explicit termination of subprocesses, a la Erlang, is the way to go.

      Context is like a virus

      Yup. It’s also true of any side effects, or indeed any dynamically scoped constructs, such as panic/defer/recover. Go already mandates a stack-frame associated context object for panics, debugging, etc. Maybe it should just give up and expose the current lexical and/or dynamic context to all functions implicitly?

      1. 6

        I like that this post was written, and would like to see suggestions, but as someone who has abused the hell out of contexts in the past (with lots of success, and some regret!), I find fault with some of the arguments:

        If you use ctx.Value in my (non-existent) company, you’re fired

        1. Go’s type system sucks. So, yes, it’s not statically typed, but you can write wrappers around it to ensure safety at call sites, assuming you use the wrappers. Blame the type system, not the vessel. Or name every other package that uses interface{}, too.
        2. this is the most important point. It’s easy to forget to load state into a context, and much better to require explicit loading (e.g. a constructor that takes a Foo), though even in that case, you can pass a nil. BUT, you still are. documenting based on function signature, which is much easier to get write, and maintain.
        3. Use of ctx.Value has actually made testing super easy in some code I’ve written to use it. I’ve since become convinced that other ways are better. But, you simply inject a Mock into the context, and everything magically works.
        4. This is why new types are created for keys so often. I’ve not ever come across this as an actual problem.
        5. Not a very good argument. I’ve had lots of success especially around database code which uses ctx.Value to overwrite a connection object with a transaction instead for sub calls… without error. E.g. no more error prone than writing addition code with signed ints that can overflow. Incidentally, the new context friendly databaselsql uses the same pattern, and we’ve since switched to it… again, without issue.

        In summary, point 2 is correct, but the supporting points are mostly FUD.

        Context is mostly an inefficient linked list

        So… don’t use Context when a) you have thousands of Values nested in it, b) when you need absolutely every nanosecond of CPU time. In short, this is not a problem, 99% of the time, and if it becomes a problem, I know of some great papers that can increase the efficiency of linked lists…

        1. 1

          I would be interested to know what papers you’re referring to.

          1. 1

            The one that comes to mind first is Phil Bagwell’s Vlists.

            But you’ve also got Shao’s Unrolling lists as well.

            And, of course the CDR coding technique.

        2. 4

          This is a silly article without talking about the context of context. Say you want to time out or cancel an operation at an arbitrary safe point. How do you do it? How do you do it if the task spans several logical threads of execution? If you want a mostly failed example, look at the docs for the java thread class and the graveyard of deprecated APIs for stop/resume/kill.

          Honest question because I don’t know the answer. Here’s a few possible options:

          1. Work in a pure functional language so that by default every piece of code is a safe point and maybe you can do some monad magic such that timeouts and cancellations are automatically checked before/after and (with help from a runtime) during I/O.

          2. Thread context apis through every single function call and try not to screw it up. (This could be enhanced with help from the compiler, maybe just like ARC the compiled could insert default hidden context args, or maybe these are goroutine locals, idk)

          3. Revisit the erlang thing and supervisor trees (I don’t know if this is an actual solution but I heard those words somewhere)

          4. Revisit the failed java versions that seem to consist of “make the runtime panic/throw exceptions at arbitrary points” and see if there’s a sane way to handle things.

          5. Something better?

          1. 6

            Cancellation is really useful. In Twisted we added them to Deferreds, which are same as JavaScript Promises, and being able to chain cancellation, and then build timeouts on top of it, made network programming much much easier.

            The fact there was any idiom at all for this in Go was one of the few things I thought Go was doing well; it would indeed be much nicer to have it in the language itself. Presumably the answer will be “in the 1970s no one could cancel anything, so who needs it.”

            1. 2

              I would guess that once the other options are proposed, context.Context is the least bad option.

              1. 1

                At Google, we require that Go programmers pass a Context parameter as the first argument to every function on the call path between incoming and outgoing requests.

                Sounds like a classic case where monads (or some other way of modeling effects) could help.