7 ms·
I think anyone can agree with the author that Go definitely lacks conveniences you're used to in other modern languages. I just think we also tend to dramatize
by hombre_fatal 6y ago
I think anyone can agree with the author that Go definitely lacks conveniences you're used to in other modern languages.
I just think we also tend to dramatize how much that matters.
I also think Go's benefit really is that it's simple and that you have very few tools that let you do anything other than focusing on solving your problem, and like the author I do think goroutines are the exception to that.
Where I work, we don't even use them. We use plain ol' mutexes instead.
When coming from higher level languages, Go does feel frustrating. In Javascript, you're writing `const [a, b, c] = await Promise.all([x(), y(), z()])`. In Go, you're writing 40 lines of WaitGroup code. It's easy to go ughhh.
But I think a nice way to appreciate Go's conservative middleground is to go back to writing some C/C++ code for a while, like some Arduino projects. Coming from that direction, Go feels like a nice incremental improvement that doesn't try to do too much (with perhaps the exception of goroutines).
Go's performance is also particularly stand-out which makes up for many of its convenience shortcomings. It's fast enough that I've written some code in Go where I would have written C not long ago. And writing C involves quite a bit more concessions than what Go gives you, so in comparison, Go kinda spoils you.
Go has plenty of annoyances too though. Not having any dev vs prod build distinction is annoying. Giving maps a runtime penalty of random iteration in order just to punish devs who don't read docs is annoying. It's annoying to have crappy implementations of things like html templating in the stdlib which steal thunder from better 3rd party libs. Not being able to shadow vars is annoying. `val, err :=` vs `val, err =` is annoying when it swivels on whether `err` has been assigned yet or not, a footgun related to the inability to shadow vars. etc. etc.
But it's too easy to overdramatize things.
- strken 6y agoGo's lack of generics and metaprogramming can be extremely frustrating when you're working with services that copy data around from one struct to another, which ends up being a lot of them. You're left with reflection, code generation, or doing it field by field. It'd be nice to have syntactic sugar for splatting structs together, or a library that let you do it in a typesafe and efficient way. EDIT: I will defend the random map order, though. Golang maps were only predictable up until a set number of items, which caused hard-to-detect bugs where your small test case would be in order but your large real-life data would be out of order.
- hombre_fatal 6y agoI certainly agree. I would love for Go to get to a place where I never have to copy and paste code again. Generics will get us part way there. The next item on my wishlist are algebraic data types. In complex projects I've opted for Rust over Go just because of the abstraction power of being able to represent things like: Dest = Register (A | B | C) Src = Register (A | B | C) | Immediate value OpCode = Nop | Add(Dest, Src) | ... In Go, I'm so tired of `interface{}`. Edit: To respond to your edit about map iter order, that's related to my complaint of Go's lack of dev vs release build. Randomizing map iter order in dev builds is acceptable to me. Go makes you pay for that in prod.
- yuribro 6y agoI don't understand the jump from the lack of algebraic types, to using interface{}. You can spell out all your types into explicit structs (where you can choose between a simple implementation with a little worse performance or to make the implementation a little more complex) and pass them around. There are many complex code bases (kernels & databases come to mind) which use C (not C++), and they don't resort to passing void* around the "business logic" parts. The idea that for a complex project you would choose a different language with so many different characteristics due to a minor detail about the type system (this is not exactly Haskell vs JS...). This kind of decision making would not pass in any reasonable organization...
- kaoD 6y agoI don't know Go. How can you emulate sum types on it? And how do you pattern match them?
- simiones 6y agoIt depends on what you mean exactly by "emulating sum types" and "pattern matching them". An example is rolling your own discriminated union: type Avariant struct { a int } type Bvariant struct { b string } type SumChoice int const ( SumA SumChoice = 0 SumB SumChoice = 1 ) type Sum struct { type SumChoice A *Avariant B *Bvariant } sumA := Sum{choice: SumA, A: &{a: 7} } sumB := Sum{choice: SumB, B: &{b: "abc"} } func foo (x Sum) { switch(x.choice) { case SumA: { fmt.Printf("Value is %d", x.A.a) } case SumB: { fmt.Printf("Value is \"%s\"", x.B.b) } } } As usual with Go, it's ugly and verbose and error prone, but it just about works. The previous poster was probably thinking of something similar but using interface{} (equivalent to Object or void*) instead of Sum, and then pattern matching using an explicit type switch: func foo (x interface{}) { switch actual := x.(type) { case AVariant: { fmt.Printf("Value is %d", actual.a) } case BVariant: { fmt.Printf("Value is \"%s\"", actual.b) } } } This is slightly less risky and has less ceremony, but it's also less clear which types foo() supports based on its signature. Since Go has only structural subtyping [0] for interfaces, you would have to add more boilerplate to use a marker interface instead. [0] In Go, a type implements an interface if it has functions that match (name and signature) the interface functions. The name of the interface is actually irrelevant: any two interfaces with the same functions are equal. So if you declare an interface MyInterface with no methods, it is perfectly equivalent to the built-in `interface{}`: any type implements this interface, and any function which takes a MyInterface value can be called with any type in the program.
- xpressvideoz 6y agoI agree with that. Go can be frustrating if you expect the level of expressiveness of Python, Java, or Rust. But if you regard it as a better C, you can appreciate its ergonomic upgrades compared to C. What is unfortunate is that Go could be much more than just a better C, considering Google's potential and the decades of programming language research that were present at the inception of Go. By trying to simplify things too hard, and ignoring precedents, Go missed a huge opportunity cost. And it even failed to fulfill its original intent: it cannot be used in place of C, unlike Rust.
- systemvoltage 6y agoCould you please expand on why is Go not a replacement for Rust? Is that because of the lack of manual memory management?
- hombre_fatal 6y agoThe obvious case is embedded programming. You can run Go via a custom compiler for microcontrollers: https://tinygo.org/ https://tinygo.org/. But to their point: why incur that sort of overhead for a language that doesn't necessarily give you enough conveniences to be worth it? Or rather, Rust gives you more conveniences with very little runtime overhead. It's not exactly damning to be unable to compete with C on a microcontroller with 2K RAM, they were just pointing out the end of my comparison with C/C++.
- xpressvideoz 6y agoWhat I wanted to say was that Go was not a replacement for C, compared to Rust which could have similar performance characteristics as C or C++. The lack of manual memory management is one thing, but there are cultural reasons too. For example, in Go it is very idiomatic to return an error value as a string, which cannot be even imagined of in the C, C++, or Rust ecosystem. That's because as Go is targetted toward backend-level programming nowadays (which was different at the beginning of Go, when Go was marketted as a systems language), some minor overhead is acceptable if the convenience it brings is worth it. Go is full of such compromises, which isn't exactly a bad thing, but unsuitable to the lowest level programming which begs for an extremist position regarding performance issues. The mentality of Go is good enough for conventional programming, but bad for systems programming. And I think such cultural differences stem from Go's simplicity first mindset.
- arp242 6y agoI don't think this article really overdramatizes things; if I had written "zomg Go is terrible look what I need to write to delete an item from a list!" – a sentiment I've seen expressed a few times over the years – then, yeah, sure. But that's not really what the article says. As it mentions, I don't think these are insurmountable issues, and I still like and use Go: it's pretty much my go-to language these days. Overall, I think it did a lot of things right. But that doesn't mean it's perfect or can't be improved.
- hombre_fatal 6y agoI used "dramatize" to describe the blog post (in such that the blog post was written) and then "overdramatize" at the end in reference to my own infinite list of annoyances with Go and the normal course of discussion on HN about proglangs. The blog post does admit "Are these insurmountable problems? No." Learning it in 5 minutes certainly is a stretch as you point out, but I'd still say you can learn it in a weekend to a greater degree than any other language I use. Though you'll notice my comment after the first sentences is a way to share my own thoughts on Go, an opportunity I'm never going to neglect.
- ganafagol 6y agoThe curse of having landed on the HN front page. You write a little blog post with some random thoughts, somebody found it insightful and posts it to HN and if more people like it, it ends up on the front page. That exposure tends to attach a wide-ranging seriousness to this kind of blog post which may dramatically overinflate the point that it originally intended to make.
- nemo1618 6y ago> Giving maps a runtime penalty of random iteration in order just to punish devs who don't read docs is annoying Can you explain what you mean by "runtime penalty?" Last I checked, the iteration is only random wrt its starting offset; after that, it proceeds linearly through the buckets. So you only generate one random integer per `range`, which seems acceptably cheap.
- scaladev 6y agoIt still seems to be working as you described. https://github.com/golang/go/blob/db8142fb8631df3ee56983cbc13db997c16f2f6f/src/runtime/map.go#L831 https://github.com/golang/go/blob/db8142fb8631df3ee56983cbc1...
- lmm 6y agoBut why would you be comparing to C? I've yet to find any scenario where Go is an option and e.g. OCaml isn't (unless the scenario is "I want to use a language whose creator called it a "systems language"").
- pjmlp 6y ago> But I think a nice way to appreciate Go's conservative middleground is to go back to writing some C/C++ code for a while, like some Arduino projects. Coming from that direction, Go feels like a nice incremental improvement that doesn't try to do too much (with perhaps the exception of goroutines). As someone that was writing Arduino like code in C++ in 1993 (MS-DOS), I fail to see that.
- BenFrantzDale 6y agoIf in 2021 you think of “C/C++” as a single language, you are missing out on the last decade of C++.
- pjmlp 6y agoC/C++ isn't a programming language, rather the abreviation for C and C++. I am fully aware of the last decade of C++, including papers written by Bjarne Stroustrup and other C++ key figures, where they uses the hated C/C++ expression, that so many happen to have issues with. Which I can gladly point you to. Given that security is one of my areas, I always keep up to date with C and C++ standards, even if I don't use them every day.
- c-cube 6y ago> C/C++ isn't a programming language, rather the abreviation for C and C++. Maybe the abbreviation should be `C & C++` then :-). But I'm not sure if infix precedences allow it to work.
- BenFrantzDale 6y agoI know the history, but I still think your comment was lumping them together unfairly in this context. With few exceptions, if you are writing `new` or `delete` you are doing C++ wrong in 2021 and that I would call C/C++ :-)
- pjmlp 6y agoIt wasn´t my comment, rather OP's quote. As for not using new and delete, I kind of agree, then again the only C++ GUI worth using has it over the place. I rather see all those school kids learning Arduino to use new and delete than malloc() error prone size calculations, or dive into template errors due to the misuse of smart pointers.
- throwaway189262 6y agoI frequently describe Go as "C, with memory safety and GC". I don't run into many that disagree. Go is basically how Java was pre-generics. People forget that early Java was much like Go in some respects. Everything casted to object. Not really sure what the object is unless you wrote the code. Lack of common patterns and a rich set of libraries (Guava, Apache Commons). Early Java was supposed to be safe C++. Converting C++ to Java is still trivial unless there's pointer craziness. Generally copy paste the C++ and change naming conventions. I hate working on unfamiliar Go projects. Copy pasted code everywhere. And everyone builds their own abstractions because there aren't enough. I don't understand Go's appeal. To me it feels clunky and stuck in the 90's. Designed by C programmers for C programmers. Like C it has no generics, bad packaging, and is a pain in the ass to cross compile. Its only killer feature, IMO, is it's fiber threading model and that's quickly being copied by other languages, even ancient Java.
- otabdeveloper4 6y ago> Converting C++ to Java is still trivial unless there's pointer craziness. Generally copy paste the C++ and change naming conventions. What? You've obviously never seen actual C++ code to make such a ridiculous statement.
- nvarsj 6y agoGoogle C++ looks a lot like Java, maybe that's where they are coming from. But it's still quite a stretch!
- otabdeveloper4 6y agoAs they say, "you can write FORTRAN in any language".
- pjmlp 6y ago1996 C++ (when Java appeared) being used in MFC, OWL, Powerplant and CSet++, alongside Smalltalk in the gang of four book, definitely.
- 6y ago
- grey-area 6y agoGreat summary of areas where Go could be improved for real world use. I’d want any of these improvements over delete at index in a slice (which I’ve never wanted).
- simiones 6y ago> I also think Go's benefit really is that it's simple and that you have very few tools that let you do anything other than focusing on solving your problem I feel just the opposite: Go has very few tools, which forces you to often have to solve language games instead of focusing on your problem. You want to remove an element from a list? Instead of writing that, you need to iterate through the list and check if the element at position i has the properties you want, and if it does, copy the list from i+1 (if it wasn't the last!) over the original list. This is NOT the problem you were trying to solve. You want to store a map from some struct to another? Instead of writing that, you have to create a key struct that is comparable for equality from your original struct, which probably involves string concatenation and great care to avoid accidentally making unequal structs have equal keys; and then two maps, one from keys to the key struct and another from keys to the value struct, and of course client code also has to know about this explicitly. You want to pass around some large structs, or iterate over slices of them? Well, you'd better start using pointers, and start being careful about what is a copy and what isn't, otherwise you'll pay a steep performance price, without any syntactic indication. You want to work with sets of values? You'll have to store them as map keys, perhaps doing all of the black voodoo described above. And of course, you can't do a reunion of two sets, you have to iterate through the keys of the first map and the second map and add them to a third map. All of these things are annoying when you are writing them, and even more of a problem when you are reading the code, as you have to understand the intent from the messy internals that Go forces you to expose.