From First Principles: Why Scala?
lihaoyi.com
lihaoyi.com
* Li's libs (os-lib, upickle, utest) have clean public interfaces, but most Scala ecosystem libs are hard to use, see the JSON alternatives for examples: https://www.lihaoyi.com/post/uJsonfastflexibleandintuitiveJS...
* The Mill build tool looks a lot better than SBT, but seems like everyone is still using SBT
* Scala minor version are binary incompatible, so maintaining Scala projects is a big pain. Upgrading Spark from Scala 2.11 to Scala 2.12 was a massive undertaking for example.
* Scala has tons of language features and lets people do crazy things in the code. Hard to win technical arguments with Scala geniuses that like using complicated language features.
* Scalatest is stil used by most projects and is annoying to use, as described here: https://github.com/lihaoyi/utest#why-utest. The overuse of DSLs in Scala is really annoying. Too many DSLs is another example of something I consider to be an antipattern, but there is no Scala community consensus on the responsible use of DSLs.
I'm optimistic about Scala. There are some folks that love the language and are continuously improving the ecosystem. Scala 3 will have to sell a better story about ditching legacy tooling and giving users a better default stack if it wants to compete with modern Go/Rust/Python.
I was on a team building good old crud apps using monads, monoids, categories, combinators, effects cats, seamless and bunch of other nonsense that i've now purged from my brain.
You could've easily mistaken our team for a programming language research group at a university.
This is literally the number one reason i would never choose scala. There is no upper bound to amount of stupidity one can indulge in. Our code was so convoluted that even intellij had trouble understanding what the hell was going on and would spit out compilation errors when there were none.
I now work with clojure team and i feel a sense of relief and weight taken off of my head from all the useless stuff i had to learn and use.
They're really smart people, and they had to be extremely capable programmers to get this far, but the embarrassing question is, how would a team of mediocre developers have tackled this problem? They would have picked a mediocre language and probably written a naive, straightforward solution, and after a few months of squashing bugs and developing production workarounds, it would have been a workable, tolerable system. They would have spent the last year working on something else.
The naive solution would have more inherent unreliability, but our vast and intricate solution, designed to scale to the moon and be more self-healing than the T2000, is never going to approach the same reliability as a naive solution, because it's too complex (and the code is too hard for mortals to read and reason about) for us ever to iron out the bugs.
They make a huge deal out of how much safer and easier to reason about our code is thanks to the FP discipline, but why do we have just as many concurrency-related production issues as a typical team using blocking operations and threadpools? Why do we have _more_ incorrectness caused by swallowed errors than I came to expect in projects that relied on exceptions?
I'm still keen on mastering this style of Scala, because I think the benefits can be had, but it annoys me that some of my teammates are happy to use those stated benefits to justify their programming adventures without seeming to care if they actually materialize.
I don't mean to be rude to your colleagues, but almost by implication in what you've said, they're not good programmers. Being a good programmer is nothing to do with leveraging a fancy FP language. It's about delivering working, maintainable, testable code. Whether you write buggy spaghetti code in Go or misuse Scala, its the same root problem.
I've been a Scala dev for five years, worked on some massive projects and it's been a dream. As long as you agree on a style, don't go crazy with the language and use it with discernment, it's a joy to work with. It's a sharp tool though and it takes discernment to know how to wield it appropriately. I don't know if that's a downside, it's more a warning.
That's fair. I guess if by good programmer we mean, fluency with the language and algorithms / abstractions /types etc then perhaps they qualify. I really meant something more like "software engineer" ie. someone who can architect a simple, sane, working solution adhering to the usual best practices of good software construction. Maybe there's a missing piece between being a good coder vs scaling this knowledge up to the application level. The latter is indeed a much rarer skill and too often, people without it influence major decisions.
My pet peeve in this business are supposedly good programmers who get praised despite never having actual results. And it is not like they would be rare.
And my feeling is the same with Clojure and Scala, I'm not a specialist, but code can be written in such a way that I can even contribute to it.
It's always better to write simple, almost dumb code that anyone can understand than to use advanced abstractions from category theory or whatever.
Why? Because every single study or anedocte I've ever heard is like yours: adding complex abstractions to code do NOT make it more reliable. But they do make it much harder to modify, understand, and fix.
FP tought us that unconstrained mutable state is very bad, and that has helped us immensely in the mundane languages of the world (Java, C#, C++, Go). But I think it's time to learn the other lesson from FP: overly complex abstractions are also very bad and should NOT be used willy nilly as they simply have a very low or negative cost-benefit.
I disagree with the general take that "adding complex abstractions to code do NOT make it more reliable". Good use of abstraction makes code easier to write, easier to maintain and easier to extend. Good use of abstraction can make code more concise and more regular while at the same time allowing better diagnostics when you do something wrong.
As a quick example: a generic JSON serialization library that can work with any type is probably a lot nicer to use that one that requires manual reimplementation for every class in your program. It'll be more complicated to write but it's probably well worth it in the end. Similarly a logging system that can abstract over several backends and log levels depending on the environment probably beats having a bunch of if/else every time you want to log a message.
But one needs to remember the old saying: debugging code is harder than writing it, so if you write code that's as smart as you're capable of producing then by definition you're not smart enough to debug it.
This is the reason I used the term "overly complex abstractions", not just "abstractions" in general, of course. Overly complex meaning it's a failure to use "the right abstraction" where you instead go for something much more complex than the proble called for... this is what I understand most people in this thread are referring to when they refer to previous Scala projects they've worked on.
I used to write complex code because I found it elegant. Now I write simple code because I enjoy breaking down a complex problem into its simplest form, which for me means I fully understand the problem.
I still think programmers should work with powerful tools, because "dumb" tools often force you to express things in convoluted ways, and you don't need a powerful language to make a huge mess. (Java proves that you only need single inheritance to produce virtually unlimited amounts of spaghettiness in real-world projects. The difference between Java and Scala is not that Scala enables teams to produce epic piles of FP crap, it's that we long ago stopped being shocked when teams produce epic piles of OO crap.)
I think the programmers doing terrible things with Scala would be doing terrible things in any language, given the chance. What they need is hands-on technical management. When they have a wild idea, they need to be walked through an appropriate engineering decision-making process. They need somebody with the authority to tell them, "Ha ha, yeah, that design with etcd and Kafka and the robot space lasers would friggin' rock, how cool would that be, but on the other hand, we could just stick a REST API in front of a database and you'd be done in two weeks, so let's compare pros and cons."
It does not matter how "simple" each line of code is.
What matters is how "simple" the whole application/solution is.
You can argue that you can write very simple code in Assembler - all instructions are very clearly defined and very simple (well, maybe not anymore in CISC...). Someone can learn them all in a day. The problem is just that now you end up with a lot of code and overall that will be hard to maintain.
Compare it to Javascript: certainly not the language where code is most maintainable or simple, but the end-result is easier to maintain then assembler.
In the end, the choice of programming language is like a the choice of a compression-tool like gzip. The more complex, the more difficult to learn and use (setting library size and various compression settings) but the output will be smaller compared to a simple tool or no compression (Assembler) while containing the same amount of information.
I write lots of F#, and I find that computation expressions (a fancy version of do-notation) makes my code more "dumb" and "simple", despite being an advanced "FP" feature. This is because it pushes the complexity from my business logic and into library code.
With computation expressions:
async {
let! foos = fetchFoos
for foo in foos do
do! launchFoo foo
do! clearFoos
}
Without computation expressions (this is probably wrong but you get the idea): Async.bind
fetchFoos
(fun foos ->
Async.bind
(
foos
|> Seq.fold (Async.bind (fun foo -> launchFoo foo)) (Async.just ())
)
(fun () -> clearFoos)
)
Notice how my fancy non-blocking code reads like straight-forward blocking code?I could not manage complexity without this feature.
Sometimes false. If the (more) complex abstractions are in well-tested libraries that haven't been written solely for your code.
> But [complex abstractions] do make [your code] much harder to modify,
Not necessarily. You can separate concerns; you can isolate changes; and you hopefully reduce the amount of code you've written yourself, significantly even.
> [complex abstractions] do make [your code] much harder to understand
This is as likely false as it is true.
> [complex abstractions] do make [your code] much harder to fix
Again, this very much depends. If you're using a well-tested, widely-used external library with those abstractions, it may well be easier to fix your own code.
Lisp, in its persistent failure to achieve world domination, has been teaching us this lesson for over 60 years now!
Infinitely powerful languages are fun and, yes, powerful. But as soon as the project has more than one or two people in it, they also result in runaway complexity that will hurt the product far more than any gains in expressibility.
Because that was the obsession, they ended up spending all their time iterating on this perfect type system and not enough on the product. I think people with that issue are more likely to want to use Scala.
Result types, Either et cetera make it extremely easy to swallow errors by flatMapping thoughtlessly losing the context of where they were - you typically /want/ the call stack when you hit into an exceptional flow.
For example, I find myself hating the Either type, because I feel like there is a socially established convention that one half of the Either is the type that matters, the value that you want, the value that is the point of the computation you're doing, and the other half is a garbage value that should never be directly handled. So I really feel like I should conform to the convention and reserve Either for cases where one of the possible types doesn't matter. But how often is it true that one side of the Either doesn't matter? People want me to encode success/failure in an Either type, but if I do that, are they going to treat failure with the care it deserves?
I often handle Either (and Option) using pattern matching when I feel it's important to give both code paths equal importance and equal visibility in the code, but people change it because flatMap is supposedly more idiomatic, and they believe that eliminating pattern matching from their code is a sign of sophistication.
I feel like this stems from a strong desire among FP folks for the happy path to be the only one visible in the code, and the non-happy path to work by invisible magic. Maybe there are some brilliant programmers who achieve this by careful programming, but there are people mimicking them who seem to rely more on faith than logical analysis. They just flatMap their way through everything and trust that this results in correct behavior for the "less important" cases.
I'm sorry that this turned into a bit of a rant, but I'm entirely fed up with it, and it accounts for a lot of what I dislike about the code I work with on a daily basis.
For unfamiliar topics or when presented with uncommon insight, I believe rants, monologues, even diatribes are actually some of the best things to read.
There's always a tradeoff between making the happy path clear and making the error handling explicit. The whole point of Either is to be a middle ground between "both cases are equal weight and you handle them by pattern matching" (custom ADTs) and "only the happy path is visible, the error path is completely invisible magic" (exceptions). Given that people in Python or Java tend to use exceptions a lot more than they use datatypes, I'd argue that a typical Scala codebase puts more emphasis on actually handling errors than a typical codebase in other languages.
Where each case really is of equal weight, consider using a custom datatype (it's only a couple of lines: sealed trait A, case class B(...) extends A, case class C(...) extends A) rather than Either.
instance Functor (Either a) where -- a is fixed here!
fmap :: (b -> c) -> Either a b -> Either a c
fmap _ (Left l) = Left l
fmap f (Right r) = Right (f r)
As a general rule, the last type parameter of a type carries special significance: the same applies to Tuples.You _can_ trivially construct a type where the two labels are swapped; it's just labels, Left and Right aren't intrinsically important, except insofar as they reflect the positions of the type arguments in written text.
data Either' a b = Left b | Right aIsn't this the same as letting exceptions bubble up in a non-FP language?
It's not that bad though, because you get much less of these kind of errors than in a typical Java program (for example).
My argument is that as a programmer you /choose/ the base lemmas which you're comfortable with - with an exception you're saying for a large swathe of your code, it will assume a lemma. When is /does/ break you get to point the finger at it with a stack trace saying here is where the lemma was broken. As an aside there are nicer ways to do this ala contracts, but exceptions aren't a /bad/ way.
The equivalent of try.toOption is try catch everything and discarding. The equivalent of flatMapping without adding surrounding context is equivalent to try catch rethrowing. Both of these are /much/ too common in FP codebases.
I agree that some kind of logical call stack is a very useful thing to have, and I'd recommend implementing something along the lines of https://github.com/lancewalton/treelog that provides it.
In my experience, mediocre developers would stick to a simple language like Python and only use simple Python features... and write a completely impenetrable, 100% coupled and wholly unreadable mess that would make you wish for over-engineered Scala. Don't even get me started on what happens with "simple" distributed computing tools like Hive...
I see people complain about languages like Scala leading to over-engineering and I just don't get it: have you not worked with sub-par Python or JavaScript code? Sure, poorly designed complex solutions have problems, but so do under-abstracted codebases! Bad code is bad code, and I don't think it's meaningful to say that "over-engineering" is categorically worse than "under-engineering".
I actually worked with somebody who wrote pathologically over-complicated Haskell. Had some friction between us over that. His Python code? Somehow even worse.
No serious language I've seen at either end of the abstraction-friendly spectrum (not Haskell/Scala nor Go/Java/Python/etc) puts a meaningful floor on code quality. An inexpressive language can have a low ceiling for how good code is, but restrict or simplify the language however you like and people will still happily write utterly unworkable code.
I've found that languages—especially less common languages thanks to the "nobody gets fired for IBM" effect—get used as scapegoats for not talking about cultural issues. If people on the team are writing clearly poor, over-complicated and bug-riddled code, it means there is some sort of technical leadership shortcoming, and it would not be any better if they were using Java instead. (I mean, have you seen Enterprise™ Java™ codebases?) And, at the same time, it's clear that a Scala codebase with tasteful technical direction and leadership—conveyed through team culture, code reviews, shared expectations... etc—can be absolutely great to work in.
At this point, I'm leery of blaming languages for programming problems without a clear mechanism. "The language lets people do bad things" isn't a real mechanism because all languages do bad things. "The language attracts people who like complexity" seems specious too. (Again, let's not blame languages for hiring and cultural problems!) "The languages defaults incentivize poor code" is a better argument, although it can be a bit fuzzy. And arguments like "language A doesn't have capability B that we need" or "language C allows classes of bugs other languages don't which empirically cause problems" are the most compelling of all.
Here's the thing, if you look at a basic Spring crud app, you are also using Monads, Monoids, Categories, Traverse, etc. but you aren't expressing it in the type system. Seriously go look at modern Spring's flux stuff, it's all there minus the type classes.
I've seen teams that tried to over engineer Spring, teams that tried to over engineer Node applications, and yes there are teams that over engineer Functional style Scala.
All the teams I worked with (and managed) at Verizon leaned pretty heavily into FP style Scala without much over engineering and the experience was extremely pleasant. The only production issues in my three years there I remember were performance related, finding out how to get more throughput or lower latency out of FP Scala. I literally can't remember any 'bugs' that made it to production.
I agree that using Monads, Monoids, etc. isn't necessarily indicative of over engineering in itself. If used well they can make the code clearer/simpler.
I don't use spring or plan to use it. Not quite sure why it needs to be looked up. What are you trying to say?
I don't think the super complex language features should be removed. Li's libs do some crazy stuff under the hood, but provide a clean, Python-like public interface. Most devs aren't that good and complex underlying implementations leak and yield complex public interfaces.
Lots of folks would love Scala codebases that only use 10% of the available language features and none of the complex frameworks. But, like you mentioned, it's a hard language to use responsibly.
That coupled with side-effect free functional style (as much possible without overstretching) uses the power of Scala and keeps the code concise and readable. That is how I use it and find it very pleasurable to write in Scala compared to Java.
[0]: https://docs.scala-lang.org/overviews/scala-book/introductio...
Does this have the same problem in large languages such as C++ though? Where everyone thinks there's an optimal subset of the language, but no one agrees on what that optimal subset is?
For me, Scala is a multi-paradigm language with tons of features. The community just needs different types of style guides for different apps. vars are horrifying for the functional crowd, but cool with me for example.
They already do it: it's called Kotlin.
In fact, in one project my former employer was involved in, one main reason they picked Scala was to, on the one hand, weed out the chaff from their existing team of .net developers (in a "shape up or ship out" kind of fashion), and on the other to weed out the 95% of mediocre Java developers.
This reminded me of a great blurb about Niklaus Wirth's approach to languages:
>Wirth’s philosophy of programming languages is that a complex language is not required to solve a complex problem. On the contrary, he believes that l languages containing complex features whose purposes are to solve complex problems actually hinder the problem solving effort. The idea is that it is difficult enough for a programmer to solve a complex problem without having to also cope with the complexity of the language. Having a simple programming language as a tool reduces the total complexity of the problem to be solved. [0]
[0] Pg.2 http://www.cslab.pepperdine.edu/warford/ComputingFundamental...
This, I feel, is underneath a very large proportion of everything in our industry.
This is scala equivalent of HN/pg lisp folklore.
I had the misfortune of working with basically this same team, on scala (of course).
I argued for maintainable simple technology but scala was too hip to pass, for many.
Still remember one meeting trying to decipher a bug and while reviewing the code in question the lead scala fan said "It is not reasonable to expect to understand what code does by looking at it. I'll need to go research this for a few days."
I hope to never see scala (or its ilk) ever again. Give me the most stable language and environment, where every question is a FAQ and every odd behavior is documented in every book and I never need to fall into the rabbit hole of language traps. Then I can just work on building the product, which is the whole point.
Ah I had the same experience.
That this is allowed to happen just shows that putting naive non-technical managers in charge of coders is a disaster.
This is an interesting point. Perhaps part of the reason my current employer has been successful with Scala has been that we never were integrated into that part of the community.
We hire non-Scala programmers and they write Scala without any training, and it generally turns out OK and ends up converging in a pretty boring style without any fanciness
Imagine another codebase uses a feature like self-types extensively: https://docs.scala-lang.org/tour/self-types.html
A small team has no good way to resolve the argument if self types should be used all over the place or avoided entirely.
A lot of the hardcore Scala community hates Spark which sort of summarizes the situation.
I agree with most of the above. A couple of additional thoughts:
* sbt -- I still have a lot of coming up to speed to do here, but the manual is like 500 pages and it's somewhat overwhelming. There are tons of little oddities, like why can't I run `sbt --version` and instead have to do `sbt sbtVersion`?
* The functional side is fascinating -- I'm still studying the cats library. I can almost describe a Monad! It's a pretty big mountain, though, and there are times where I have doubts whether the benefits will be worth it. Would love to hear some re-assurance! ;)
* The ecosystem for microservices seems pretty closely tied to akka & lagom. These are quite complex in their own right and we've been having trouble with the latter in particular. Curious to learn about alternatives. ZIO?
* Re: DSLs. Also not a huge fan of DSLs. One refreshing thing about python is that often configuration can just be in Python itself (as in Django, for example). See also:
[1] https://erikbern.com/2018/08/30/i-dont-want-to-learn-your-ga...
[2] https://github.com/cf020031308/cf020031308.github.io/blob/ma...
[3] https://twitter.com/antonycourtney/status/589238574429515777
SBT is not worth it. Ignore it and use Mmaven.
> The functional side is fascinating -- I'm still studying the cats library. I can almost describe a Monad! It's a pretty big mountain, though, and there are times where I have doubts whether the benefits will be worth it. Would love to hear some re-assurance! ;)
You shouldn't use these things unless and until you need them. Cats is basically a library of techniques for letting you accomplish things that seem like they might need language features by instead writing plain old functions that return plain old values. If you use the fancy technique for the sake of using the fancy technique, you're putting the cart before the horse. You should use them where you'd otherwise have to use some weird language feature (exceptions, magic async, mutable variables...).
> * The ecosystem for microservices seems pretty closely tied to akka & lagom. These are quite complex in their own right and we've been having trouble with the latter in particular. Curious to learn about alternatives. ZIO?
Mostly you don't need anything too complex. I'd recommend using akka-http to start with, but don't use any akka proper - stick to the routing DSL level and use futures rather than actors. When you're more comfortable with the functional abstractions you can switch to http4s.
It’s not worth it, the entire language is a mountain of documentation and hard to understand concepts that just gets in the way of actually delivering product features for the business. It will help you to think differently about programming problems though.
> * Scala minor version are binary incompatible, so maintaining Scala projects is a big pain. Upgrading Spark from Scala 2.11 to Scala 2.12 was a massive undertaking for example.
Scala just chose a strange naming scheme. Other languages would have just increased their major version instead. The scala minor version is increased every few years and not every month or so.
> * Scala has tons of language features and lets people do crazy things in the code.
Actually, that's not true. Or rather: compared to what language?
Scala has surprisingly few language features, but the ones it has are very flexible and powerful. Take Kotlin for example. It has method extensions as a dedicated feature. Scala just has implicits which can be used for method extension.
> * Scalatest is stil used by most projects and is annoying to use, as described here: https://github.com/lihaoyi/utest#why-utest. The overuse of DSLs in Scala is really annoying.
I agree with the overuse of DSLs. Luckily that got much better, but older libraries like scalatest still suffer from that.
> * Li's libs (os-lib, upickle, utest) have clean public interfaces, but most Scala ecosystem libs are hard to use, see the JSON alternatives for examples
I think that just comes from using the library in a non-idiomatic way. In most applications, you will need to use the whole json anyways, and then you use (or can use) circe like that:
{
"id": "c730433b-082c-4984-9d66-855c243266f0",
"name": "Foo",
"counts": [1, 2, 3],
"values": {
"bar": true,
"baz": 100.001,
"qux": ["a", "b"]
}
}
case class Data(
id: String,
name: String,
count: List[Int],
values: List[DataValue]
)
case class DataValue(
bar: Boolean,
baz: Float,
qux: List[String]
)
import circe.generic.auto._
import io.circe.parser._
val data = decode[Data]("...").get
data.copy(name = data.name.reverse).asJson
Yeah, that is more code, but as I said, in the vast majority for projects, you need all or most of the fields anyways, so all the structure definition is a one-time thing.The advantage is that the last line is plain Scala code. You don't even need to understand the json-library to do transformations and re-encode into json.
I don't think it's fair to say it like this. Scala's implicits can mean different things, depending on where they're used. Scala 3 even divides 'implicit' to multiple keywords.
(kind of 'static' in C++ I guess, only more complicated)
Scala has one feature (implicits) but it can be used ("mean") for different things.
Essentially, you can mark definitions as implicit and you can mark parameters as implicit. Yes, Scala 3 uses different keywords to make it easier to understand which is what, but both is still just the concept of things being implicit.
Think about it: one without the other is completely useless. If you cannot define implicit parameters, then marking any value as implicit will not have any effect. The other way around too: you can mark your parameters as implicit as much as you want, if you can't define implicit values, you will always be forced to pass all parameters manually.
Even implicit classes (excentions) are just syntactic sugar for regular methods that are marked implicit.
It's why Scala 2 and 3 are able to maintain pretty good interoperability.
values: DataValue
?Otherwise the decoder is incorrect and, I'm not 100% sure but, is likely to throw a runtime error.
In many ways, it's even worse than that. Scala has exactly two features (implicits and punctuation-free method calls) that allow you to build massively complicated libraries that pretend to be language features, and which work worse than equivalent features in languages that support them explicitly. A good example of this is typeclasses which are core features of haskell and rust, but are implemented by explicitly passing implicits (hah) in Scala.
Scala 3 seems to be a really good step in the right direction for me. There's a clear desire for many of the features that are currently achieved through burdensome hacks on top of implicits, and actually reifying several of them into core language features is a counterintuitive way to make things simpler rather than more complicated.
I also like(d) a lot about Scala, but this is what ultimately led me to other languages. There's too many language features cranked together. This makes a) for a steep learning curve and b) exposes you to expert's code that is so dense and 'smart' that its a PITA to decipher what it does.
b) you'll encounter especially when incorporating libraries that are not fully stable, and you have to track down bugs in them (if only to know if your code is at fault, or the library's code).
I had this with Akka when it was just released. One line of code and 2hrs of debugging to determine what it did, and if it was faulty or not.
Scala is a weird case, I would normally be the perfect fanboy for it, I like functional programming, did my fair share of SML, Haskell, Clojure. Also I am not at all dogmatic and can even find joy in writing Java.
However, Scala and I never got along well. It is - I think - too magic. I'd even prefer pure Java over it tbh.
And then, the community is fairly toxic, which also made me not want to stick around.
Unless, of course, that specific DSL is the sole reason for choosing Scala, like with Chisel[1].
Domain specific languages are heavy users of the implicit keyword and implicit conversion; maybe that makes the code more concise, but it doesn't quite help with reading / understanding the code. For me that's the greatest problem with scala - figuring out what this code in front of me does.
Contrast that with something like Java which, while quite simple syntactically, has a huge and varied super-structure of incongruent abstractions on top of it. Every time I dive into a largish codebase of "enterprise Java" it feels like the engineers has a running bet on who could use the most GoF patterns in any given module.
For example,
- Scala singleton objects add complexity footprint to the language, but reify a super common pattern in Java and ensures everyone does it the same way.
- Case classes add yet more resource footprint, but again reify a common pattern that in Java-land is fragmented between Beans and POJOs and other things.
- Named and optional parameters add complexity over Java's simple method calling style, but subsume a whole zoo of builders, overloading, telescoping and other patterns used to work around their absence
Each of these features certainly makes the language more complex, but arguably at the same time they make user code more simple and boring
Isn't it "major" versions?
AFAIK Scala has a version scheme of "epoch.major.minor"
Yes, there is a large crew of FP fans around scala that can be obnoxious at times, but they have also brought a lot of good stuff around. The way I see it: You need groups that “overshoot” and then settle on the middle ground. Compromise...
I use FP when it’s not too complicated, but stay away from the larger monad systems that are hard to explain to newcomers (effects/IO/Kleisli etc) because I honestly feel like it’s too much. Let me just run my debug logger when I want to... (yes I know that’s not what it’s about)
I probably fall in the category of using scala as the (way) better Java with sprinkles of FP and non-mutable data objects everywhere. I really like the authors style (his work is awesome in general and I point to your posts a lot for our juniors, thank you for your contributions if you read this)
It has its flaws, just like any other language, but it’s also incredibly expressive and fast (enough) for the backend and data engineering that we do
I guess we also choose our style and abstractions carefully and only use things when we can justify it as a team. If someone raised a PR full of novel concepts that we hadn't grokked as a team, it might well get rejected. For example we started using monad transformers a while ago only after the concept and benefits in our code was explained and bought into by the team. This doesn't just apply to Scala but is especially important when using it. Reach consensus with your team about the style and evolve it together.
I get the feeling that other language cultures, maybe Rust being one, are good at just using FP, without talking endlessly about it.
There's definitely been a shift in the community in this direction. While the hardcore-FP folks are still around, as are the hardcore-Reactive folks, there are now many others using Scala in this more simple, simplistic way to good effect. Scala is a big tent and every subcommunity can find things they like even if they don't always agree
I found Scala to be an extremely enlightening language: it taught me a lot about functional programming. But, the complex type system and the sacrifices needed to ensure Java compatibility entail many quirks that I find frustrating. I think it's actually a similar dynamic to what you mention: "retro-fitting these features onto an existing language never fits quite as nicely as a language that was designed with them from scratch."
BTW, I am a huge fan of your writing: the Principle of Least Power is a sacred programming text to me .
I don't believe any of those new alternatives has made better choices; some of them haven't had time to accumulate as many warts as Scala yet, but all of the ones I've seen have design decisions that make them inevitable. Higher-kinded types still work much better than every other way of achieving the same things that's been found - and if you want both higher-kinded types and decent tooling/library support, Scala is still pretty much the only option. (Haskell exists, but even if you consider its tooling good enough, implicit pervasive laziness has huge costs).
That reminds me of that dead JVM language called Ceylon. It didn't compromise anything to ensure Java compatibility. The combination of OOP+FP was executed with zero friction. Meanwhile Scala did it so poorly it created a stupid myth that OOP and FP shouldn't mix.
Of course everyone knows that nobody used Ceylon, not even Redhat who sponsored the language. The compiler was also dog slow. Typechecking complicated type unions/intersections can get really slow with bigger projects. The metamodel also created a lot of JS bloat if you were brave enough to use it in the browser. Lots of practical complaints that ultimately make you stop using it. But oh man the idea and design. It was very well thought out and enjoyable to program in.
IMO Gavin's biggest mistake was picking Eclipse for the IDE platform at a time when every professional was moving to IntelliJ.
Example: adding OOP in PHP and Javascript. In both examples, especially the first attempts, were half-baked. Why bolt on half a language feature? Both languages would be much better served with dependency management and / or modules, which for both languages came out of the community at first.
Counter-example would be Go, that resists change - especially if it's requested with a "this feature is in language X! I NEED it!". From the FAQ: https://golang.org/doc/faq#Why_doesnt_Go_have_feature_X (followed by some features other languages have. Generics / type arguments is one that will probably be added in an upcoming version of Go, but they only considered it once they fully understood the problem and need)
It will happen, just slower. Just as half-baked.
JavaScript is actually quite similar to Self (besides the syntax), which itself is a kind of a SmallTalk like language. It's hard to be more OO than that. :-)
Now it gets generics, so we are one step closer to Scala. :)
Scala has been in production use and getting bugfixes for longer than some of the newer languages have existed. I feel like a re-read of https://www.joelonsoftware.com/2000/04/06/things-you-should-... is warranted.
Julia, Rust, and Elixir are all great, but popularity, ecosystem are not there yet.
The alternative is to use multiple different languages to fill different niches. In theory this sounds suboptimal but I think it might actually be easier to learn multiple simpler languages than it is to learn Scala.
BTW according to PYPL: Swift, Kotlin, TypeScript, and Rust are now more popular than Scala.
Scala and F# are sinilar enough to basically be the same language, though I found F# had a number of warts and idiosyncracies that made me move on to Scala. Scala didn't have all of these (though of course it had warts of its own!) and is what stuck with me for the long term
That principle is actually directly from the CTM book, which seems to have been a great inspiration for Martin Odersky in the language design.
Same for compile times. TS compilation is ridiculously slow yet there're no complains whatsoever out there. Scala compile times are OK on the other hands side, especially if you ever used C++ with similar complex code, or TS like said, but "the slow compiles" are a topic now for years.
Just makes no sense to me, don't get it.
Basic things are supported well. We use Play. We have GraphQL in web services - use Sangria. Slick FRM for database access is very clean. We use Akka for live web socket connections to Spark for interactive execution.
We extend Spark code from outside when needed - adding functions to classes in imported libraries.
It’s a learning curve for new engineers, but we hire the best and they like Scala after using it for a bit. SBT is confusing as hell, build times were painful but getting better. Debugging is sometimes hard (implicits)
One thing I’ve learnt us that if you’re handed a gun, you don’t have to start shooting at your feet just because you can. We used C++ at NVIDIA sensibly a decade back - using OOP but still avoiding horrible parts of C++. We use Scala sensibly now.
Can’t imagine another language that would work for us. I’ve used C, C++, F# before (building compilers and databases)- don’t know Kotlin. Only other language we use is Golang for our kubernetes operator (excluding UI)
If you haven't used a statically typed language before, i.e. you're from Ruby, JS, Python, et. al then it's one bangin' ass brain rodeo. I'd highly recommend spending a couple months trying to learn it. Once you're done, you will almost certainly spend near zero effort learning any other typesystem save the MLs (which would, in some ways, probably be a better rodeo).
One of my favorite "ah-ha! that is totally how it fucking works" moments with Scala was going to see how `Function` worked only to find this bad boi: https://www.scala-lang.org/api/current/scala/Function22.html
A lot of time people think there's more magic going on than there is and just seeing that's how a function works with 22 arguments was pretty illuminating for me coming from Ruby.
Dang. I just checked in the Common Lisp I use and it's a bit higher. What's the reason for having to have at least 22 concrete function signatures like that?
CL-USER> call-arguments-limit
4611686018427387903 (62 bits, #x3FFFFFFFFFFFFFFF)This is no longer true in Scala 3, so there will be no such limit: http://dotty.epfl.ch/docs/reference/dropped-features/limit22...
To do this though, you need a defined static type for the type of Functions each of a different number of arguments.
22 is just where they stopped to keep defining those types.
I teach it to complete beginners to programming and generally it goes great - there is a huge demand for junior Scala developers in my neck of woods.
However when I found that 22 argument monster a while ago it was a bit of a wtf moment to me.
Why 22? Why not 21 or 23 ? Is it one of those no one is going to need more than 22 arguments?
> Is it one of those no one is going to need more than 22 arguments?
You can work around this limit with nested tuples, or just defining Function23 yourself. Although if your function accepts 23 arguments, you should probably refactor that anyways.
It is also worth noting that Scala 3 drops this limitation by implementing function arguments with arrays: https://github.com/lampepfl/dotty/pull/1758
- Scala adds an Option type, whereas Kotlin adds nullability checking for existing object types.
- Scala has its own convention for getters and setters, whereas Kotlin automatically turns JVM getters and setters into properties.
- Scala defines its own set of collection classes, whereas Kotlin has zero-overhead wrappers over the JVM collection types.
I haven't tried Kotlin/JS lately, but I expect that Kotlin's strong focus on interop with existing stuff would serve it well there too, especially when it comes to minimizing bundle size.
Scala may be more powerful in some ways, but Kotlin gives me the conciseness I want, with great interop with the huge Java ecosystem.
And that is a huge benefit. Scala collections are really great.
In addition, Scala also has wrappers around Java collections for interop, or you can just use plain Java collections directly.
It doesn't really matter how great they are, it kills JVM interop right there ... and its even worse if it doesn't and you get implicit conversions killing your performance.
This is where there is a bit of bait and switch with the JVM story (applies to a lot of JVM languages) - "language has great feature X" and "use any library from the JVM ecosystem" - often turn out to be mutually exclusive in a practical sense. Groovy is the only JVM language I've found that seems to deliver a real JVM interop story, but that's explicitly because it is designed from the ground up for that.
That's extreme. It really doesn't "kill" interop at all. If you want to convert, convert. There are many cases where you are not calling Java in hot paths and where converting is perfectly acceptable. If you don't want to, then don't and use Java collections directly. You have the best of both worlds depending on what kind of code base and libraries you are dealing with
Also, implicit conversions between Scala and Java collections have been discouraged/deprecated for years now. You should use the explicit conversion when needed, with `.asScala` or `.asJava`. This makes it obvious whether you might pay a price in the conversion.
Finally, your position overlooks the fact that there are solid reasons to use and prefer Scala collections instead of Java collections: they are quite feature-rich compared to Java collections, are immutable by default, while providing mutable collections if you want those as well.
> that's explicitly because it is designed from the ground up for that
Scala was designed for Java interop from the get go. I am not sure how you are suggesting making things better, besides saying that Scala should never have had its own collections. That would take away a huge part of what makes Scala great, in my book.
It's not in a "trivial sense". You can call any Java code from Scala, call Scala code from Java (except when dealing with Scala constructs that don't have direct Java equivalents). Even Java and Scala functions are the same since Java 8/Scala 2.12. I am not sure how much more interoperability you could even wish again, besides saying that Scala should be identical to Scala. And I write this having written Scala for 10 years and used dozens of Java libraries in Scala projects.
> because of its insistence on using its own collections
This reads like Scala is somehow doing this stubbornly against all reasonable arguments. That's not the case. Scala collections have benefits. Maybe this means using Hibernate is not straightforward from Scala. Ok. I don't know. But this doesn't invalidate Scala having its own collections.
There are tradeoffs in everything. We are talking about two languages and how they interoperate. As far as language integrations go, the Java-Scala interop is excellent, in my view.
Have you tried Kotlin yet? If so, what interferes with its JVM interop story in your opinion? I'm happily using Kotlin with several Java libraries, and IIRC, no Kotlin-specific libraries except for the stdlib.
Kotlin's approach amounts to doubling down on null IMO - it actually makes it harder to interoperate with newer JVM code that favours options over nulls (e.g. streams) from Kotlin than from Scala. And since "platform types" are silently treated as non-null, you still get null exceptions at runtime.
> - Scala defines its own set of collection classes, whereas Kotlin has zero-overhead wrappers over the JVM collection types.
Which means Kotlin has no actually immutable collections; a supposedly immutable collection can be silently mutated by another thread in parallel, which can be pretty surprising.
There are definitely things that Kotlin does well, but IME "smoother interop" is mostly a myth.
Tones of ad-hoc features isn't good language design imho, even the features are desirable in isolation. That's the same issue as the biggest problem with PHP or C++.
On the other hand side Scala may seem "complicated" on the surface but it's actually just a bunch of quite simple basic principles, and some syntax sugar on top.
Scala is like math: It's build form simple first principles. Just it offers the means to combine those and create this way almost arbitrary complex abstractions.
One (arguably) negative consequence of this is that Scala's collections don't play nice with Hibernate and similar ORMs. Due to the way they work, both Scala and Hibernate wanted to "take over" your collections, and obviously they cannot do so at the same time. Of course, it's arguable whether this is a flaw with Scala or with Hibernate, but for the programmers who just wants to get things done that's irrelevant.
It used to be a big deal a few years ago, to the point teams in my company decided to either drop Hibernate or drop Scala.
PS: if I remember correctly, early Scala adopters had reached the consensus that ORMs didn't play nice with Scala's FP style anyway, and so using Hibernate was a mistake. Possibly this attitude changed later.
With Doobie you manually write your queries, and then map the results into the objects in your domain model. Nothing is generated for you. OTOH, nothing is hidden and you are free to write queries as optimized and specialized as you need. The real selling point of Doobie is a typesafe API for manipulating and combining queries, and fragments of queries, into larger wholes. This works very well when your application interfaces with a database it doesn't own.
With Slick you get access to a DSL that lets you layout how your tables look. From there Slick offers an api that let's you treat SQL tables as-if they are basically mutable collections, with Slick handling all the SQL generation itself. You also get DDL, so that you can automate db creation and upgrades. This work very well when your application owns and controls the database it is connecting to.
Both of these have diverged from that traditional ORM model. Slick bills itself as FRM, or Functional Relational Mapping. And Doobie is embedded queries on steroids.
I don't remember what the problems were with Slick that leave such a bad feeling when I hear its name -- that was 5 years ago -- but I had problems with Quill just last year. I was trying to use it to generate an efficient "in" query against a two column composite primary key, and nothing seemed to work. Since it uses macro magic, one of my attempts triggered an internal compiler error instead of normal compiler feedback.
I ended up dropping Quill for ScalikeJDBC:
It seems to be less popular/active than other libraries, but it is dead simple to use, even for developers new to Scala. I write exactly the SQL I want just like I would in psql. There is little-to-no magic. I think that the only slightly magical feature I use is variable interpolation into SQL ("SQLInterpolation") that prevents injection attacks. It actually has capabilities to automatically map tables/columns into different structures and generate code for you, but my team doesn't use any of that. We just write SQL.
Having said that I think Quill looks nicer (https://getquill.io/) if you want a DSL like that.
Having used Hibernate in a Java project, I would argue using Hibernate is always a mistake. Way too much complexity/brokenness for basically no benefit.
I like null coalescing, but Scala uses a more generic way to handle nulls like Options. After working with both, I'm not sure which way I prefer. I do like not having to create new syntax for language features and could be handled with a monad. Async/await is another that can be handled with an IO, Future, ZIO, etc.
Kotlin also side steps the big standard library by using inline functions a lot (in addition to the mentioned advantage if extending existing Java classes).
To really appreciate Kotlin’s design, one should know some Scala.
Scala is positioned to write new code while using old java libs
Kotlin is positioned to write code/libs which can be shared with new java code
The theme is replacement vs co-existing!
I don't think the author fully comprehended the linked resource (namely https://clojurescript.org/about/differences). Perhaps he just noted the its size.
A good chunk of the document lists things in common, not differences. The actual differences have nothing to do with dynamic typing and are deliberate design decisions:
- don't pretend the JVM and JS have identical runtimes when it comes to concurrency or numerics. Clojure is a language, not a platform abstraction.
- Emit efficient, optimally minifiable javascript code, at the cost of offering something less traditionally lisp-y when it comes to eval, macros, and other forms of code loading. Those are still possible, but some discipline is imposed.
> - Emit efficient, optimally minifiable javascript code, at the cost of offering something less traditionally lisp-y when it comes to eval, macros, and other forms of code loading. Those are still possible, but some discipline is imposed.
This is exactly the kind of thing I'm talking about: Scala.js doesn't need to make these tradeoffs at all! Concurrency works identically (just no parallelism), numerics work identically, macros work identically. The list of pure-Scala things that behave differently in Scala.js is shockingly short (https://www.scala-js.org/doc/semantics.html)
Scala.js sacrifices none of these and still emits efficient (sometimes faster than idiomatic JS!), optimally minifiable javascript code. The fact that Clojure needs to make these tradeoffs and Scala doesn't is largely due to Scala's static nature allowing more aggressive analysis and optimisation at compile time. For example, the reason Scala.js can provide exact semantics for numerics while maintaining high performance is precisely because it knows what exactly types things are, and can use that information to optimize the generated JS in a semantics-preserving way (http://lampwww.epfl.ch/~doeraene/presentations/jslongs-vmm20...)
The big difference is the interop, and from your link with Scala.js it seems that's also true of it.
I'd say in practice, where ClojureScript and Scala.js maybe can differ a bit is that idiomatic Clojure itself relies a lot more on interop than Scala. So probably more Scala code doesn't depend on Java interop and thus can be more easily ported to Scala.js, whereas more Clojure code relies on interop making it more difficult to port to ClojureScript.
The other big difference is that Clojure supports runtime macros and eval, and those would require the compiler be bundled with your frontend app, which would increase the bundle size a lot. So ClojureScript needs to drop some of the dynamism. It's very rare to have Clojure code that actually depends on this though.
Overall in practice, what you'd want to share between backend and frontend is sharable as-is between Clojure and ClojureScript, which is all that's really needed. You'll build a React JS based frontend in ClojureScript, and share data-model, validation, transforms over it between backend and frontend. And you'll want your render functions to be shared between them as well so you can do server side hydration for React.
P.S.: I also want to point out there is a camaraderie between ClojureScript and Scala.js, and the devs exchange ideas, taking inspiration from one another as well. Since they face a lot of the same challenges.
How can those possibly be abstracted away and unified? If doing so, what would have the solution have to do with type inference at all?
i.e., this is simply a difference in how one approaches platform interop (raw vs abstracted/unified). Clojure occasionally offers cross-platform abstractions (e.g. core.async) but it's not its main philosophy.
How does this make a difference to the Clojure interface to threads?
After all, if you're running your JVM on hardware that doesn't support parallelism, you're not getting parallelism anyway.
> JS has a single type for representing numbers.
The comment you're replying to already covered that:
> For example, the reason Scala.js can provide exact semantics for numerics while maintaining high performance is precisely because it knows what exactly types things are, and can use that information to optimize the generated JS in a semantics-preserving way
> Clojure occasionally offers cross-platform abstractions (e.g. core.async) but it's not its main philosophy.
I can understand providing different libraries for different platforms, but language semantics should remain (as much as is possible), e.g. numerics.
Clojure futures, promises, and some of its other concurrency primitives have synchronous semantics. So they don't take callbacks, instead the calling thread blocks on the result when it's at the point where it needs to wait for it.
So that interface doesn't work with JavaScript's concurrency model. On the other hand, Scala futures have a asynchronous interface, and you specify a callback for success and error, and cannot block waiting for them, which happen to be 1:1 with the JavaScript promise interface.
That's why on ClojureScript you don't have access to those concurrency construct, since they just don't work within the JavaScript model.
The compiler maybe could have gone through amazing lengths to rewrite all use to an async one, but it just so happens there is already in Clojure something that does so, but it rewrites a CSP interface to an async concurrent model (not futures and promises). It's called core.async and this one exist in both Clojure and ClojureScript.
> I can understand providing different libraries for different platforms, but language semantics should remain (as much as is possible), e.g. numerics
Even Scala.js has some differences in numeric types they list in their page. There's always the possibility of mismatch of a language over some runtime. In ClojureScript I think it was planned to eventually bring the same numeric semantics over, but they started without, and it seems no one minded and then never bothered.
Scala Futures are more or less a 1:1 mapping to Javascript Promises, and work identically.
There isn't long running IO or sleeps, same JS, but this isn't a big problem in practice. As long as you're not juggling threads directly - and most Scala code doesn't - your code should run unchanged concurrency-wise in Scala.js just with no parallelism. Though of course you can't use OS interfaces like java.nio.file.Files in the browser!
I think in this case Clojure provides pretty strong competition with core.async - you do have to use the async specific calls for sleeping and channel IO but it still works using the same API on JVM Clojure and ClojureScript sides.
I remember years ago when I had a heated discussion about local mutable state. An that time it felt like heresy to even suggest it. I just recently had another discussion with the same programmer and he was like: "Yeah, that works".
Good times.
There are beautiful, simple, elegant ways to write Scala, but I'm afraid that the community isn't converging on a single concrete style, and a consensus around an abstract set of ideas about style isn't useful. Everybody agrees on words like "simple" and "elegant" and "readable," but I've seen some real abominations in Scala that people are tragically proud of. I'm afraid that Scala has attracted too many of the wrong kind of programmer, the kind that identifies their professional value with their ability to implement complex solutions that are intractable to their peers. The problem is not that you can't write simple code in Scala, but that people choose not to.
I don't want to blame the pure FP style of Scala itself, because I've seen the same problems in the "better Java" style as well, and because I'm still thinking that I might be able to write good code in this style and might even come to prefer it. But oh man, the Scala programmers I've personally worked with who embrace pure FP have really screwed up priorities.
However, things have definitely gotten a lot better now. There's a large contingent of "boring Scala" folks in the online community, and its growing. See this discussion https://www.reddit.com/r/scala/comments/lfbjcf/does_anyone_h... for example
AFAIK a lot of the wannabe-Haskell-programmers ended up transitioning fully to actual-Haskell-programmers too, which is probably a win-win that's good for everyone involved. Life's to short to use a language you hate, different strokes for different folks and all.
Scala teams also have a hard time hiring and if they hire a dev that’s new to Scala they then have to deal with a very long ramp up time.
Without going into how do you define a higher quality product, does quality really matter that much..? Most code is going to be re-written every few years anyway. Software engineers aren’t building architectural wonders that will last hundreds or maybe thousands of years. So is the complexity of a language like Scala really worth it, just to deliver a product that will be obsolete in a few years anyway?
The main argument against this is that it's a concession that makes the codebase less coherent. The more hardline argument being that the encapsulated mutable state is still more prone to bugs and should be almost always be avoided, except at great cost.
It’s rare that you need to use vars in Scala, but still if it’s scoped to the function then you know you can mentally discount it once the function terminates.
$ time scalac Hello.scala
real 0m2,907s
user 0m7,901s
sys 0m0,308s
$ cat Hello.scala
object Hello{
def main(args: Array[String]){
println("hello world")
}
}
Implicit type conversion can be debated I guess, but otherwise a very beautiful language.Standard use case is to use sbt (well, or mill, since we're in lihaoyi's thread :D). Sbt starts up slow, but with bloop, or sbtn (native client of sbt), the compilation is really fast (much faster than C++ for example):
$ time sbtc compile
[info] entering *experimental* thin client - BEEP WHIRR
[info] terminate the server with `shutdown`
> compile
[info] compiling 1 Scala source to /home/.../target/scala-2.13/classes ...
[info] compile completed
[success] Total time: 0 s, completed Feb 11, 2021 4:21:51 PM
sbtc compile 0,08s user 0,02s system 21% cpu 0,492 totalThey did that with a YouTube link sometime before [1]. Stopped after 8 failed submissions.
I'm not sure about the JIT part though. Aren't many of the recent language success stories now about AOT compiled languages like Go, Rust and Swift?
A strongly typed language like Scala has the potential for massive optimizations under the whole program optimization umbrella...and no JIT would ever dare to try those types of optimizations, because they are extremely memory and compute intensive. They'd have to pause execution for minutes at a time just to figure out what to do.
I think it is unfortunate that Scala was designed for the JVM first. It brought a lot of syntactic baggage for interop purposes, and it saddled it with weird things like reflection and dynamic class loading, making it hard to statically compile. The ideas behind scala's type system are extremely powerful and could have fundamentally changed programming for the better, but all we got was a JVM me-too that was quickly usurped by Kotlin. I don't think it is dead or a lost cause...but the legacy that the JVM has on Scala is one that slowed it down, not sped it up (like what was claimed would happen with JVM languages).
But I do agree that Scala in the early years did have some ugly artifacts from compiling to the JVM and trying to remain a “good citizen“ there, not sure about current trends.
I haven’t had much opportunity to use Scala, but I did take Martin’s Functional Programming with Scala course years ago, and that was great.
In Scala everything is an expression, meaning everything evaluates to some result even if that result is void.
In swift everything is a statement. There’s a big difference because statements don’t return things, only functions do.
It sounds minor, especially since swift has pattern matching and first class supper for functions etc, but the truth is these are all improvements over a more oop (in the Java sense) way of programming. They don’t however make the languages very similar because at a more basic so level they’re very different.
I would even go as far as to say that rust is more like Scala than swift is, although it’s been a while since I coded in rust.
That sounds absolutely awful.
To me, Swift and Kotlin look like they have taken inspiration from Scala. I think Kotlin explicitly did. That idea transfer from one area to another is how we get better langauges.
The common ancestor is OCaml, and the ML languages in general.
To be fair, that may change as Swift adopts Actor based concurrency.
Scala also has Scala Native which, well, compiles to native. It's not yet as polished as Scala JVM and Scala.js, but the plan is to make it so.
Scala needs a new "official stack", hopefully Mill + utest/Munit + scalafmt (with opinionated settings).
Scala 3 + SBT + Scalatest + whatever formatting isn't going to grab folk's attention.
Maybe so... but so do Python or Javascript. It seems to me some other languages have only one stack (rust: cargo, rustfmt...; the .net world as well). Does it make them better? (honest question). Then again, what's Java official stack?
The only problem is, once you've tried out Rust, it will be difficult for you to come back to Scala.
I no longer program in Scala, but I used to for my previous job and I regularly came across build files I couldn't understand -- and when I asked the person who wrote them, they couldn't explain their build properly either.
And this happened in enough cases that I wouldn't consider it an exception or a problem with a particular developer. SBT used to have two entirely separate syntaxes, and it was a mess translating from one to the other. I don't know if this is still the case (I know one of the two syntaxes got deprecated, can't remember which one now).
The proponents of the approach that sbt takes have failed to realise that build tools have zero value whatsoever, every second spent on them is a second wasted.
What you want is a good mix simplicity (yes copy paste is fine) and some basic examples to follow, and the ability to break out into script if you are doing something unusual.
Especially for those coming from languages like Python and Go, where the former has no build system (ignoring setup.py), or an almost universal one (go build, sometimes make).
All of the projects I have touched are documented using sbt, and transitioning the build process for existing code projects while a new language learner is a double cognitive load.
If you're dealing with dependencies only, then it's a straightforward change. OTOH I've never had an issue with SBT since it's cleanup a couple of years back so YMMV.
If you're not, the documentation is missing as you say. Maven does just work, but the documentation assumes you're using Java.
You use alternatives at your own peril. This means you won't be able to understand other people's builds, for example.
I know some Scala projects use Maven instead. Whether that's a good idea is debatable!
The tradeoff is with SBT, you won't be able to understand your own builds!
(This is kind of a joke, but not really...)
That's gonna be hard to beat for most languages I guess, the best IntelliJ support will most likely always go to Kotlin :-)
Jetbrains knows its main source of income is still, by far, Java, so they don't drop that ball.
val map = hashMapOf( "John" to "Doe", "Jane" to "Smith" )
Seems pretty non-ridiculous to me. Maybe you were using an old version? I guess you can switch now :)
My company uses Scala a lot with with these ecosystems (my team is on Typelevel, another one is on ZIO) and in fact Spark and Akka is what we're trying to avoid as much as possible. Once I got used to the composable nature of FP and static types - both Spark and Akka just feel clumsy and unintuitive, like maintainers of Spark chose Scala because it was a "Java with fancy syntax" back then, ignored most of benefits of static types, HKT, immutability, type classes etc. If there's future behind Scala - it definitely doesn't lie in Apache land. Akka is slightly different and probably has higher potential, but just an overkill for most of use cases it's advertised for.
scala is real big impediment to making data processing accessible to general public in your company. order of preference now at my company is,
1. sql 2. pyspark 3. java spark 4. scala spark
eg: shopify found that 70% of their pyspark could be converted to just sql https://shopify.engineering/build-production-grade-workflow-...
Here's a more detailed PySpark vs Scala comparison in case folks are interested: https://mungingdata.com/apache-spark/python-pyspark-scala-wh...
I think Scala Spark (using 10% of the language features) is a better technical decision (because it provides huge benefits like fat JARs, shading, better text editor support, etc), but the worse overall choice for most organizations because people are generally terrified of Scala.
They'd rather do nothing than write Scala code. I can empathize with their position.
Ding Ding Ding! Presto/Athena now is becoming huge in BI ecosystem. We don't really use Spark for ad-hoc BI anymore, we use it for data science and large repetitive workload.
I'll counter that if you add a type checker to one of these languages, such as TypeScript (for JavaScript) or mypy (for Python), then all of those problems go away.
I'm personally particularly excited about the possibilities unlocked in the Python community by type checking, which I wrote about recently here: https://dafoster.net/articles/2021/01/26/python%27s-type-che...
Additionally, RE "Poor performance" the author is presumably talking about CPU-bound performance. If you happen to be writing network-based or I/O bound programs, which you always are in JS/TypeScript and sometimes are in Python (for web applications), then the effective performance is more affected by the network/database/disk than the programming language.
Then, creating a good typesystem is really really hard. Even professional language designers struggle with that. Take Martin Odersky, the creater of Scala. He didn't just invent Scala, no, he learnt under his professor, then created pizza-lang and did other experiments and worked with other languages (e.g. he was the one who added generics to Java) before he used all his expertise to create Scala. And even then mistakes were made.
You can see it from the language. I work with both Scala and python (with type annotations) and you can see that one is created by a specialized professional, where the other one is created by smart people but not with the same sophistication.
> Additionally, RE "Poor performance" the author is presumably talking about CPU-bound performance.
He probably does, but these languages are most often also better for IO-bound programs. The reason is that writing asynchronous and concurrently executed code is very difficult and pretty much adds another layer of problems on top of everything someone does. Languages with static types (not only Scala) can leverage a big advantage here, because the compiler can help you to indicate what is synchronous and what isn't.
Scala certainly has no shortage of these people, but at my work we throw senior engineers with zero Scala experience or training at the language and it generally turns out OK. Turns out that a 10x faster Python or a 10x more concise Java are selling points enough, even without any fancy code!
You can move fast with safety with the caveat that the code quality is harder to manage/control.
Contrasting this with golang where they really take the guard rails to the other extreme (e.g. can't compile if a variable is unused, not even compiling just to test it out locally).
Another weak-point of Scala is that the learning curve is steep, especially when it involves SBT, the defacto build system for Scala.
In fact, a lot of it is putting in thr guardrails people like from Go: enforced autoformatting, banning unused variables, fatal warnings, etc. are all just a flip of a switch these days (though we only enforce these things on-merge, people are free to test locally regardless, which I understand is a big pain point in Go!)
Are there any studies comparing real world impact of structural vs nominal typing?
Defects? Code size? Team size? Architectural choices, big and small, like use of Visitor design patterns?
I've looked, but no joy.
--
FWIW, metaprogramming should be reserved for personal projects and small high trust teams. Definitely not for bog standard data processing and CRUD apps.
I'm kinda curious how structural typing story plays out in this dichotomy.
From the time I've spent working with Typescript (Probably the most mainstream with with structural types), I don't think structural-by-default is the right approach if we are to design a language for safety & productivity. The error messages are a lot worse than nominal typing, and useful patterns like new type are either not safe or has a runtime cost. [1]
I suspect nominal first with good structural typing support from the compiler is where the sweet spot is, but to integrate it nicely in any language used today is probably an open research question.
Regarding your main point, I agree, compiled languages do not necessarily have complicated build setups. But maybe it is tainted by my experience in this field.
That's an interesting way to put it (and pretty neat), but I guess I'm not totally convinced it is that useful in practice. I saw the linked example about the parser using implicit objects but I think that could be rewritten in a number of different ways without relying on that feature.
The problem is that I use Python as a “glue language” and heavily rely on excellent third party libraries. I don’t want to learn how to write a minimum spanning theorem algorithm; I’m used to have it next to several other convenient graph utilities in networkx. There’s so much stuff for Python — where else will you find something like giotto-tda?
I’m not gloating about Python, I feel stuck.
As for concrete languages, I like Java, because it has just enough abstraction to make it useful and is not overly complex. Scala is a real gem in that it was one of the first OOP+FP hybrid, a marriage that seems really fruitful since then basically every OOP lang introduced more and more FP concepts and vice versa. There is also Clojure if you are into LISPs. If overly functional programming is not your thing, Kotlin is a modern language with some additional features to Java though I am not particularly fond of it. And there are even more niche languages running on top of it.
But the real gem is the JVM itself, and it is really actively developed and many new, exciting features are in the works.
Java is still light years behind in terms of productivity through language features. (however, therefore it has higher backwards compatibility, which also helps productivity)
* It claims to be a compiled language "like C++ and Java" - not making a distinction between targeting multiple hardware platforms and targeting a cozy VM.
* It ties itself strongly to the Java ecosystem.
So, I guess I might consider it in the 'nicer Java' category - and it does seem nicer than Java, subjectively - but not outside of that ecosystem.
- JVM
- JavaScript (Scala.js)
- native (Scala Native)
The tie to the Java ecosystem has significantly loosened over the last few years.
> The last thing that Scala does well is to lean heavily on the host language for both its language semantics as well as its implementation. Scala is typically run on the JVM, which together with the Java ecosystem provides a host of useful things that Scala doesn't need to worry about: etc. etc.
So, what do you do with Scala when you don't use the JVM at all, nor any Java libraries?
Scala has multiple host languages, and it leans heavily on each of then whenever used on that platform
For example I am now cross-compiling JVM/JS a fairly large project which uses, directly or indirectly, Circe, Cats, ScalaTest, Enumeratum, Shapeless, and Parboiled2, and all of that works flawlessly.
Why not? is the mechanic stupid? the 8-in-1 screwdriver is marvelous, has the DOUBLE of features!
At the end of the day, he has only the tools he need for his job, and thats it.