I am launching my startup on Typescript (backend and frontend). So far, it's been an amazing experience - I can define the problem in terms of types even before I start writing any executable code, and refactoring is a breeze if you make your types hard enough. There are mature libraries, you can share code between the two sides, infinite options for PaaS providers who can "just deploy" a typescript codebase. As a solo dev, it's saving me an incredible amount of time to go to market.
I've had experience with large codebases in Python, Java, Go and Javascript before.
Python - as everyone already said, just sucks. Refactoring is a nightmare, you have to test every single line of code for any sort of dependability and that makes refactoring even harder. It allows for too much meta-programming and within 3-6 months your codebase is legacy.
Java is just not fun to write for me, but Kotlin is kinda fun and I could see myself choosing that. The problem is that my product deals with a lot of deeply nested, user-defined objects, and converting JSON (or YAML, or XML) to POJOs is a verbosity nightmare.
I just fucking really hate Go. I can't explain it, but I find the language infuriating. It somehow manages to be less expressive, more verbose and more straight up boring than all the other options.
> I can't explain it, but I find the language infuriating. It somehow manages to be less expressive, more verbose and more straight up boring than all the other options.
I sort of get it. I think people fixate too much on the for loops and the `if err != nil` boilerplate, but there's definitely some validity with respect to "how to properly annotate errors" and emulating enums (in the Rust sense of the word) is pretty error prone and it still doesn't get you exhaustive pattern matching.
The stuff I like about Go:
* Single static, native binaries - being able to just send someone an executable is pretty nice, not needing to make sure people have the right libs and runtime installed is phenomenal.
* Great tooling. I love that the Go tool takes a list of dependencies. Unlike Gradle/CMake/etc I don't have to script my own build tool in a crumby/slow imperative DSL.
* Compilation speed. Go compiles really fast.
* Small language, easy learning curve. Any programmer can read just about any Go project with very little experience. Any Go programmer can jump into any other Go project and be productive immediately (no extensive configuration of IDE or build tools or anything like that). You can also write surprisingly abstract code without reaching for generics, but you will likely have to change your programming habits.
* Value types done right. Most other mainstream languages don't have them, and the ones that do very often implement them poorly (e.g., IIRC in C# you can define a type as a struct or a class and only structs can be value types, and classes are more idiomatic).
* Go eschews inheritance and prefers data-oriented programming over object-oriented programming (although OOP is poorly defined and some people think it means "anything with dot-method syntax").
* I'm just more productive in Go than any other language
Stuff I don't like about Go:
* No enums
* I wish there were fewer tradeoffs between abstraction and performance
* Needs a better system for annotating errors
* Doesn't run on embedded systems (although I've heard good things about TinyGo)
* Doesn't make me feel as clever (this is a nice property for professional software development, but for hobbies not so much)
On balance, I think Go is the better language, but I can also understand why it chafes people. Different strokes. :)
I am still fairly early in my career. I've worked mostly in Java, but my current job is a mix of Python for E2E testing, PHP and JS for application work. Go is my preferred language for tools.
My favorite language is by far Java with Go a close second. I dislike the others. The only thing I wish Go had is more functional support, but only in the style of Java's fluent API. I strongly dislike PHP, JS, and Python functional style and would almost prefer they not exist.
> I can define the problem in terms of types even before I start writing any executable code
This is how code is supposed to be written. Sort out the logic first, don't write the actual implementation details until later (models, http handlers, file parsers).
The support for this type of SOLID design is much more common in Go than in Typescript projects which often seem to inherit bad practices from Javascript developers.
> I can't explain it, but I find the language infuriating. It somehow manages to be less expressive, more verbose and more straight up boring than all the other options.
I don't hate it like you, I just find it amazingly backwards, and I think I can explain why:
The community has the same persistence as certain teachers when it comes to locking things down and making things harder than necessary for no good reason, all for our own good.
Prime example: announcing a supposedly cutting edge language without generics in 2009, launching version 1.0 without it in 2012, 8 years after notoriously conservative Java got it, then waiting until 2022 before listening, that is some hardcore teacher mentality IMO.
Yeah, honestly I would respect them more if they never added generics. Still wouldn't use it. I haven't tried go-with-generics but I can't imagine it can be a good addition so late in the game.
If you like doing compile time things with types like typescript enables then Nim is fantastic. I've been learning Rust recently and I'm continually disappointed with how little compile time or type based stuff you can do.
With prior C++ experience it's easy to equate C++ templates and Rust generics (similar syntax - usually does similar things - quack like ducks etc.); I know I did. And through this lens generics will soon seem very limited compared to templates. But the underlying reason for this is because C++ templates aren't generics - they're much closer semantically to hygienic, declarative macros. C++ templates are based around rules like SFINAE and allowing multiple alternatives and the compiler will work out which is the most-specific match that works. C++ templates (even with concepts) are unconstricted - when you give a template a type, there aren't any checks enforced by the language about the types; many templates have a list of requirements and you, the programmer, have to check them, unless the template itself has guards and checks the types.
Through this lens, the semantic equivalent of C++ templates in Rust isn't generics, but macro_rules! - but for most uses of C++ templates you would rather use generics and traits.
Yah the type level programming in Rust really is limited to traits which provide little compile time functionality. It's pretty straight jacketed which is nice in some ways as you wont have un-anticipated interactions with user types. On the downside that limits un-anticipated usage of libraries or types.
Even macro_rules! aren't as capable as modern C++ templates with types. I am let down with how lacking Rust is when doing compile time type based programming. There's no CTTI or RTTI in Rust. The procedural macros don't have access to type information, just syntax. The constexpr equivalents are so limited, etc.
Yep, true. Stuff like Zig's famous MultiArrayList, where you write pretty simple Zig code to take apart and reify types and functions just doesn't seem to be much of a thing for Rust by design. Procedural macros also have build time issues, because the macro must be fully compiled before any code using it can be compiled (hence why they must be put in a separate crate), and as I understand it procedural macros only get TokenStreams, so if they want to work with Rust code, they must re-create the AST from tokens with their own parser. Unfortunately, derive macros also happen to be procedural and subject to those limitations, handy as they may be.
What I really like about Rust generics on the other hand is that, while limited in many ways, those limitations allow them to be complete in the sense that as far as types concerned, the generic signature is the law of the land; it'll compile with any type fulfilling the trait bounds, and the implementation behind the signature is fully limited to exactly what's provided by the trait bounds.
Look at Kotlin if you haven't. Sure, "it's just Java" but really it's not. A good example is the collections all have an astoundingly uniform API and everything can map into everything else. It's really quite enjoyable.
What don't you like about nodejs? seems like a solid choice for most things, the only use one language (nodejs/browser) is very attractive (besides SQL).
I've had experience with large codebases in Python, Java, Go and Javascript before.
Python - as everyone already said, just sucks. Refactoring is a nightmare, you have to test every single line of code for any sort of dependability and that makes refactoring even harder. It allows for too much meta-programming and within 3-6 months your codebase is legacy.
Java is just not fun to write for me, but Kotlin is kinda fun and I could see myself choosing that. The problem is that my product deals with a lot of deeply nested, user-defined objects, and converting JSON (or YAML, or XML) to POJOs is a verbosity nightmare.
I just fucking really hate Go. I can't explain it, but I find the language infuriating. It somehow manages to be less expressive, more verbose and more straight up boring than all the other options.