Scala is a pretty simple, concise and coherent language though. Never understood why people deem it has too many features, I write it professionally and never felt so.
At least comparing it to C, C++, Python. Java may be more simple, but you have tons of features added with metaprogramming via dozens of annotations generating lots and lots of boilerplate, Lombok and Spring are examples of that.
Java codebases are way scarier than Scala.
I'll start with one (key)word: `implicit`
I agree with your distaste for metaprogramming though.
import io.circe.syntax._
List(1, 2, 3).asJson
Where `asJson` requires an instance of an `Encoder`[1] and this Encoder can be derived with the help of implicits.For you as a normal user, the two common places where you might use implicits are (1) for implicit classes providing syntactic sugar:
// original
def doSomething(a: A): B = ???
val a: A = ???
val b = doSomething(a)
// with implicits
implicit class AImplicits(a: A) {
def doSomething: B = ???
}
val a: A = ???
val b = a.doSomething
and for (2) implicit conversions. These are a foot-gun, so should be used in limited circumstances. At work, we use case classes in data pipelines then convert these to avro classes on save; there's lots of ways to do this, but as an example if you have an `Optional[Int]` and your avro constructor requires a nullable java `Integer` then `JavaConverters` won't save you and you'll need something like: implicit def optIntToInteger(optI: Option[Int]): java.lang.Integer = optI.map(Int.box).orNull
[1] https://circe.github.io/circe/api/io/circe/syntax/package$$E...They are changing implicits in Scala 3 though with the "given" keyword which is more ergonomic.
Odersky called the approach for Scala 3 "intent over mechanism."
Are you suggesting to use scala without using for comprehensions? Or do you mean you don’t need to write your own?
It’s been a long time since I wrote scala, so may be getting it wrong.
I don't think implicit counts as "too many features" or as something complex. Basically all it does is finding a canonical value in scope for a hole of certain type.
Either way, the local Scala guru there said the Scala community was starting to get over implicits.
At some point it was true, but nowadays the problem is solved because IDEs will show you were an implicit is used and where it comes from. Without that, it was indeed more difficult - but also not more difficult than Java's reflection or DI (where you had and have even less help).
Yeah, but don't you feel the same pain when using Spring's autowiring? Or python's (or C++ template) default arguments? Or dynamic method dispatching in any OO language, where you don't know what method you actually call? Or late bindings?
I don't see how implicits' implicitness is significantly worse than any other kind of implicitness common in other language.
Default params are generally fine since they're hidden on the implementation side but explicit if they vary at the caller site.
And even dynamic dispatch I try to use carefully and sparingly. I just like really explicit programs.
I see. My point was that such things are pretty common in most contemporary languages and I don't see how Scala is special in this regard.
Yes, unlike Go or Java, implicits are part of the language spec and not some metaprogramming magic on top of it, but I'm not sure it's a bad thing.
But why do you think Kotlin is easier to integrate than Scala? Both work on JVM.
If you want to assert that finding Kotlin developers is easier or that Kotlin is an easier language to learn, sure, that might be the case (I really don't know), but that's not really an integration task.
The real difference is that more of the Kotlin ecosystem uses Java's fundamentally mutable collections compared to the Scala ecosystem, and using actually immutable collections in Kotlin is extremely difficult to the point that essentially no-one does it. IMO that's a bad tradeoff in the long term, but you can absolutely take the same approach in Scala if you really want to.
https://www.scala-lang.org/api/2.13.5/scala/jdk/javaapi/Coll...
One other benefit of that is you maintain object identity. I don't think that Scala's wrappers do that.
But it also has Scala collections. With scala collections you get the full power of the Scala type system, as well as a much richer and full featured collections api. So most scala programmers won't bother with java collections unless they have specific java interop requirements.
The Scala adapters are merely ways of converting java collections to scala collections and vice versa.
The problem is that those java collections suck in comparison to the Scala collections. So Scala programmers prefer to use Scala collections. Scala programmers would never willfully use java collections if they don't have to, and if they absolutely have to, they have minimal overhead conversions back and forth. The minimal conversion overhead is the price they're willing to pay to use better collections while maintaining java interoperability.
Kotlin:
listOf(1,2,3,4)
.map{i -> i + 1}
.filter { it % 2 == 0 }
.flatMap { listOf(it, it * 2, it * 3) }
Kotlin even has a similar take to Scala's views, which they call sequences: listOf(1,2,3,4)
.asSequence()
.map{i -> i + 1}
.filter { it % 2 == 0 }
.flatMap { listOf(it, it * 2, it * 3) }
.toList()
Is this really so "lacking in capability and grace" compared to Scala? The only think missing is persistent immutable collections, which are implemented in kotlinx.You said:
> You have to litter your whole code with `.asScala` and `.asJava`, the java collections don't work with for comprehensions, etc.
So how does kotlinx achieve zero-cost compatibility with Java collections without having something like `.asKotlin` and `.asJava`? Because otherwise there is no difference between Scala and Kotlin in this regards.
import java.util.List;
public class Foo {
public static void blah(List<Integer> list) {
}
}
Kotlin: import kotlinx.collections.immutable.persistentListOf
fun main(): Unit {
Foo.blah(persistentListOf(1,2,3))
}
This compiles fineIf I have to use Java, I put it into a separate sub-project and make sure that I have a nice API to interface between the subprojects. `.asJava` and `.asScala` then only appear at very specific places where the interop happens.
Or an other way of saying it is that your approach of walling off modules as either java-collection or scala-collection is kind of like saying "Java and Scala collections work well together, so long as you don't have to use them together too much. If you minimize how much they need to interoperate, the problem is not so bad."
Yes, that's actually a very good way to rephrase it. That being said; I myself wouldn't limit that to just the collections; Java and Scala on the language level have excellent interop. For Scala-the-ecosystem the story is a bit different.
Java is not the primary target for most of the Scala libraries. Not even a secondary one, I dare say. (I'm not even sure how you would model a Java API for a library - say; Cats - that uses implicits for the heavy lifting.)
That of course stems for the fact that Scala makes use of concepts that have no correspondence in neither Java or Kotlin, so there will necessarily be something lost in translation.
Spring is about half the server side JVM ecosystem. Android represents a good chunk of frontend usage. Kotlin is a first class citizen for both; Scala just isn't. It has its own frameworks of course but they are kind of niche in comparison.
I think it's great that Java is slowly evolving to have features that other languages have. Kotlin supports Java's records as of this week's 1.5.0 release via an annotation. Meaning that if you have Java code that needs to interact with Kotlin code, you can write a data class that from the Java side looks like a record if you put the right annotation on it. It's a compatibility feature that's only relevant if you are planning to use or support Java. Another notable feature that landed with this week's release include sealed interfaces (it already had sealed classes). You can use both with data classes of course.