> 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.
«"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.»
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.
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.
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.
The language complexity comparison based on the number of pages in the spec is incredibly not useful and disingenuous.
The Java specification is actually a fairly good specification and also covers a great deal of the runtime (of which Scala gets a free ride).
The C specification is 500 or so pages.
IMO the Go specification is sorely lacking in details (albeit I must confess written in a much more modern and pithy way). It also doesn't have legacy baggage. This isn't to say Go is bad or good but you shouldn't base your decision on the length of the spec (particularly when formatting also plays a large part).
BTW if you are looking for close to the best of both Go and Scala (an in between) take a serious hard look at OCaml. Yes OCaml multicore is not done but it is coming. And in ref to the authors mention of being explicit IMO OCaml is the most explicit language I know (ironic given it has incredibly good type inference).... Oh and for completeness its specification is 657 pages. I actually learned most of the language by reading the spec.
As I read the author's comparison, OCaml came to my mind as well. Reading Minsky's posts and the Real World OCaml book got me really interested in its capabilities for modern software development. I've heard mixed reviews about the object-oriented features, but the functional ones are solid, expressive, and easy to understand.
At home, I started using it as my primary language just a few weeks ago.
I've been playing around with OCaml a fair bit in my spare time too. I've been using Go at work full time for about 4 years so that's my main comparison.
I love OCaml as a language but the ecosystem is really pretty bad. There is just too much fragmentation and lack of libraries. I would have expected more from a language that is over 20 years old.
Don't get me wrong, I want it to succeed since I agree it has a really nice mix of features.. I just have my doubts given its track record so far.
> Don't get me wrong, I want it to succeed since I agree it has a really nice mix of features.. I just have my doubts given its track record so far.
While I agree... Javascript is also just as old and arguably more fragmented (with the exception of jQuery).
That being said perhaps unlikely but OCaml may make a massive comeback just like Javascript did (particularly if for some reason Rust fails which I doubt).
Javascript has web giants' backing who made it work and well enough to write sophisticated frontends like webmails etc. OCaml has Jane street and may be a couple more using it as secondary language for some applications. I don't think it is going to catch up big time.
Thanks for the tip. I see utop referenced all the time, but I just haven't fired it up.
Currently I'm designing a small language so I can learn OCaml better. I'm planning to use the ocamllex and menhir tools for lexing and parsing. I'm sure utop will come in handy for testing out ideas.
There is overlap and there sort of has to be because of the memory model and reflection if I recall correctly. Certainly the documentation could be more pithy though.
C++ has tons of arguably useful features. But these features also in a way distract from the task at hand - solving a problem. You risk ending up discussing the meta problem too much - how to write and organize code.
C on the other hand is very basic, requires a lot of boilerplate and encourages re-implementation. But you are (subjectively) more likely to produce pragmatic code which will solve your problem.
Scala is the kitchen zink of languages, i could elaborate but I don't even know where to begin.
Go is very simplistic and architected to solve some recent problems. It's easy to write high concurrency, low latency apps with minimal startup costs. Perfect for microservices. And it explicitly avoids certain complicating features such as generics, while still catering for its use cases by supporting code generation and compile-time constant computation.
Go is the rise of New Jersey Style. All developers tormented by c++,ORMs,Java EE,Soap et al join ranks to show the world that "Worse Is Better".
A funny thing is that go is very similar in style to early Java, and I believe that Gosling et al did have the same mindset as Thompson and Pike. But somehow Java got overrun by the enterprise guys, and I don't know where they came from. Does anybody here know maybe?
One thing that I like about Scala is that we can use Java Mission Control to analyze how our code (to the 'method' level,) perform in production. Not just deploy and pray that our code will hold strong enough to handle the load.
I only see this feature in Scala and Java --not that we wanna compare in this case,
Ops support and transparency is superb on the JVM. That some languages don't even have something remotely comparable is something that didn't even occur to me before expanding my horizon beyond Java.
Is it available only on Java/Scala, or any language on the JVM ? If Java/Scala, do you have an idea of how much work would be needed to exploit the informations from flight recorder in e.g. Clojure ?
it should be available in any language on JVM although I've never tried it myself. It's simple enough, I don't think it would take too much work to get the information from flight recorder.
I'm learning Scala at my new job (previously worked in Ruby, Python, and long ago, Java and enjoy learning about functional programming). It's a fine language with a lot of great things about it...that get tossed the second you touch Java. It's wonderful to not have null. Except that you do anyway! My biggest complaint is that it is so multi-paradigm that different systems in our codebase have completely different styles.
> It's wonderful to not have null. Except that you do anyway!
If you hit a NPE in Scala, you're doing something objectively wrong. You should be wrapping any calls to Java libraries that may return null with Option().
I agree that null should generally be banned from Scala code, but your statement is a little too strong. I use Spark / Scala for big data applications and Option() is slower than null. From the Databricks Scala style guide: "For performance sensitive code, prefer null over Option, in order to avoid virtual method calls and boxing."
You might also want to check out Kotlin. It's basically what everyone wished Java 8 had been, but wasn't because a) backwards compatibility, and b) difference of opinion (looking at you Optional). We have a set of coding conventions we use for our Java code that includes things like using Optional.empty() instead of null and declaring all method parameters (and as many locals and properties as we can get away with) as final. We've found that using Kotlin allows us to write equivalent code in a far more succinct fashion because all those conventions we follow are the default state in Kotlin, and it's deviating from those conventions that requires extra syntax in Kotlin.
Would be interesting to get a gauge on how many bugs are typical in code in each language.
From what I've heard Go code features a comparable number of bugs to Python per line of code. But far more lines of code. I don't know how that compares to Scala but would be very interested to know.
Complete tangent, but I must say that since I recently started using pycharm it has really caught most of the obvious typo and uninitialized bugs and my python code is really crashing surprisingly rarely as I prototype to death.
Just saying in case there are others who haven't been trying modern python tooling and don't know that you can get real close to a compiled language type safety certainty these days before you run a line of code.
I get where you're coming from, but relying on an IDE for safety is a recipe for disaster. What happens when you just quickly pop open that file in on a shell to make a small change and fat-finger something? What happens when a different developer decides to use vim for everything? I used Python extensively for years and it's still my scripting language of choice, but to use it to build something safe in a business-critical situation requires an obnoxious number of tests, half of which are basically just type checks.
1) you get an error, and since it's a small change I'd assume you could guess where you caused it.
2) they get errors but they may have more difficulty finding them. (or they use a vim plugin for that, or pylint, etc. on save)
I'd guess your ability to suss out an error's cause without an IDE is a matter of comfort with a language, and your lack of ability isn't an IDE's fault.
Scala is half a decade older than Go and has lead in big data infrastructure projects. Go has lead in container management infrastructure. May be that's their niche areas.
I would be interested in seeing when they are applied in other's domain and how good/bad they really are.
And that divide is meaningful too. Writing Spark jobs in scala works pretty nicely, thanks to some complicated language features. I have difficulty imagining the equivalent go version being as nice.
Meanwhile, go's simplicity is great for low-level performance-critical code.
As a fan of Scala and a developer who has been distancing himself from anything C-like for a decade, I found this post to be pretty fair. Go seems to force explicitness; Scala can be explicit or implicit depending on who uses it. In general, I find explicit to be preferential to implicit when I'm trying to learn a code base. BUT, some cases I hate handling explicitly, such as NULL. For those cases, you can limit the need to handle them using Scala.
I believe Google actually did some research that concluded something to this effect, and it was a big part of why they started pushing Go internally. Whether or not that's the right conclusion based on that data is debatable... I'd argue not.
Why not? It seems reasonable to me that as the number of people working on some piece of code grows the average skill will settle around "not an expert", whatever that means.
I'd tend to expect the same (and agree that "not an expert" is vague), but I'm actually a bit more curious about the proposal that it's a normal distribution. Why would it be? If I had to guess, I might go for something downward-skewed, or maybe even some kind of typically bimodal thing.
Does it have to be one or the other? They are suitable for different things.
You will find it hard to write an enterprise level application with complex domain logic in Go. Go is low level. Scalas type system on the other hand is very well suited for that.
Go's standard library around networking and crypto makes it the best language of choice for pretty much anything network related. Scala doesn't even come close.
My opinion in 2017 is Scala and Go (or Go and Scala) _are_ the two best programming languages out there. Learning both is worthwhile and _together_ they cover a very large set of use-cases.
I doubt that Go network libraries and crypto libraries are so well battle tested as Java ones, which Scala can take advantage of, specially taking into account the amount of JVM and library vendors.
Perhaps I just don't have enough experience with Scala and I don't know the best practices (maybe everyone just uses akka and apache commons for everything?) yet.
def getUserId()(implicit request: RequestHeader): Option[Long] = for {
cookie <- request.cookies.get("uid")
value <- Try(cookie.value.toLong).toOption if value > 0
} yield value
I only use them on methods/vals that are intended to be publicly used just so I don't inadvertently change the type of something and break someone else's code. Within private methods, it doesn't really matter.
Scala is a very complex languages, allowing tons of construct. And multiple ways to code anything. In any serious, bigger team project, you don't wanna use Scala imho. Not forgetting the super ugly stack traces.
If you really need functional programming, use a more functional language.
If you need speed, consistent code, and lots of developers. Use golang. Its simplifies software engineering.
Also if forced to use the JVM, i would chose Kotlin over Scala.
Procedure syntax (the last two lines, lacking the `=`) will be deprecated in Scala 2.13. The two variants before those are due to type inference. Are you really arguing against type inference?
The empty argument list `()` is a convention to denote that a method has a side-effect, whereas a method with no argument list at all is considered pure.
Transportability is an oft under-valued quality when comparing languages. Yes you could write something highly performant/compact/domain-specialized in perl,scala,lisp etc but when the time comes that your product is successful and your team needs to scale or you want to transfer ownership to another team how easy is that transition going to be?
Unless your app is so trivial that syntax and APIs are the dominating factor, I'd wager the team transition will have so much other stuff to deal with. Hopefully, not least of all, the actual program logic.
And team transitions are rather rare compared to the day to day dev that goes on. Optimising for them seems misplaced.
A lot of Scala is written to be that scaling code, see Spark. I know your point was the possible hand-off between teams, just wanted to indicate it is often used as that scaling language in data-processing.
Spark is not just written in Scala, they shared the same philosophy of underthought design.
Luckily, there's a little reason of using Spark after Yandex released their Clickhouse: it better compresses data, it is much faster, it doesn't need crazy infrastructure of low quality Apache's code, it can be used in a realtime which is really huge.
I don't care much about the comparison of these two languages (there's some interesting points though), but the "general observations" intro explains very precisely things that I always struggle to communicate.
Read it, it's a few lines, you've already heard it but it never hurts to be reminded it.
Basically replace "Go" in that article with "Java 8". Java 8 makes for a much stronger argument for their situation considering they wouldn't have needed to rewrite any of the Scala code.
The author of the article almost admits it multiple times. With really only one point:
One of the other benefits of Go was widening the pool of backgrounds we can hire. We can take someone from any language background and have them ramped up on Go in weeks. With the Scala side there’s the JVM learning curve, the Java world of containers, black magic of JVM tuning, profiling tools, etc…
Do new developers really need to know about the JVM and tuning? Hell I'm not even an expert with JVM tuning. You can go pretty far with the defaults.... Containers... nobody uses containers anymore. Profiling, debugging, monitoring tools.. ahhh the JVM has the best. You need to profile code regardless of language.
That is like saying Go lang developers really need to know about C and calling channels black magic.
I find this article much more interesting than op.
In particular, the focus on keeping functional codebases simple is, I believe, the core of what makes a successful functional codebase.
Basically, at some point when working languages like Haskell, Scala or F#, you are given a choice between power and ease-of-hire/onboarding. Apparently, they made the second choice, which I believe is the good one in their situation.
I believe it is possible to stick to functional languages and make a team-enforced decision to keep things simple, but the choice of simply opting out of it is also on the table, and I won't blame them for it.
I would just have preferred having a simple language AND a great type system.
I just personally hate seeing a giant method chains in a code block.
How do i know how this call chain behaves in production? Where's all the logical exit branches from this for error recovery? Is this properly null checking if required?
I see this in python and javascript all the time. This style of programming is what i call 'happy path' programming. It only works properly with valid input.
Go forces the developer to explore non-happy path cases during development. This leads to java's 'check for null' boilerplate everywhere. The syntax is ugly however, personally believe that it leads to higher quality systems.
Learn the functional style, and it will look so much more simple.
>Where's all the logical exit branches from this for error recovery?
No exit; the error is carried on in the pipe until the end. Successive transformations simply have no effect, since the data is in error. The chain output type can be either the data you want, or an error.
> Is this properly null checking if required?
Stop using null's.
> This style of programming is what i call 'happy path' programming. It only works properly with valid input.
The unhappy path can be handled just as well, you merely have to incorporate it in the type of output your system can produce. In that way, errors are also a happy path.
> Go forces the developer to explore non-happy path cases during development. [...] The syntax is ugly however, personally believe that it leads to higher quality systems.
When I program in a language like Haskell, not exploring unhappy paths leads to code that doesn't even compile. Usually this leads to pretty good quality.
> How do i know how this call chain behaves in production?
> Where's all the logical exit branches from this for error recovery? Is this properly null checking if required?
I see this in python and javascript all the time. This style of programming is what i call 'happy path' programming. It only works properly with valid input.
If this is done correctly this fluent pattern is usually a monad. This works really well with typed languages that have variant types that enforce pattern matching like Scala and Rust.
This pattern is actually far less error prone than imperative null checking (in my experience) as you have to to deal with the wrapped value but it can be equally tedious if the language isn't expressive.
An example of this syntax for Java can be seen in RxJava.
Yes, because if you're typing the word null in your Scala code you're doing something wrong. Any Java library that might return null should always be wrapped in an Option, so that you're dealing with Some or None, and you never get an NPE.
...will get you an Option[User] of the oldest user. People who have written Scala for more than six months can immediately recognize what this line of code does, and it eliminates around 20 lines of equivalent Java (or Go?) code. This kind of thing rarely needs to be refactored.
Pedantic since you're just making a point, but maxBy would be the better performing choice though you'd have to do a non-empty check...or just a fold if you can stand a default on empty list.
Scala also forces the developer to explore non-happy path cases. But it does so with meaningful abstractions that don't force you to do it on every single line.
Scala lets you write your code in the style of 'happy path' programming:
val result: MyError \/ ResultType = for {
x <- f(input)
y = g(x)
z <- h(y)
} yield (q(z,y))
Here `g(x)` is a pure function which never has errors. But f(input) and h(y) are impure functions which could return errors.
The error case is handled by `result` being `myError.left[ResultType]`.
To actually access `ResultType`, you use fold:
result.fold(err => ..., r => success(r))
This makes it pretty hard to ignore the error case.
Scala carries some baggage from being on the JVM that makes this not-quite-true, but it mostly enforces all of those things in the type system. There's no null-checking required, and if something can fail, the type system should tell you.
e.g. your method signatures will read
doSomethingRisky(): Try[Result]
and to use that value you'll either have to carry that Try forward, or handle its failure cases.
In this sort of comparison Clojure looks quite good I think. It gets the full power of the JVM and ecosystem plus the full power of Lisp macros if you really need it.
Normally, it's a small orthogonal language with a highly typical 'Clojure' style of writing programs.
For people who like Go, they can easily switch to Clojure without relearning Concurrency stuff. They can use core.async just as Go-blocks. To make it even easier you don't have to pass around pointers ever, you can just pass around to references to immutable data or safe reference type.
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.