Live data from Hacker News

Why Go Is Not Good (2014)

yager.io

451–460 of 466 posts

Re: Why Go Is Not Good (2014)

#451
post #61

Earlier quoted context omitted.

+1 on this. Clojure's core.async[0] is the perfect example of an implementation of CSP as a library. Even JS can be used to implement such concepts via the use of generators[1]. [0] https://github.com/clojure/core.async [1] https://github.com/ubolonton/js-csp

I used to feel this way until I realized the limitations of core.async. In Go I don't have to worry about whether the particular functions I'm calling, especially IO-related, are blocking or not, as Go will create new lightweight goroutines as necessary to deal with all that. With core.async, if I use blocking IO inside of a coroutine I risk causing thread starvation. See http://martintrojer.github.io/clojure/2013/07…

Small remark: Go I/O layer is safe to use only with network I/O. Only network I/O plays nice with goroutines.

File I/O or everything else treated as syscall by Go runtime might turn you program into 10k-os-threads-monster. Scheduler will be creating new OS threads to replace those locked on syscalls until thread limit is reached and whole program crashes. Only way to prevent it is to restrict your syscall layer into fixed-size goroutine pool.

I had an interesting case recently - my app serves some data from tons of files laying in NAS, accessing it by NFS mount and one day NAS hunged completely, every I/O call to it was lasting forever. Even 'ls /mount-point-of-nas' was just doing nothing forever until Ctrl-C. In my case I've applied poweroff-poweron cycle to NAS, and everything went right in minutes, just as NAS booted. And after it I wondered, what if my server was written in Go, instead of Erlang...

And, BTW, you can never be sure, that underlying libraries of your code are safe to use.

Re: Why Go Is Not Good (2014)

#452
post #449

Earlier quoted context omitted.

You don't need sum types, Result / Optional etc can be implemented in a language without them. I can implement those in TypeScript too. Its a fast compiler, has a type system, and it supports generics.

Sum types are how Result/Option/Maybe/Either are typically implemented. The type system guarantees that inhabitants of the type contain exactly one success value xor one error value, and that accessing the value requires handling both cases, which is checked at compile time.

Sure, but you don't necessarily need sum types for Either. Its more tedious, but totally doable without them, e.g. just with higher order functions:

  let left  = a => (l, r) => l(a)
  let right = b => (l, r) => r(b)

its now impossible to consume result without passing functions to handle both the left and the right value.

  let mapRight = f => val => val(identity, right => right(f(val)))

Re: Why Go Is Not Good (2014)

#453
post #447
post #445

Earlier quoted context omitted.

> I don't know what this statement means. The Go scheduler certainly runs on different threads. So what. It means that some of the data structures the scheduler examines on every run are shared data, with locking. That makes it effectively single threaded, even if it technically runs on different CPUs (at different times). Put another way it means that it'll never run faster than a single-threaded scheduler would. >…

> It means that some of the data structures the scheduler examines on every run are shared data, with locking. This is true for every scheduler, not only for the Go scheduler. > That makes it effectively single threaded It would limit parallelism to one, if there was a single lock. This used to be the case, but now the locking is more finely grained. But this only matters if there's lock-contention anyway, which is n…

I see we mostly agree, but one thing here is a glaring error :

> It has the good parts of both cooperative and preemptive scheduling, but yes, it's certainly cooperative.

For me, the best part of cooperative scheduling is that you can work entirely without locking shared data structures, because you get "transactions" for free. This means it's rather difficult to get data races, deadlocks, etc. Go's scheduler certainly does not give you that, trades it for spreading work over different cpus.

So it has problems:

1) necessity of locking, using IPC mechanisms, ... (like preemptive schedulers, and let's face facts here : channels aren't enough in real world apps)

2) everything gets blocked by large calculations (like cooperative schedulers)

3) more generally, easy to crash by misbehaving thread due to unrestricted access shared resources (not just cpu) (like cooperative schedulers)

And advantages:

1) Actually uses multiple cpu's/cores/... (like preemptive schedulers)

2) integrated event loop that scales (like cooperative schedulers)

If you want to see a programming language with a "scheduler" that doesn't have the bad parts of cooperative schedulers, check out Erlang. If you attempt to crash erlang with infinite loops, bad memory allocation, ... (on a properly configured system) that just won't work, the offending threads/"goroutines" crash leaving the rest of your program running fine. The offending threads will restart if you configure them to do so (which is really easy).

The same can be achieved, with much more work, on the JVM, or, also with much more work, with python's "multiprocessing" library, part of the standard library.

> > On the linux scheduler with proper ulimits it's bloody hard to crash the system, for instance, forkbombs, memory bombs, ... won't do it. I hope we'll get a language where you can do that too. > I don't understand the analogy. It is not clear what "crash" means here, and it is not clear how it would apply to a runtime environment. All that stuff, forkbombs, etc, means that you can configure the system so arbitrary code can't affect the system in those particular ways.

Crash means that the system/"program" doesn't respond (in a useful manner) anymore.

Re: Why Go Is Not Good (2014)

#454
post #409

Earlier quoted context omitted.

Once you get data races in Go, you realize that immutable data structures are indeed the right way. Too bad though. That non-threadsafe, racy map is your only generic data structure. I think that critics are generally cutting way too much slack to Go. Its a horrible language - with a decent library and excellent tooling and documentation, but still quite horrible.

> you realize that immutable data structures are indeed the right way Well, that's false. Rust has mutable data structures but also statically prevents data races.

Do you mean this?

https://doc.rust-lang.org/nomicon/races.html

If that then sure, that might work too if you really need to do it. I wouldn't go so far as to say its the right way - it seems very bothersome to me.

Its more of a "yes, I'm willing to go through all this incredible pain, because I get some gain for it (constrained hardware? idk). Rust, please help me do it right."

Of course you can't do that in Go. Not only you don't get static guarantees, you can't even write a generic map with atomic access.

edit: by pain I mean this: https://doc.rust-lang.org/book/concurrency.html - and yes, thats painful compared to using immutable data structures.

Re: Why Go Is Not Good (2014)

#455
post #454

Earlier quoted context omitted.

> you realize that immutable data structures are indeed the right way Well, that's false. Rust has mutable data structures but also statically prevents data races.

Do you mean this? https://doc.rust-lang.org/nomicon/races.html If that then sure, that might work too if you really need to do it. I wouldn't go so far as to say its the right way - it seems very bothersome to me. Its more of a "yes, I'm willing to go through all this incredible pain, because I get some gain for it (constrained hardware? idk). Rust, please help me do it right." Of course you can't do that in Go. Not…

> If that then sure, that might work too if you really need to do it. I wouldn't go so far as to say its the right way - it seems very bothersome to me.

There are plenty of bothersome things about "immutable only" too.

I've employed both approaches in earnest. Each have their own set of trade offs.

> Of course you can't do that in Go. Not only you don't get static guarantees, you can't even write a generic map with atomic access. You have to remember to use RWMutex every single time. No generics.

I'm quite aware of Go's limitations, thanks.

I find it amusing that you've dismissed an entire category of practice to statically eliminating data races at barely a glance. Irony, it seems. The very thing that people lament about Gophers is precisely the behavior you've demonstrated here! (Quite literally in fact. How many gophers have you heard say something like "generics is bothersome"?)

You've been polite, but snobbery is vexing, no matter where it comes from.

Re: Why Go Is Not Good (2014)

#456
post #452

Earlier quoted context omitted.

Sum types are how Result/Option/Maybe/Either are typically implemented. The type system guarantees that inhabitants of the type contain exactly one success value xor one error value, and that accessing the value requires handling both cases, which is checked at compile time.

Sure, but you don't necessarily need sum types for Either. Its more tedious, but totally doable without them, e.g. just with higher order functions: let left = a => (l, r) => l(a) let right = b => (l, r) => r(b) its now impossible to consume result without passing functions to handle both the left and the right value. let mapRight = f => val => val(identity, right => right(f(val)))

Indeed, but now you're just being academic. Such an approach is quite bothersome. I maintain my initial criticism of your suggestion.

It's easy to be an armchair designer of programming languages. It's quite a bit more difficult to be in the driver's seat, because you have to answer the hard questions; you can't just throw feature sets against an HN comment wall and see what sticks.

Re: Why Go Is Not Good (2014)

#457
post #412
post #388

Earlier quoted context omitted.

As was mentioned before, you can absolutely do the same thing in Go that you see in lodash by giving up type-safety which javascript doesn't have anyway!.

Or you don't give up type safety. TypeScript compiles really fast and has generics.

I'm not familiar with TypeScript, but since it compiles to Javascript, I imagine it's using type erasure. Indeed, such an implementation of generics is easy to compile quickly.

If one adopted such a scheme in Go (which would also be fast to compile), you'd end up sacrificing a great deal of runtime performance, because everything generic would be forced behind a box. Such a thing is no big deal in a language like Javascript, but for a language like Go that aims to be fastish, it's probably a non-starter.

Finding a compiler that monomorphizes generics and is also as fast as Go is probably a much harder challenge. I've had some folks claim that one of D's compilers is pretty fast, but I haven't seen any good benchmarks to support that claim. Many of the other monomorphizing implementations I've tried (Ocaml, ML, Haskell, Rust) are all pretty slow. At least, much slower than Go's compiler in my own experience.

Re: Why Go Is Not Good (2014)

#458
post #454

Earlier quoted context omitted.

Do you mean this? https://doc.rust-lang.org/nomicon/races.html If that then sure, that might work too if you really need to do it. I wouldn't go so far as to say its the right way - it seems very bothersome to me. Its more of a "yes, I'm willing to go through all this incredible pain, because I get some gain for it (constrained hardware? idk). Rust, please help me do it right." Of course you can't do that in Go. Not…

> If that then sure, that might work too if you really need to do it. I wouldn't go so far as to say its the right way - it seems very bothersome to me. There are plenty of bothersome things about "immutable only" too. I've employed both approaches in earnest. Each have their own set of trade offs. > Of course you can't do that in Go. Not only you don't get static guarantees, you can't even write a generic map with a…

Fair enough. I admit to not knowing when you would prefer statically checked mutable data structures to immutable ones except for a few cases (dynamic programming arrays, fast matrix libraries, memory constrained environments).

I did use the word "seems" there though. Its not really dismissal, I would indeed like to be enlightened. In projects where I can afford a GC, I'd always take the GCed option (in my case an overwhelming majority). Same for immutable data structures (use whenever they can be afforded). Are those bad heuristics? (Afforded here refers to performance/memory constraints only)

One thing that GCed languages don't solve very well is handling other more scarce resources (file handles, connections from a pool, etc). It seems that Rust managed to solve this nicely. If only it was possible to use GC for everything except those kinds of resources (perhaps it is?), that would be perfect.

Re: Why Go Is Not Good (2014)

#459
post #458

Earlier quoted context omitted.

> If that then sure, that might work too if you really need to do it. I wouldn't go so far as to say its the right way - it seems very bothersome to me. There are plenty of bothersome things about "immutable only" too. I've employed both approaches in earnest. Each have their own set of trade offs. > Of course you can't do that in Go. Not only you don't get static guarantees, you can't even write a generic map with a…

Fair enough. I admit to not knowing when you would prefer statically checked mutable data structures to immutable ones except for a few cases (dynamic programming arrays, fast matrix libraries, memory constrained environments). I did use the word "seems" there though. Its not really dismissal, I would indeed like to be enlightened. In projects where I can afford a GC, I'd always take the GCed option (in my case an ov…

> I admit to not knowing when you would prefer statically checked mutable data structures to immutable ones except for a few cases (dynamic programming arrays, fast matrix libraries, memory constrained environments).

Those sound like pretty compelling use cases to me, and also ones that seem to be well suited for Rust. You might also consider looking at Servo; I bet its engineers could list a myriad number of reasons why immutable-only data structures are insufficient.

I note that performance is not the only trade off worth examining (to be fair, I think you acknowledged this). Another aspect of the trade off is abstraction, albeit this is fuzzier. Mutation can be more natural to a lot of folks. My pet theory is that we've built up a defense mechanism against mutation because it's the source of so many bugs; but Rust's static guarantees are worth consideration here. They remove many of the problems normally ascribed to mutability. For example, Rust not only prevents data races, but it also prevents aliasing mutable pointers to data at compile time, which defeats another class of bugs not related to concurrency at all.

My main point of contention with your comments is that you think you've stumbled on to the "right" way of doing something. In my opinion, that's nonsense. What's the point, even, to declare such a thing? Instead, focus on what the trade offs are, then make a decision based on the constraints you've imposed in any given situation. (Valid constraints absolutely include "immutable data structures are easier for me to reason about intuitively.")

> In projects where I can afford a GC, I'd always take the GCed option (in my case an overwhelming majority). Same for immutable data structures (use whenever they can be afforded). Are those bad heuristics? (Afforded here refers to performance/memory constraints only)

They don't seem like bad heuristics to me. They don't really correspond to my own heuristics, depending on what problem I'm trying to solve. (I once chose a language for a project based purely on the fact that I wanted to target non-programmers.)

Re: Why Go Is Not Good (2014)

#460
post #458

Earlier quoted context omitted.

Fair enough. I admit to not knowing when you would prefer statically checked mutable data structures to immutable ones except for a few cases (dynamic programming arrays, fast matrix libraries, memory constrained environments). I did use the word "seems" there though. Its not really dismissal, I would indeed like to be enlightened. In projects where I can afford a GC, I'd always take the GCed option (in my case an ov…

> I admit to not knowing when you would prefer statically checked mutable data structures to immutable ones except for a few cases (dynamic programming arrays, fast matrix libraries, memory constrained environments). Those sound like pretty compelling use cases to me, and also ones that seem to be well suited for Rust. You might also consider looking at Servo; I bet its engineers could list a myriad number of reasons…

Its not just that shared mutable state is hard, I'm thinking of the whole reasoning apparatus you get at your disposal:

http://www.haskellforall.com/2013/12/equational-reasoning.ht...

That indeed seems very much like something that can be called the "right way". If all functions in a given subset of the code are pure I can even imagine a tool that combines hoogle with your function's type signature and existing types to suggest how to finish writing your function (its just a graph search with nodes being the types and functions as the links). edit: seems like I don't need to imagine it - https://github.com/lspitzner/exference

Rust's way seems to me like they encode all the tediousness of dealing with shared mutable state into the type system. This is good, I guess, if you need to keep doing what you've always been doing but in a much safer way.

Post reply on HN