Go's simplicity forces developers to introduce insane amounts of complexity.
Go is definitely more verbose and less "fun" to write, but it's 10X easier to reason what's going on in a large application.
Of course, it's also the correct kind of solution for some types of problems. If you need a compiled language (many do), the competition to Go isn't Ruby and Python, it's C++ and Rust.
Actually, I feel like Zig may be most in the compiled-but-keep-it-simple-like-C headspace as Go. I don't know either Go or Zig super well. From what I do know, Zig seems not quite as stubborn as Go about forcing code to be "simple to reason about" (which is also subjective, though maybe less so than "easy to do things").
Personally, I find Swift to be a really great language, but can't deal with the Apple eco-system, XCode, raw Linux support...
Nim's stdlib is actually quite large (like 300 modules). I am sure there are things in Go's that are not in Nim's (like Go's `big`), but I am equally sure there are things in Nim's that are not in Go's (like Nim's `critbits`, `pegs`, etc).
I doubt there is much in Go's stdlib not available in Nim's nimble package system, but OTOH, I am also sure the Go ecosystem is far more vast. I just didn't want people left with the impression Nim's stdlib is as small as Zig's which it is definitely not. Nim has web server/client things and ftp/smtp clients, json and SQL parsers, etc.
I would wager jvm/clr are probably the main competition for Go at least for product/tech companies and enterprise IT.
You can write unreadable code in virtually any language but that's besides the point.
x = 1
y = 2
print(x + y)
x = "no "
y = "types"
print(x + y)
Whereas this will not even compile in Go: x := 1
y := 2
x = "no"
y = "types"I do think my example code demonstrates why Go is easier to reason at scale than Python. Python is conflating assignment and type declaration. Go has = and :=, so it's crystal clear what's going on.
Dynamic typing doesn't rule out polymorphism.
void print(PyObject objects...)
where `PyObject` is a base type.Additionally, you could perfectly well have constants, or require variables to have a fixed type, in a dynamic language. You would just pay a cost at runtime to check the type on assignment.
I like the way Rust makes clear what are the different possibilities:
In Rust we use a macro to handle variadic arguments, but if it was just a case of supporting different types, not of supporting an arbitrary number of arguments we would've had three options for the type signature:
1. Monomorphic duck typing: `fn print(arg: &impl Debug)`. Here the compiler simply generates multiple print functions, one for every type the function gets called on. The exact concrete type is known at compile time.
2. Polymorphic dynamic dispatch (with dynamic sizing) duck typing: `fn print(arg: Box<dyn<Debug>>)`. Here the compiler generates a vtable and allocates on the heap. Only a type approximation is known at compile time, not the exact concrete type, but it still counts as static typing.
3. Dynamic typing: `fn print(arg: Box<dyn<Any>>)`. Note `Any` in stead of `Debug`. Full dynamic typing with the type completely unknown at compile time. Juck! But occasionally useful for prototyping or for FFI with dynamically typed languages.
Dynamic typing doesn't rule out polymorphism.
void print(PyObject objects...)
where `PyObject` is a base type.Here's a comment of mine that explains why Python is weakly typed: https://news.ycombinator.com/item?id=26354039.
Here's a comment of mine that explains why Python is weakly typed: https://news.ycombinator.com/item?id=26354039.
1. if list(): pass # implicit coercion from collection -> None -> bool. (Very uncommon weak typing and a terrible idea.)
2. a = 123; b = 4.5; c = a + b # implicit coercion from int -> float (Common but not universal weak typing. More often hides bugs than helps with ergonomics and readability but sometimes a worthwhile tradeoff.)
3. a = 1 + false # implicit coercion from bool to int (Common weak typing in scientific languages (for masking) and older C-family languages (for bit twiddling). However, that's still bad language design. Libraries/syntax sugar should special case masking and bit twiddling. You should not have global coersion between bool and int.)
4. etc.
Crystal is a pretty solid alternative to golang.
The main example in my mind is how powerful Cobra/Viper is to create CLI apps where config can come from multiple sources - files, flags, env vars. To do the same in Rust you need to write a lot of your own code and glue together several libraries.
There's also nothing I can find for Rust that can do database migrations as nicely as Goose for go. Diesel can create its own migrations, but that's only if you're using Diesel and I prefer SQLX over an ORM and Diesel isn't async yet