- PVSM.RU - https://www.pvsm.ru -
Наша команда, аналогично с автором статьи, уже почти как год перешла со Scala на Kotlin в качестве основного языка. Мое мнение во многом совпадает с автором, поэтому предлагаю вам перевод его интересной статьи.
Прошло прилично времени с того момента как я не обновлял блог. Вот уже как год я перешел со Scala, моего основного языка, на Kotlin. Язык позаимствовал много хороших вещей, которые мне нравились в Scala, сумев при этом избежать многих подводных камней и неоднозначности, которая есть в Scala.
Ниже я хочу привести примеры, которые мне нравятся в Scala и Kotlin, а также их сравнение в том, как они реализованы в обоих языках.
Что мне особенно нравится в обоих языках так это то, что они оба являются статически типизированными с выведением типов. Это предоставляет вам возможность в полной степени воспользоваться мощью статической типизации без громоздких объявлений в коде (ориг.: declarative boiler plate). В большинстве случаев это работает в обоих языках. В обоих языках также прослеживается предпочтение неизменяемым типам вместе с опциональным объявлением типа переменной после ее названия.
Пример кода будет одинаковый в обоих языках:
Объявление неизменяемой переменной с именем age и типом Int:
val age = 1
Объявление изменяемой переменной с типом String:
var greeting = "Hello"
Оба языка поддерживают лямбда функции как объекты первого класса, которые могут быть присвоены переменным или переданы в качестве параметров функциям:
Scala
val double = (i: Int) => { i * 2 }
Kotlin
val double = {i: Int -> i * 2 }
Scala и Kotlin имеют схожий концепт data классов, которые являются представлением data model object.
В Scala это case классы, которые выглядят следующим образом:
case class Person(name: String, age: Int)
Kotlin называет данные классы как data class
data class Person (val name: String, val age: Int)
Ключевые особенности:
В Kotlin нет необходимости в специальном apply методе, также как и не нужно ключевое слово new для инициализации класса. Так что это стандартное объявление конструктора как и для любых других классов.
В основном case и data классы похожи.
Пример ниже выглядит одинаково в обоих языках:
val jack = Person("jack", 1)
val olderJack = jack.copy(age = 2)
В целом я нашел data и case классы взаимозаменяемым в повседневном использовании. В Kotlin есть некоторые ограничения на наследование data классов, но это было сделано из благих намерений c учетом реализации equals и componentN функций, чтобы избежать подводных камней.
В Scala case классы более мощные в pattern matсhing по сравнению с тем как Kotlin работает с data классами в ‘when’ блоках, в которых этого не хватает.
Подход Kotlin работает лучше для существующих Java фреймворков, т.к. они выгдядят для них как обычные Java bean.
Оба языка позволяют передавать параметры по имени и позволяют указать значению по умолчанию для них.
В Scala null safely заключается в использовании монады option. Проще говоря, option может находится в одном из двух конкретных состояний: Some(x) или None
val anOptionInt: Option[Int] = Some(1)
или
val anOptionInt: Option[Int] = None
Можно оперировать с option при помощи функций isDefined и getOrElse (чтобы указать значение по умолчанию) но более часто используемая ситуация когда монады используется с операторами map, foreach или fold, для которых option представляет из себя коллекцию содержащую 0 или 1 элемент.
Для примера можно подсчитать сумму двух опциональных переменных следующим образом:
val n1Option: Option[Int] = Some(1)
val n2Option: Option[Int] = Some(2)
val sum = for (n1 <- n1Option; n2 <- n2Option) yield {n1 + n2 }
В Переменной sum будет значение Some(3). Наглядный пример того как for может быть использован как foreach или flatMap в зависимости от использования ключевого слова yield.
Другой пример:
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 person is age 1"
Kotlin заимствует синтаксис groovy, достаточно практичный в повседневном использовании. В Kotlin все типы non-nullable и должны быть в явном виде объявлены nullable с помощью "?" если они могут содержать null.
Тот же пример может быть переписан следующим образом:
val n1: Int? = 1
val n2: Int? = 2
val sum = if (n1 != null && n2 != null) n1 + n2 else null
Это намного ближе к Java синтаксису за исключением того, что Kotlin принудительно выполняет проверки во время компиляции, запрещая использовать nullable переменные без проверки на null, так что можно не бояться NullPointerException. Также нельзя присвоить null переменной объявленной как non-nullable. Помимо всего компилятор достаточно умный, чтобы избавить от повторной проверки переменной на null, что позволяет избежать многократной проверки переменных как в Java.
Эквивалентный Kotlin код для второго примера будет выглядеть следующим образом:
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}")
}
Или альтернативный вариант с использованием "let", который заменает "if" блок на:
person?.age?.let {
person("The person is age $it")
}
Я предпочитаю подход в Kotlin. Он гораздо более читабельный и понятный, и проще разобраться что происходит в многократных вложенных уровнях. Подход Scala отталкивается от поведения монад, который конечно нравится некоторым людям, но по собственному опыту могу сказать, что код становится излишне перегруженным уже для небольших вложений. Существует огромное количество подводных камней у подобного усложнения в использовании map или flatMap, причем вы даже не получите предупреждение при компиляции, если вы делаете что-то не так в мешанине из монад или используя pattern match без поиска альтернативных вариантов, что в результате выливается в runtime exception которые не очевидны.
Подход в Kotlin также уменьшает разрыв при интеграции с кодом Java благодаря тому что типы из нее по умолчанию nullable (тут автор не совсем корректен. Типы из Java попадают в промежуточное состояние между nullable и not-nullable, которое в будущем можно уточнить), тогда как Scala приходится поддерживать null как концепт без null-safely защиты.
Scala, конечно, поддерживает функциональный подход. Kotlin чуть в меньшей степени, но основные идеи поддерживаются.
В примере ниже нет особых различий в работе fold и map функций:
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 }
Оба языка поддерживают концепт цепочки "ленивых" вычислений. Для примера вывод 10 четных чисел будет выглядеть следующим образом:
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())
Эта та область в которой Scala и Kotlin немного расходятся.
В Scala есть концепция implicit преобразований, которая позволяет добавлять расширенный функционал для класса благодаря автоматическому преобразованию к другому классу при необходимости. Пример объявления:
object Helpers {
implicit class IntWithTimes(x: Int) {
def times[A](f: => A): Unit = {
for(i <- 1 to x) {
f
}
}
}
}
Потом в коде можно будет использовать следующим образом :
import Helpers._
5.times(println("Hello"))
Это выведет "Hello" 5 раз. Работает это благодаря тому, что при вызове функции "times" (которая на самом деле не существует в Int) происходит автоматическая упаковка переменной в объект IntWithTimes, в котором и происходит вызов функции.
Kotlin использует для подобного функционала extension функции. В Kotlin для того, чтобы реализовать подобный функционал нужно объявить обычную функцию, только с префиксом в виде типа, для которая делается расширение.
fun Int.times(f: ()-> Unit) {
for (i in 1..this) {
f()
}
}
5.times { println("Hello")}
Подход Kotlin соответствует тому как я в основном использую данную возможность в Scala, с небольшим преимуществом в виде чуть более упрощенной и понятной записи.
Одна из лучших особенностей Kotlin для меня даже не в том функционале что есть, а больше в том функционале которого нет в Kotlin из Scala.
Спасибо за внимание.
Оригинал Scala vs Kotlin [1]
P.S. В некоторые местах в переводе специально оставил слова без перевода (null, null safely, infix, postfix и т.п.).
Автор: nerumb
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/179180
Ссылки в тексте:
[1] Scala vs Kotlin: https://agilewombat.com/2016/02/01/scala-vs-kotlin/
[2] Источник: https://habrahabr.ru/post/308562/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.