Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Golang is such a elegant language.

But comparing it to JavaScript isn't fair. JavaScript has paid my bills for years, but it's held together by collective hope.

The only thing missing is a decent mobile framework. I'm using Fyne, but it just looks dated. At least for my current app it's functional though.



Eh, I still don't get it.

Go seems too high level for low level work - use Rust, manage your own memory, no garbage collector.

Go also seems too low level for high level work - use TypeScript with all the nifty ES6 features, powerful type system, exceptions, etc..

Where does Go fit in here?


If Node/Typescript isn't performant enough, you need to move a lot of bytes around, but training a developer team on Rust seems like a massive organizational expenditure.

Go is probably the most efficient language for 0 -> Production. The standard library has everything you need to build a production backend service. There's zero build system shenanigans. Anyone who's seen a C-like language can start writing mediocre code today, and be pretty well off in 2 weeks.

So far at Notion we're solving all our problems with Typescript/NodeJS, but I'm currently working on a distributed system with Consul that needs to move a lot of bytes in and out of files in somewhat complicated ways, and boy howdy am I feeling the painful performance ceiling of single-core NodeJS, and I'm sure if I sat down to rewrite the performance sensitive part in Go, I'd be done in a few days and it'll do 10x the throughput of the NodeJS service with the same resources.


I mostly agree but... you can't use worker threads or something with Node to distribute the work? It's only like one line to submit a job to a worker thread, how is that much more than "go thing()"?


There’s tools for parallel execution in Javascript like Worker or node:worker_threads but they have two big drawbacks that make them somewhere between annoying and useless:

1. No shared objects between threads. You can share non-resizable contiguous byte arrays (SharedArrayBuffer) but 98% of existing code makes normal objects and arrays, and if you want to send those to another thread, you pay a serialization memcopy round trip (no cast a buffer in this language). This severely limits threading to “shared nothing” style workloads. Can you pass a node HTTP request to another thread? No :(

2. Each thread worker needs to boot up from scratch from its own entry point file. This forces some pretty weird code layout and imposes a big boilerplate overhead as well as runtime overhead. And remember - no sharing! So if your threads need a common resource like a Postgres connection pool, they’re going to create their own copy.


Yeah I agree. I made it work for one big system but Java or Go would have been nicer lol. Luckily I was able to just:

2. You can do this w/o restarting worker each time, which helps. Just keep it alive and submit work to it.

and then coordinate state in parent worker.


Node is single threaded.

No more needs to be said after that.


onboarding 10 Typescript Backend developers is like onboarding 10 developers from entirely different languages. Some are _heavily_ OOP driven, and turn literally everything into a class. Some are heavy on functional programming and start using curried functions everywhere. Others are used to classic express servers, while another group has only ever worked with graphql/prisma, or has only deployed on lambda functions and hasn't really seen express-based routing.

Literally everyone comes with their own project setting, they all have to get used to that specific folder structure, or those eslint/prettier settings. And on top of error-handling in JS/TS is miles behind Go's (and that's despite Go's error handling also being clunky and not as elegant as Rust's or ocaml's, but still much much better than JS's). It is _extremely_ easy to mess up a typescript project, it's significantly harder to mess up a go project (although obviously still easily possible :)

Btw. I also don't hate typescript/JS, I think it's a great language that allows for a big variety of expressiveness in entirely different programming domains, I personally use it all the time and enjoy it. I just don't think it's a particularly great language to scale a team with.


Go fits into when you don't want to comb through 50-line stack traces that exclusively reference nested dependency after nested dependency.


Go fits in well in the backend where Java would have been, but with a better stdlib, simpler tooling and a smaller deployment footprint.

IMHO it's great for docker-based services. And that's a pretty big marketplace.


Rust is also really really hard.

A lot of this is just syntax, but I've just about come to the conclusion that I'm too stupid to learn it.

Golang is very easy, I can generate small binaries to do cool things without too much code.

Typescript is dragged down by the legacy of JavaScript, things randomly break all the time, configuring babel is the stuff of nightmares.


I felt the same way about rust until I started working on https://google.github.io/comprehensive-rust/ and in a couple days have wrote several working rust programs.(trivial ones)

It took me from a couple years of "I should learn rust" to "I've written some rust and ran rust programs" in a few hours.


> Go also seems too low level for high level work - use TypeScript with all the nifty ES6 features, powerful type system, exceptions, etc..

You can solve most problems with if-else and loops. This wasn't something I was aware of before Go, but now I see how simple it is and can be.

It strips the problem domain down to its core because you're forced to express the solution in the simplest form it can be. I know a lot of Go haters throw vitriol for exactly this reason (see fasterthanli.me/articles/lies-we-tell-ourselves-to-keep-using-golang), but the truth is simplicity really gets you 80% of the way and most of the time that's enough.


Go makes error handling explcit, which is a very important part of development. Not only this makes you more conscious on thinking what you need to do when something goes wrong, but also makes codes more maintainable in my opinion.

I strongly prefer go error handling compared to a throws-type-error-handling language.

Also, with this comment I hope to get some pushback: I haven't kept up with the latest typescript, python or any other language features. I'm talking from almost a purely ignorant perspective so I hope to learn a bit more on how developing with other languages feels like.


> Also, with this comment I hope to get some pushback: I haven't kept up with the latest typescript, python or any other language features. I'm talking from almost a purely ignorant perspective so I hope to learn a bit more on how developing with other languages feels like.

Can't push back there - every other language I'm aware of uses at least one (and often both) of "throwing exceptions" or "returning Result types which either contain your actual data, or an Error", both of which let you just write your logic and wrap it in a single handler rather than repeating `if err != null return _, err` everywhere (or if you _want_ to handle each error individually, you can!)

I've gradually reached the conclusion that Gopher's really just do prefer GoLang's verbose repetitive approach. And, y'know what - good luck to y'all. It's not for me, but I'm trying to get better at just letting people enjoy things :)


how does that work with try/catch? try/catch is significantly more verbose than just if err != nil // do something imo, and also much more brittle.

Agree re: Results type in Rust and Ocaml, etc. Those are better in my view too. And yes, you can define a Result<any> return type in Typescript as well (and in fact that's what I mostly when I write typescript and works ok) but unlike Rust this is definitely not 'idiomatic typescript/js' and other developers who might not be familiar with Result types will probably initially dislike and then probably dismiss it.


> try/catch is significantly more verbose than just if err != nil // do something imo...

Further to what the other replier said (about the ability to bubble-up errors), try-catch also lets you handle multiple errors in one block:

``` try { fileOutput1 = getSomethingFromFileSystem() fileOutput2 = getSomethingElseFromFileSystem() fileOutput3 = ... } catch (FileSystemException e) { // handle } ```

If I understand it correctly, GoLang's idiom would claim that this is a bad thing to do, and each error should be handled individually. Which - sure! That's _usually_ a reasonable, defensible, and safe position. But that means that GoLang's approach is always as verbose as its possible to be, whereas try/catch at least has the _possibility_ to condense handling.

> ...and also much more brittle

Can you be specific about what you mean by "brittle"? To me, it denotes a lack of flexibility - that is, if thing1 changes in an unexpected-but-still-legal way, then thing2 is likely to break. I can't see how that applies to try/catch-vs err-check - in both cases:

* The exception/error is bound to a variable

* (in most well-typed languages) the Type of the exception is checked by the type system, and/or (in every language, inc. GoLang) properties of the exception are checked by code

* Something is done (a standard code action, a return/throw of an exception, or a program termination)

You can write a brittle GoLang check (only checking for, say, `if e.message = "a very specific error message"`), and you can write a very flexible try/catch block (with a fallback `catch (Exception e) {doSomethingGeneric()}` - or, indeed, the _most_ flexible "try-catch" is "don't even catch it, let it bubble-up and let your framework/application handle it")


There is no need to have try-catch at every function invocation. One can do this only at the level at which one needs to handle the error.

In Go, every call made to a function is 5 statements and lines. Go code tends to bloat up the screen quite a bit and eyes glaze over.

    result, err := f()
    if err != nil {
      return nil, err // I don't want to handle this here but at callers.caller.
    }
    return result


> Also, with this comment I hope to get some pushback

More of a push forward, really: if error-handling guarantees are what's driving you away from dynamically typed langauges, Go is pretty much the worst place you can land that isn't C. It doesn't make you check nils, it doesn't remind you to check error values from functions that you call only for side effects (though the linter will, admittedly), and it doesn't have sum types so there's semantic ambiguity even in the common case - that is, in `data, err := fn()`, it's common to assume that at most one, and perhaps exactly one, of `data` and `err` will end up non-nil, but that's not a constraint you can express with the type system.


I agree with not being able to rule out nil checks, I just realized how arbitrary I am with nil checks, else it can get very nil-check bloated in some common scenarios. However the other two haven't been an issue for me so far.

I'd love to have the chance to explore the nuance of what other tradeoffs include going with any other language, but certainly requires more nuance than a deep comment response might trigger.

But just trying my luck, what do you think is worth trading off the more exhaustive error handling? (Regardless on dynamically typed or not)


I look at Go like Python + multicore world. (and nice to have speed from compilation vs JIT). And in my career that's almost exactly what we've used it for: rewriting higher load services from Python (2.7 at the time) to Go.


> Where does Go fit in here?

Where you move past academic language discussion and start using the tooling. Typescript is a pretty nice language but the tooling around it is practically unusable. It's laughable how bad it is. Outside of browser work, you're going to pick Go – and still would even if they made the language 10x more flawed – over Typescript every time just to not have to deal with that ecosystem.

Granted, people are trying to make it better. Dahl going on his Go kick and wanting to copy its lessons in the Typescript world via Deno has lit a fire, but there is still a lot of work to do.


> Typescript is a pretty nice language but the tooling around it is practically unusable.

I work mainly with node/ts and totally agree, maybe just add that by tooling it is whole ecosystem as well. This problem is not visible if you work either with relatively small code base or new code base. But as soon as you have something old and big you'll see where the pain comes from.


Can you elaborate on what's hard about TS tooling? It's really easy to just use `npx tsc`


Probably not. I live in a parallel universe where `npx tsc` does nothing except spit out available arguments. I can first `npx tsc --init`, after which `npx tsc` converts the TS files into corresponding JS files, but that puts you no further ahead. You still need tooling to do anything with those files. In a universe where `npx tsc` knows what tool you need every time you run it – something completely incomprehensible in this universe – it is undoubtedly also impossible for those in that understand what we go through in this one.


What in practice causes the most pain for mé are the various module formats in combination with TS. Just getting my test runner (Mocha) and Node and the bundler and... to work with TS and the chosen module format is always _not_ fun. Combined with package updates that break the current working solution because they now natively support es modules. I hope these problems will all disappear in the future, but I'm somewhat sceptical. And TS is slow - not C++, Haskell and Rust slow, but still. But I never used TS/Node for anything big (backendy), but just small frontends and VS Code extensions, where the time of getting everything set-up to work takes a relatively larger part of the "actual" work.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: