Erlang is well-researched, pragmatical language (read the Armstrong Thesis at last) this is why it is "great". User-level runtime, which is trying to do kernel's job is a problem, but relying on message-passing and being mostly-functional it, at least, have no threading/locking problems - so it scales. Nothing much to talk about.
Go is also well-researched language with emphasis on keeping it simple and being good-enough and doing it right way (utf8 vs. other encodings) - a philosophy from Plan 9. Go has in it a lot of fine ideas, attention to details and good-enough minimalism - the basis for success. It is also pragmatic - that is why it is imperative and "simply" static-typed.
Criticism about lack of generics or is not essential, especially considering that generics in a static typed language is an awkward mess. Complexity of its user-space runtime is a problem, of course, but runtime is hard, especially when it is not mostly-functional.
Go is in some sense "back to the basics/essentials" approach, not just in programming but also in running the code, and even this is enough to be successful.
BTW, its syntactic clumsiness and shortcomings (hipsters are blogging about) came from being statically typed, just admit it. On the other side, being C-like and already having C-to-Go translators opens up the road to static-analyzing and other tools.
Go is the product of old-school (Bell labs) minds (like Smalltalk or Lisps) not of bunch of punks.)
> Criticism about lack of generics or is not essential, especially considering that generics in a static typed language is an awkward mess.
This is very wrong. Sure, C++ templates are a mess, but generics in a language with a well-designed type system, like Haskell or Rust, are incredibly useful and very elegant, especially in combination with type classes/traits.
Java is a much bigger exception than Haskell, a heavily runtime-checked language with significant established codebases in which generics were retrofit with the foremost goal to minimise their backwards-compatibility impact. Witness C#, a very similar language whose team decided they had a low enough userbase yet that they could afford to break BC and introduce separate reified generic collections[0].
And just about every other language (Haskell very much included) was designed with generics from the start.
Haskell is not an exception, Java is.
[0] the C# team knew all along the language would have generics, but generics were not ready for 1.0 so they were pushed back to 2.0
A few corrections: The foremost goal when retrofitting generics into Java may have been backwards compatibility, but the requirement that drove the designers to type erasure and the clunky implementation was migration compatibility.
C# had generics retrofitted and kept perfect backwards compatibility. You can still run C# 1.0 programs on a modern 4.0 runtime. That is backwards compatibility, and it would have been quite possible to fit Java generics the same way.
Migration compatibility is a (in hindsight) rather absurd requirement where old (non generic-aware) code should be able to call new (generic-aware) code where you as the integrator have control over the source code of neither the "old" nor the "new" code.
In practice this would only happen when you use an old library that has taken direct dependencies on external (to the library) non-generic classes (i.e. not interfaces), and where you will not accept that the old code keeps calling the old classes.
C# retained the old collection classes, implemented new generics-aware collection classes that still supported the old interfaces (with proper type checking and -casting). This meant that old code could still be passed references to new generic collections and work as expected.
The only constraint in C# is that is "old" code has taken a strong dependency on an old non-generic class you cannot pass a new generic class to that code.
A common theme is that only languages that had generics retrofitted into them have awkward implementations (because your design space is now constrained), while those that were designed to have them ab initio typically work fine.
I don't really want to belabor the point of Go and generics; it's one of the many simplicity vs. expressiveness design choices that language designers have to make. But the claim that parametric polymorphism in a statically typed language is (presumably inherently) an awkward mess is a peculiar one, given that there are so many successful examples.
Saying that statically typed languages cannot be expressive is pure FUD. Go designers just didn't strike the right balance between features and minimalism, because they didn't care. Without its concurrency model, Go is not much.
Take a look at OCaml, or any ML variant language (aside from Rust, which is more C++ than ML in my opinion anyway) for that matter, for a static typed language with a good implementation of generic types.
I think you're comparing Rust to a language with ad-hoc polymorphism like C++ (where you pay for the somewhat simpler signatures with confusing template instantiation errors), and I'm very glad we didn't follow the C++ route. Rust's generics are very similar to those of Haskell.
The only major generics-related issue that is getting some significant thought is the ability to have the automatically derive the return type on top-level functions, which is not something that is common in other statically typed languages (only C++ and those with whole program type inference, which has its own set of drawbacks).
Go's error handling leaves something to be desired. I find myself reinventing exceptions a lot by just passing errors up through generic "error" return types.
It feels like it would be a lot cleaner to add syntax support for exceptions and call it a day.
There is support for fully "exceptional" behavior. It's called "panic".
The Go language designers explicitly didn't include exceptions because "coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional" [1].
I agree with them. While handling errors everywhere is a little painful to write, it enforces better practices by making you acknowledge that things could fail and ignore, handle, or pass the buck.
> I agree with them. While handling errors everywhere is a little painful to write, it enforces better practices by making you acknowledge that things could fail and ignore, handle, or pass the buck.
There are better ways of accomplishing the same thing though, one way is the Either monad in Haskell. Here's an example I post sometimes comparing error handling in Go to Haskell's either monad:
func failureExample()(*http.Response) {
// 1st get, do nothing if success else print exception and exit
response, err := http.Get("http://httpbin.org/status/200")
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
} else {
defer response.Body.Close()
}
// 2nd get, do nothing if success else print exception and exit
response2, err := http.Get("http://httpbin.org/status/200")
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
} else {
defer response2.Body.Close()
}
// 3rd get, do nothing if success else print exception and exit
response3, err := http.Get("http://httpbin.org/status/200")
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
} else {
defer response3.Body.Close()
}
// 4th get, return response if success else print exception and exit
response4, err := http.Get("http://httpbin.org/status/404")
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
} else {
defer response4.Body.Close()
}
return response4
}
func main() {
fmt.Println("A failure.")
failure := failureExample();
fmt.Println(failure);
}
The equivalent Haskell code:
failureExample :: IO (Either SomeException (Response LBS.ByteString))
failureExample = try $ do
get "http://www.httpbin.org/status/200"
get "http://www.httpbin.org/status/200"
get "http://www.httpbin.org/status/200"
get "http://www.httpbin.org/status/404"
main = failureExample >>= \case
Right r -> putStrLn $ "The successful pages status was (spoiler: it's 200!): " ++ show (r ^. responseStatus)
Left e -> putStrLn ("error: " ++ show e)
I took a survey of my Go code base once. In the mature bits of code, approximately 1/3rd of the places where an error was received, something other than simply returning it was done. And I don't really go in for crazy wrapping schemes, either... it means something was logged, or retried, or modified, or somehow reacted to.
What often starts out as simply a return, by the time something gets to production quality, has often changed.
If you're using Go for its core use case, network servers, the error handling turns out to be very solid, precisely because almost every other error handling paradigm strongly encourages you to just lump all the errors together and not think about them individually. (Yes, that includes Option<>.) I can see where that might be very annoying on a desktop GUI app or something, but if you are not thinking about every single error in your at-scale network server, you're actually doing it wrong.
Why? Seems to me that it's precisely equivalent to Golang's error handling story (assuming you mean something like Result). In fact, it's a lot easier to handle errors individually that way, both because it tends to make it harder to use a return value without checking for errors and because its generic nature means that you can handle different errors differently without having to deal with interfaces.
If you mean that it's more typing to write "return err" and it forces you to think about it more, I don't really buy that. "return err" is 10 characters; "map" (e.g. in Haskell) is 3 and "try!()" is 6. I really doubt that the difference between 3 and 10 characters has any practical outcome. For Golang programmers, "return err" is muscle memory.
I should have said the monadic form of Option<>. When you use it monadically, the result is nearly equivalent to exception handling; you call a function and the default behavior is to bundle up the error and just throw it up. It is true that if you are manually unwrapping it every single time, it's equivalent to checking every time.
"For Golang programmers, "return err" is muscle memory."
First, as I said, no, I actually think about it every time. And second, I bet you end up with "muscle memory" default handling under any scheme (for instance, exceptions: don't catch them or rethrow them)... in the end, you can bring the horse to water but you can't make it drink. You can only feel good that at least you brought it and you did your part. Option<>, even manually unwrapped, does not force the programmer to do something sensible with the error any more than any thing else does, or indeed, can.
> User-level runtime, which is trying to do kernel's job is a problem, but being mostly-functional it, at least, have no threading/locking problems - so it scales. Nothing much to talk about.
Spoken as someone who has never really used it. Dismissing it as nothing much to talk about is ridiculous.
I'm sorry, but if go would be product of lisp minded people it would be simple language with ability to extend the language on the fly. Reality is in go there is no way to even introduce some nice contract on top of error return and if statements without external preprocessor.
Go is also well-researched language with emphasis on keeping it simple and being good-enough and doing it right way (utf8 vs. other encodings) - a philosophy from Plan 9. Go has in it a lot of fine ideas, attention to details and good-enough minimalism - the basis for success. It is also pragmatic - that is why it is imperative and "simply" static-typed.
Criticism about lack of generics or is not essential, especially considering that generics in a static typed language is an awkward mess. Complexity of its user-space runtime is a problem, of course, but runtime is hard, especially when it is not mostly-functional.
Go is in some sense "back to the basics/essentials" approach, not just in programming but also in running the code, and even this is enough to be successful.
BTW, its syntactic clumsiness and shortcomings (hipsters are blogging about) came from being statically typed, just admit it. On the other side, being C-like and already having C-to-Go translators opens up the road to static-analyzing and other tools.
Go is the product of old-school (Bell labs) minds (like Smalltalk or Lisps) not of bunch of punks.)