4 ms·
Not him. For me it's: 1. context.Context and putting everything under the sun there, untyped (which is a design fault, not just "people using it wrong") 2. r
by shp0ngle 6y ago
Not him.
For me it's:
1. context.Context and putting everything under the sun there, untyped (which is a design fault, not just "people using it wrong")
2. reusing the looping variable in for loop
2.5. general awkwardness of go templating (I plan to write whole article about this, but too lazy to actually write it)
- pcl 6y agoI struggle with context.Context as well. It really comes across as a design cop-out. The language makes a big deal about memory management and data structure ownership, and as a result it’s sorta a pain to send stuff around on the stack, both at initialization and runtime. As a casual golang user, the context mess feels like an escape hatch to help with those problems. I’d prefer some GC compromises built into the language itself instead.
- throwaway894345 6y agoI'm confused about how a "GC compromise" would help with having to pass things around the stack. It sounds like you just want a request-scoped global or something, but I don't know what that has to do with the GC? FWIW, I think request-scoped globals are terrible and is among the many things I hated about Flask. I think generics will largely solve this problem by allowing us to write generic middleware with a type-safe context and probably even functions to compose the middleware and automatically propagate the context variables.
- pcl 6y agocontext.Context is used in a number of situations, including for a "don't call it a request-scoped global". And whatever, web frameworks always end up with one of those. Would love to see languages do better, but ok. My concern with its use in golang is that so much seems to get jammed into it. My experience with it is that it ends up being a holding pen for all the things that other systems do with initialization-time dependency injection / resource lookup sorts of things, but replaced with "oh just pass `ctx` as an argument everywhere". From what I've seen of Go, it looks like that happens because of the state management idiomatic constraints of the language, which in turn seem GC-related. In a garbage-collected language, I'd put a bunch of context-y things into some sort of request executor datastructure, the web framework would manage the initialization and retention of that, and request handlers would operate against those intialized data structures. That doesn't line up all that well with idiomatic Go, and so we end up needing to hide those sorts of things somewhere else. Enter context-abuse. Of course, there are plenty of use cases where those sorts of patterns aren't relevant. Right tool for the job and whatnot. Again, I'm a casual golang user at best -- I bet I'm missing some newer / better approaches, and would love to learn more about what they are.
- shp0ngle 6y agoGo has GC? I don't understand your message that much :)
- pcl 6y agoYeah but my experience with idiomatic go has been heavily value-biased, and a bunch of useful patterns for state management tend to lend themselves more towards a reference-oriented system. In my experience, this is outside the happy path, but necessary, and so people end up jamming all sorts of things into context.Context to get around the idiomatic paths.
- saturn_vk 6y agoMy belief is that a) context should never have had a dual role of being able to hold arbitrary data (just add a map[interface{}]interface{} to the *http.Request ...) and b) it should not have existed as a means of work cancellation. Right now, one is forced to have a context argument just so that it can be passed from some upper level to a lower level that actually needs it, even if your function itself doesn't really need it. Since goroutines are built into the language, a means of cancelling them should've been built in as well.
- kortex 6y agoBut how do you cancel something without a handle to that something? What (I think) you are suggesting implies globally scoped, mutable variables that affect control flow dramatically. Sounds really hard to reason about and very munch anti-Go-explicitness. Context is the handle by which an outer context holds on to the nested ones. The only non-bonkers way to have a handle like that would be to give every function a getContext function to query some kind of context tree and signal parent/children. This is totally against Go explicitness as well. Maybe something like Scala's implicits to sugar it, but that's another not-go-looking pattern (although it would be nice)
- bbatha 6y agoThe go statement could return a handle to the go routine. There can be a cancel magic function that cancels the go routine. You can regain the task local storage by using this handle as a key into a map of task data. This would have been a breaking change particularly in CGo threads but it’s a vastly simpler model for the user.
- saturn_vk 6y agoThis is just hypothetical, but the go statement could be an expression that returns a cancel function. From there, the sky is the limit. And in the other side, have a runtime.Done function that returns a channel that will need closed when the said cancel function is called. There are a bunch of other runtime functions that are goroutine specific already, so there is precedent
- specialist 6y agoWhat would you call fixing #2? "Loop's vars are const within loop's scope?"
- shp0ngle 6y agoNo, it can just use a different variable each loop turn? Go authors themselves admit it might have been a design mistake, but is unfixable without breaking compatibility. https://golang.org/doc/faq#closures_and_goroutines https://golang.org/doc/faq#closures_and_goroutines > This behavior of the language, not defining a new variable for each iteration, may have been a mistake in retrospect. It may be addressed in a later version but, for compatibility, cannot change in Go version 1.
- throwaway894345 6y ago> No, it can just use a different variable each loop turn? How does that work? The call-frame needs to be of fixed-size, but it's rarely knowable at compile time how many iterations a loop will perform. Unless we begin heap-allocating loop variables or treat each loop iteration as a function call (both of which will kill performance, I think), I don't see how this could work?
- morelisp 6y agoYou seem to have some fundamental misunderstanding about what costs memory. As the documentation says, the following works fine: for _, v := range values { v := v // create a new 'v'. go func() { fmt.Println(v) done <- true }() } The scope of the variable has no effect on how many values may or may not be allocated.
- throwaway894345 6y ago> You seem to have some fundamental misunderstanding about what costs memory. Probably, hence my "How does that work?" question. I don't doubt your example--I know it works, but I'm struggling to understand how it manages to work, since (as I understand it) the closure presumably captures the environment, which presumably means taking the address to the new `v` which presumably is the same address for each loop iteration?
- skrtskrt 6y agoI hated Go templating until I really dug into it and started loving how flexible and not-OOP it is. It makes me want to port it to Rust, since so many of the major Rust templating libraries are Jinja copies or Handlebars copies.