Our team, similarly to the author of the article, has been moving from Scala to Kotlin as the main language for almost a year. My opinion largely coincides with the author, so I offer you a translation of his interesting article.
It has been a decent time since I did not update the blog. Already a year, I switched from Scala, my main language, to Kotlin. The language borrowed a lot of good things that I liked in Scala, while managing to avoid many of the pitfalls and ambiguity that Scala has.
Below I want to give examples that I like in Scala and Kotlin, as well as their comparison in how they are implemented in both languages.
What I particularly like in both languages is that they are both statically typed with type derivation. This gives you the opportunity to fully utilize the power of static typing without bulky code declarations ( orig .: declarative boiler plate ). In most cases this works in both languages. Both languages also show a preference for immutable types along with an optional variable type declaration after its name.
Sample code will be the same in both languages:
Declaration of an immutable variable with the name age and type Int :
val age = 1
Variable variable declaration of type String :
var greeting = "Hello"
Both languages support lambda functions as first-class objects that can be assigned to variables or passed as parameters to functions:
Scala
val double = (i: Int) => { i * 2 }
Kotlin
val double = {i: Int -> i * 2 }
Scala and Kotlin have a similar concept of data classes, which are representations of the data model object .
In Scala, these are case classes that look like this:
case class Person(name: String, age: Int)
Kotlin calls these classes as data class
data class Person (val name: String, val age: Int)
Key features:
In Kotlin, there is no need for a special apply method, nor is the need for the new keyword to initialize a class. So this is a standard constructor declaration like any other class.
Basically the case and data classes are similar.
The example below looks the same in both languages:
val jack = Person("jack", 1) val olderJack = jack.copy(age = 2)
In general, I found the data and case classes interchangeable in everyday use. In Kotlin there are some restrictions on the inheritance of data classes, but this was done out of good intentions with regard to the implementation of the equals and componentN functions in order to avoid pitfalls.
In the Scala case, classes are more powerful in pattern matshing compared to how Kotlin works with data classes in 'when' blocks that lack this.
The Kotlin approach works better for existing Java frameworks, since they look like normal Java beans to them.
Both languages allow you to pass parameters by name and allow you to specify a default value for them.
The Scala is null safely to use the monad option . Simply put, option can be in one of two specific states: Some (x) or None.
val anOptionInt: Option[Int] = Some(1)
or
val anOptionInt: Option[Int] = None
You can operate with option using the isDefined and getOrElse functions (to indicate the default value), but the more frequently used situation is when monads are used with map , foreach or fold operators, for which option is a collection containing 0 or 1 element.
For example, you can calculate the sum of two optional variables as follows:
val n1Option: Option[Int] = Some(1) val n2Option: Option[Int] = Some(2) val sum = for (n1 <- n1Option; n2 <- n2Option) yield {n1 + n2 }
In Variable sum will be the value Some (3) . An illustrative example of how for can be used as foreach or flatMap , depending on the use of the yield keyword.
Another example:
case class Person(name: String, age: Option[Int]) val person: Option[Person] = Some(Person("Jack", Some(1))) for (p <- person; age <- p.age) { println(s"The person is age $age") }
The line "The person is age 1" will be printed.
Kotlin borrows groovy syntax, quite practical in everyday use. In Kotlin, all types are non-nullable and must be explicitly declared nullable using "?" if they can contain null .
The same example can be rewritten as follows:
val n1: Int? = 1 val n2: Int? = 2 val sum = if (n1 != null && n2 != null) n1 + n2 else null
This is much closer to the Java syntax, except that Kotlin enforces compile-time checks, preventing nullable variables from being used without being null -checked, so you should not be afraid of a NullPointerException. Also, you cannot assign a null to a variable declared as non-nullable . In addition, the compiler is smart enough to avoid null re-checking of a variable, which avoids multiple checking of variables like in Java.
The equivalent Kotlin code for the second example will look like this:
data class Person(val name: String, val age: Int?) val person: Person? = Person("Jack", 1) if (person?.age != null) { printn("The person is age ${person?.age}") }
Or an alternative using "let", which replaces the "if" block with:
person?.age?.let { person("The person is age $it") }
I prefer the approach in Kotlin. It is much more readable and understandable, and it is easier to understand what happens in multiple nested levels. The Scala approach is repelled by the behavior of monads, which of course some people like, but from my own experience I can say that the code is becoming too overloaded for small investments. There are a huge number of pitfalls in this complication in using a map or flatMap , and you won’t even get a warning when compiling if you do something wrong in a monad jumble or using pattern match without searching for alternatives, which results in runtime exception which are not obvious.
The approach to Kotlin also reduces the gap when integrating with Java code due to the fact that the types of it are nullable by default (the author is not entirely correct here. Types of Java fall into an intermediate state between nullable and not-nullable, which can be refined in the future ), while Scala has to support null as a concept without null-safely protection.
Scala, of course, supports the functional approach. Kotlin is slightly less so, but the main ideas are supported.
In the example below, there are no particular differences in the work of the fold and map functions:
Scala
val numbers = 1 to 10 val doubles = numbers.map { _ * 2 } val sumOfSquares = doubles.fold(0) { _ + _ }
Kotlin
val numbers = 1..10 val doubles = numbers.map { it * 2 } val sumOfSquares = doubles.fold(0) {x,y -> x+y }
Both languages support the concept chain of "lazy" calculations. For example, the output of 10 even numbers will be as follows:
Scala
val numbers = Stream.from(1) val squares = numbers.map { x => x * x } val evenSquares = squares.filter { _%2 == 0 } println(evenSquares.take(10).toList)
Kotlin
val numbers = sequence(1) { it + 1 } val squares = numbers.map { it * it } val evenSquares = squares.filter { it%2 == 0 } println(evenSquares.take(10).toList())
This is the area in which Scala and Kotlin diverge a little.
Scala has the concept of implicit transformations, which allows you to add extended functionality to a class thanks to automatic conversion to another class if necessary. Example ads:
object Helpers { implicit class IntWithTimes(x: Int) { def times[A](f: => A): Unit = { for(i <- 1 to x) { f } } } }
Then the code can be used as follows:
import Helpers._ 5.times(println("Hello"))
This will output "Hello" 5 times. This works due to the fact that when you call the function "times" (which actually does not exist in Int), the variable is automatically wrapped into an IntWithTimes object, in which the function is called.
Kotlin uses extension functions for this functionality. In Kotlin, in order to implement this functionality, you need to declare a regular function, only with a prefix in the form of the type for which the extension is being made.
fun Int.times(f: ()-> Unit) { for (i in 1..this) { f() } }
5.times { println("Hello")}
The Kotlin approach corresponds to how I mainly use this feature in Scala, with a slight advantage in the form of a slightly more simplified and understandable record.
One of the best features of Kotlin for me is not even in the functionality that is, but more in the functionality that is not in Kotlin from Scala.
Thanks for attention.
Original Scala vs Kotlin
PS In some places in the translation specifically left the words without translation (null, null safely, infix, postfix, etc.).
Source: https://habr.com/ru/post/308562/
All Articles