Hacker News new | comments | show | ask | jobs | submit login

> Scala code can be very dense and hard to grok at the early stages of learning.

This seems to be the crux of his argument, and I think it's a rather poor one. I have used both Go & Scala professionally. Yes, it took less time for me to start writing real code in Go. However, I found Go's "simplicity" to be limiting and frustrating when it came to building production applications. Things like the weird split between functions returning errors but occasionally panicking, lack of inheritance, and poor dependency management through github links make Go a poor choice for applications within a business setting. Scala does allow for more variance in individual coding styles, but not so much that an experienced Scala programmer can't read & understand other peoples' code with relative ease. It's a non-issue during development as long as the team agrees to follow the same set of conventions, just like any other language.




It also confuses ease of reading with density of meaning. It presents the simplistic idea that the less dense statement is always the easiest to read, which might be true in isolation but it's almost always false in the larger context. It's pretty easy to prove this, simply ask yourself, would you rather read a quicksort implementation in C or assembly? C is certainly more dense in meaning, but it's also much easier to read precisely because of that denseness.

I'm not a fan of Scala by any means, I think it suffers from a lot of the same problems C++ has, but I'd still take it over Go any day. In an effort to be easy to learn Go rejects any non-trivial concepts in the language leading to a language that makes expressing complicated concepts complex and trivial concepts verbose. Go is a language predicated on the idea that C is very nearly the pinnacle of language design, if only it hadn't included those complicated pointer things, and the only thing that really held it back was insufficient tooling. It rejects modern languages as unnecessarily complicated, and instead stipulates that instead of a complex language, it's your code that should be complex.


It also confuses ease of reading with density of meaning. It presents the simplistic idea that the less dense statement is always the easiest to read, which might be true in isolation but it's almost always false in the larger context.

This is my experience with Go in a nutshell. You start out pleased by how easy it is to read the code. You end up frustrated by how much damn code you have to wade through to understand a relatively simple system.

Perhaps the fantasy is that Go forces programmers to write simple, elegant systems. In reality, of course, the effect is much smaller than hoped. Go is much easier to get right than C, so programmers have no fear of writing vast reams of code. This is not such a problem for the original author, whose understanding of the system precedes the implementation, but anyone else reading the code has to refine meaning from very meager ore.


>It also confuses ease of reading with density of meaning

He says more about that later and I find that part more interesting:

"Go code is explicit and the Scala code requires context to understand."

I believe he is saying that in order to understand a particular Scala expression you need more information from outside the immediate local context than in Go, i.e. information only available in type definitions.

I haven't thought about it enough to say whether or not I agree, but I think it is a very interesting hypothesis.

If it turns out to be true, it would support the claim that Go works better than more type centric languages whenever developers are less familiar with the codebase, irrespective of language proficiency.

I find it annoying that familiarity with the language and familiarity with the codebase keep getting conflated in these debates. They are completely seperate issues.


Just to muddy the waters a bit, you also need to add in familiarity with standard libraries and conventions. To use an example that hopefully a sufficiently large chunk of the readership is at least passingly familiar with, if you're familiar with the Java Spring framework and its conventions then understanding an annotated piece of code is usually fairly trivial. Without that understanding however it's going to be utterly confusing as none of the code would seem to bear any relation to the rest.

Programming is in many ways the act of artfully layering abstractions to concisely express a complicated concept. If you are unfamiliar with, or do not understand some of those abstractions you're obviously not going to fully understand the concept being expressed.

Part of the advantage to a strong type system is that you only need to learn a particular abstraction once. Once you learn how a particular type (or set of inter-related types) functions, anytime you see that type used from then on you immediately understand something about the system its being used in. This is in contrast to a weakly typed system where the only thing you know for sure is how the author encoded his expectation of the system at a given point (this is important, you don't know what the author intended just from the code as the author could have made a mistake). In a weakly typed system it's left as an exercise for the reader to try to divine the expected behavior from clues left in the code. This is of course not even touching on the case when the runtime behavior deviates from the expected behavior in often quite significant fashion.

Looked at from another perspective, strongly typed languages promote modularity and re-use of concepts, while weakly typed ones tend to favor explicit and single use concepts. That is, the abstractions in the strongly typed system are implicit, but often re-used across many code bases, while the abstractions in the weakly typed language will tend to be explicit and either re-used less often, or else customized for a particular use case to the extent that it's unsafe to make assumptions about the expected behavior of the abstraction.


Can't help but quote [1]:

«"Generics are not free." Creating a modern statically typed language WITHOUT generics isn't free either. Just like implicit interfaces are not free, just like the reflect package is not free, just like using interface{} somewhere isn't free, just like telling people to use code generators isn't free.»

[1]: https://news.ycombinator.com/item?id=13358631


I too use Scala and Go professionally and agree with all of your points, but I want to add one. Code reuse. I am tired of copy/pasting, rewriting the same things, forcing my same-lib packages to be unidirectionally dependent, etc.

I've often contemplated what could happen if Go had syntax-extension macros that could build new AST (not go generate, and not in the same language). I believe that Go may in turn become a quality target for a better language. Not oden which didn't have any corporate backing, and from my experiences it doesn't compile fast enough or have quality enough DCE to support reasonable JVM cross compile.


>Things like the weird split between functions returning errors but occasionally panicking, lack of inheritance, and poor dependency management through github links make Go a poor choice for applications within a business setting.

FWIW I've been using Go since 0.8 and this stuff isn't really an issue for me.

1. Panics should not cross package boundaries unless they are meant to be fatal!

2. "lack of inheritance": 90% of the time embedding does the job of inheritance for my use cases just fine. It also prevents a lot of stupid... (from me and others)

3. "poor dependency management through github links" For all my non-trival projects I always fork all my dependencies into their own repos and then link upstream as upstream. If there is not feature I want or bug that needs fixing, I update them every 6 mo - 1 yr. I do this with every language I use where my dependences are source code and not distributable libraries (.a, .so, .jar etc).


> For all my non-trival projects I always fork all my dependencies into their own repos

That sounds absolutely terrible. Even worse than having to track down & install libraries and headers for C projects. At least in the case of those libraries, you can enforce a particular version using autoconf or whatever. Vendoring source code for dependencies (especially in separate repos) is fragile and a pain for anyone else to track down when jumping in to your existing project.


How so? The project just includes a script that git clones the forks into your Go workspace for the project /foobar/src....


And then you have to maintain a bunch of ad hoc shell scripts separate from your actual build process. Like I said: fragile and a pain.


Besides a shell script with a "for each repo. git clone", there is no build process (besides running the go build command)...


And of course the fact you're forked off means you can update at your own pace and not have to take someone's ticket to "fix" a "broken" build. While I don't do this forking, it can help control churn, especially in projects with lots of deps.


That's part of the authors point and one that's I heartily agree with: Go is not necessarily very nice to program in, but other people who have to read and maintain your code find it very nice indeed that you were forced to be frustratingly simple. This was a design choice by engineers who did constant maintenance and reviews no doubt and I appreciate it every day.


I don't find Go simple at all, actually. Any individual line of Go might be simpler than many individual lines of Scala. But understanding how the whole system works can actually get complicated, particularly in code where the lack of generics forces type information to be lost.

Consider a pipeline:

    source ~> retryQueue.firstInput ~> requestAttempter
    requestAttempter.successOutput ~> finalizeResult
    requestAttempter.failureOutput ~> retryQueue.failureInput
This is basically logic to process a stream of messages, successes get finalized, and failures go into a retry queue until they succeed. I've got a large system doing this right now with Akka streams.

In Scala, type signatures ensure that everything lines up or else you've got a compile error. In a similar Go system, I lacked stream abstraction (I think Cloudflare has a beta library that does this now) and the lack of generics mean that streams are untyped.

For example, what if I pass a `Message` object to `retryQueue.failureInput` rather than a `MessageWithRetryMetadata`?

So I don't agree at all that Go code is easier to understand and maintain than Scala.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: