Scala vs Go
quora.com
quora.com
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.
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.
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).
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.
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.
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.
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.
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.
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.
«"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.»
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.
but isn't the java spec split in two: the java language specification and the java virtual machine specification? (honest question)
At home, I started using it as my primary language just a few weeks ago.
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.
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.
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).
I actually used Go for my last one and was happy with it. However, next time I'd like to take a dive into the functional world.
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?
"The EJB specification was originally developed in 1997 by IBM and later adopted by Sun Microsystems"
So IBM does sound likely.
Hints that https://en.wikipedia.org/wiki/CICS was the product inspiring EJB.
I wouldn't say that it is inherently worse to have a simple feature set. Sometimes less is more [0]
[0] https://commandcenter.blogspot.de/2012/06/less-is-exponentia...
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().
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.
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'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.
It's like someone writing Java-like Python code. When you write in Scala you completely change the way you write code, the whole paradigm.
This question could be asked Haskell vs Go, it would be the same IMO.
If you have chosen Scala, you don't choose it as a Java replacement, you choose it as a programming philosophy replacement
Edit: Picking on Scala for lack of network libs is like picking on Go for lack of Category theory libs
I would be interested in seeing when they are applied in other's domain and how good/bad they really are.
Meanwhile, go's simplicity is great for low-level performance-critical code.
That's a yardstick I didn't realize anyone was using.
At scale, the skill level of developers reading/writing/maintaining/testing code is going to be a normal distribution around the mean of "not expert."
Has this been researched or shown in any formal way? Or have similar observations been formulated elsewhere?
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.
Gotta call a http endpoint in Go?
https://golang.org/pkg/net/http/
Gotta call a http endpoint in Scala? https://www.google.com/search?q=best+http+client+library+in+scala
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.import play.api.mvc.RequestHeader
def getUserId()(implicit request: RequestHeader) = { request.cookies.get("uid").map(_.value.toLong).filter(_ > 0) }
Return type is missing, it will throw exception (toLong).
You are correct about the exception if the cookie's value can't be parsed as a Long. You might instead write the code as:
def getUserId()(implicit request: RequestHeader): Option[Long] = {
request.cookies.get("uid")
.flatMap(cookie => Try(cookie.value.toLong).toOption)
.filter(_ > 0)
}
or, equivalently: def getUserId()(implicit request: RequestHeader): Option[Long] = for {
cookie <- request.cookies.get("uid")
value <- Try(cookie.value.toLong).toOption if value > 0
} yield valueIf the author is concerned, the way to express this to be more expressive could be something like this:
val uidOpt: Option[Long] = request.cookies.get("uid") map {
case Some(uidStr) if uidStr.toLong > 0 => Some(uidStr.toLong)
case _ => None
}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.
def doSomething():Unit = {...}
def doSomething:Unit = {...}
def doSomething() = {...}
def doSomething = {...}
def doSomething() {...}
def doSomething {...}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.
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.
And team transitions are rather rare compared to the day to day dev that goes on. Optimising for them seems misplaced.
Read it, it's a few lines, you've already heard it but it never hurts to be reminded it.
> As a developer, the only performance that I care about is my development cycle
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.
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 don't know of a wider pool than Java.
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.
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.
> 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.
>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.
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.
Option(myJavaLibraryFactoryMethodUsers()).flatMap(_.sortBy(- _.age).headOption)
...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.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.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.