13 ms·
> Go uses channels for all concurrency. Go uses goroutines for concurrency, not channels. Goroutines are like threads, but they are managed by the Go runtime i
by ngrilly 11y ago
> Go uses channels for all concurrency.
Go uses goroutines for concurrency, not channels. Goroutines are like threads, but they are managed by the Go runtime instead of the OS, and they use less system resources (a new goroutine uses just a few kilobytes, and its stack is resized on demand if necessary).
Go uses channels as a synchronisation mechanism. But other synchronisation mechanisms can be used (i.e. mutexes or atomic operations).
> Even simple tasks often need them because Go's I/O libraries tend to be async-first.
That's quite the contrary. Most libraries are synchronous. You don't need callbacks (like in Node.js) or async/await (like in Python 3). This is made possible by goroutines and that's a big advantage of Go.
I very much like the async/await mechanism offered by C# or Python because it makes side-effects explicit. But its drawback is its "virality": if you introduce an async operation in a function, you have to "propagate" the change by converting all callers to async/await.
About the error handling, I'm still on the fence. Your comment summarizes very well the drawbacks of Go on this topic. But what we gain in terms of simplicity and making error handling very explicit is probably worth it. I think that error handling is still a subject of tension in every language and the problem is not fully solved (even in Rust, Haskell or Erlang). Time will tell.
> But its build story is still really, really bad.
I think you meant "packaging story" instead of "build story". It's true that at this moment, the Go project has not rallied around a single and universal packaging tool (like npm in Node.js). But in practice, if you vendor your dependencies, I think it's easy to manage a large codebase without errors.
> do try F# if you haven't yet. [...] its error handling is light years ahead of what Go offers
I'd be curious to read a short example.
> Marlow's paper on Haxl for facebook
I agree about this paper.
- pcwalton 11y ago> I very much like the async/await mechanism offered by C# or Python because it makes side-effects explicit. It's also potentially faster and more efficient than goroutines, because it packs the state to be shared on context switches into what is typically a very tight structure instead of saving the entire stack. > I think that error handling is still a subject of tension in every language and the problem is not fully solved (even in Rust, Haskell or Erlang). Time will tell. Can you elaborate as to what the problems you see with error handling in those languages are?
- KirinDave 11y agoWell it's not like the Either monad (Haskell) or erlang's {err, Reason} pattern is much better. Go's just bad at sharing details about new errors, in that interfaces are a pretty poor tool for that sort of work. At least Either has an Applicative and Monadic form, which is really nice for decoupling flow from handling. Error handling is tricky because it requires richness both in how you talk about types and how you handle control flow. But using GADTs and to a lesser extend F#'s discriminated unions for errors does have nice static checking properties. That's definitely an improvement over Go's "I just regex'd the string please send help" approach.
- Locke1689 11y agoIt's also potentially faster and more efficient than goroutines, because it packs the state to be shared on context switches into what is typically a very tight structure instead of saving the entire stack. I'm sure you already know this, but for anyone new to design in concurrency backends, not only does async/await (CPS) have the potential to really trounce goroutine-style concurrency in modern systems, but if you have a really smart compiler and some OS support, it can really fly. See Joe Duffy's blog post about asynchrony in Midori for more info[1]. [1] http://joeduffyblog.com/2015/11/19/asynchronous-everything/ http://joeduffyblog.com/2015/11/19/asynchronous-everything/
- ngrilly 11y agoRust error handling can be concise thanks to the try! macro, but macros bring their own problems (like making more difficult to write refactoring and static analysis tools). Haskell error handling can be concise thanks to monads, but they need higher kinded types which bring their own share of complexity. The conversation on the "RFC: Stabilize catch_panic", found on Rust's issue tracker, illustrate some unsettled questions I had in mind (https://github.com/rust-lang/rfcs/pull/1236 https://github.com/rust-lang/rfcs/pull/1236). For example, kentonv wrote: All code can fail, because all code can have bugs. Obviously, we don't want every single function everywhere to return Result<T, E> as a way to signal arbitrary "I had a bug" failures. This is what panic is for. graydon wrote: Currently you've adopted a somewhat-clunky error-type with manual (macro-assisted) propagation. Some rust code uses that correctly; but much I see in the wild simply calls unwrap() and accepts that a failure there is fatal. ArtemGr wrote: The only way to maintain both the safety and the no-panic invariants is to remove the panics from the language whatsoever. Explicit errors on bounds check. No assertions (you should make the assertion errors a part of the function interface instead, e.g. Result). Out of memory errors returned explicitly from every heap and stack allocation. If you'd like to keep the assertions, the smooth allocations and other goodies then you either need a way to catch the panics or end up making programs that are less reliable than C. No modern language crashes the entire program on an out-of-memory or an integer overflow, but Rust will. The libraries we have, they do panic, it's a matter of fact. Withing the practical constraints and without some way of catching panics you can't make a reliable program that uses external crates freely. BurntSushi wrote: If something like catch_panic is not stabilized, what alternative would you propose? (Option<T> and Result<T, E> are insufficient.) On the same topic, there is this post about introducing a `?` operator or a `do` notation (inspired by Haskell) to streamline error handling: http://m4rw3r.github.io/rust-questionmark-operator/ http://m4rw3r.github.io/rust-questionmark-operator/ And there is RFC 243, about "First-class error handling with `?` and `catch`": https://github.com/rust-lang/rfcs/pull/243 https://github.com/rust-lang/rfcs/pull/243 But I'm sure you're quite aware of these discussions :-) My general feeling is that, whatever programming language you consider (Python, JavaScript/Node, Go, Rust, Haskell, Erlang, etc.), the right way to handle errors is still an open question.
- KirinDave 11y ago> Go uses goroutines for concurrency, not channels. This is more of a practical piece of advice than a strictly correct one. In practice, Go concurrency means typing "go" but thinking in terms of channel groups and one-off channels. I just don't find that distinction very productive when someone asks how it "feels" or what it is "like" to program in Go as opposed to asking for an explanation of Go's concurrency model. > That's quite the contrary. Most libraries are synchronous. You don't need callbacks (like in Node.js) or async/await (like in Python 3). This is made possible by goroutines and that's a big advantage of Go. Within the context of a single goroutine, you're right. I didn't express this very well. Goroutines are often how you process network I/O. My bad for not explaining this well, I was in a bit of a hurry when I wrote that part and the whole thing got too long so my proofreading was a bit sloppy. Thanks. > I think that error handling is still a subject of tension in every language and the problem is not fully solved (even in Rust, Haskell or Erlang). Time will tell. I really think Common Lisp had a fantastic solution here and I miss the evolution over try-catch they spec'd. I wish more people understood it. It was such an excellent idea. > I think you meant "packaging story" instead of "build story". It's true that at this moment, the Go project has not rallied around a single and universal packaging tool (like npm in Node.js). For Go, is there a difference? You either have static builds or you don't, and artifacts don't exist outside of this last time I checked. So any packaging solution is de-facto part of the build story and vice versa. This is in sharp contrast to Maven or others where build artifacts can exist entirely outside of the expectation of use in the build chain (it's possible to launch a jar directly). The actual compilation of the final build is fast and the cross compiler is definitely nice to have while we're all forced into a weird world where there is no good laptop OS for developers who often end up in open plan buildings where we're expected to be migratory (or simply working without an office at all). I wouldn't dream of underselling that. Of course, I'd prefer a good interactive development pattern. There's a great gif of Daffy Duck dressed as a highwayman constantly swinging down from a rope into a tree over and over. My friends associate this with compiling Go, and I feel like that's a very good metaphor. But just getting the libraries you need to build is a hassle. Plain and simple.
- ngrilly 11y ago> I really think Common Lisp had a fantastic solution here and I miss the evolution over try-catch they spec'd. Are you thinking of Common Lisp's conditions/handlers/restarts? I've never programmed in Common Lisp but have always been intrigued by this idea. > This is in sharp contrast to Maven or others where build artifacts can exist entirely outside of the expectation of use in the build chain Ok, I think I understand now. So you'd like to be able to use "prebuilt" dependencies in Go (delivered like a .so in C++ or a .jar in Java)? Honestly, the compilation is so quick, and the advantages of a statically linked executable are so great, that I have trouble imagining why I would want that. In Go, instead of saving the dependency as a .jar file (for example), I just saved the dependency source in the `vendor` directory. For the record, this is exactly what big projects like Chromium do in C++.