Комментарии 48
Понятно, что в официальном блоге JetBrains такого прямого противопоставления не будет, но от коммьюнити хотелось бы четкого списка пунктов «Почему Kotlin, а не Scala».
Насколько я понимаю, в Scala недостаточно хорошо продумана обратная совместимость — как между версиями самой Scala, так и интероперабельность из Java в Scala, и наоборот. Например, те же properties в Scala не используют геттеры и сеттеры по умолчанию, а для совместимости нужно использовать какие-то дополнительные аннотации. Как на уровне байткода будут выглядеть геттеры и сеттеры в Kotlin?
С функциональщиной тоже порой коммьюнити Scala несет слишком далеко — невозможно разобрать, что все эти хитрые операторы и комбинации на самом деле означают. Как итог — высокий порог вхождения.
Как со всем этим делом в Kotlin? Правильно ли я понимаю, что стараются устранить недостатки Scala, и это и есть основная киллер-фича?
Насколько я понимаю, в Scala недостаточно хорошо продумана обратная совместимость — как между версиями самой Scala, так и интероперабельность из Java в Scala, и наоборот. Например, те же properties в Scala не используют геттеры и сеттеры по умолчанию, а для совместимости нужно использовать какие-то дополнительные аннотации. Как на уровне байткода будут выглядеть геттеры и сеттеры в Kotlin?
С функциональщиной тоже порой коммьюнити Scala несет слишком далеко — невозможно разобрать, что все эти хитрые операторы и комбинации на самом деле означают. Как итог — высокий порог вхождения.
Как со всем этим делом в Kotlin? Правильно ли я понимаю, что стараются устранить недостатки Scala, и это и есть основная киллер-фича?
+1
UPD. На официальном сайте есть краткое сравнение: https://kotlinlang.org/docs/reference/comparison-to-scala.html
0
Основная киллер-фича котлина, что они не пытаются делать киллер-фич, а нацелены на разработку простого и удобного инструмента для реальной разработки. Конечно, можно пытаться сравнивать скалу и котлин, и в каждом посте обязательно про это бывает, но лично я не вижу большого смысла про это много писать — в статье по этому поводу раздел "Простой и совместимый". Это же и главные отличия от скалы, на мой взгляд.
+9
Как на уровне байткода будут выглядеть геттеры и сеттеры в Kotlin?
Можно написать пару маленьких программок и декомпилировать.
геттеры и сеттеры будут реализованы как методы
getVarName()
и setVarName(...)
Но мне синтаксис Scala кажется более простым. В kotlin больше сущностей и ключевых слов.
Например, для переопределения
+=
в scala достаточно создать метод с очевидным названием "+=", причём нет никаких ограничений на тип и количество аргументов — это самый обычный метод, а не какая-то дополнительная сущность.В kotlin придётся написать в стиле
operator fun plusAssign
, что не совсем интуитивно (приходится вспоминать, что и как называется), и есть ограничения — метод не может возвращать значение.Причём в kotlin можно переопределить только небольшой список операторов, а в scala свободы побольше (например, можно определить := или ++=).
На самом деле, scala-метод типа += будет создан с именем "$plus$eq", которое не очень удобно вызывать из java. Было бы идеально, если бы в kotlin стало можно давать методам имена типа "+", которые на самом деле отображались бы в "plus" и.т.п.
Или, например, синтаксис объявления метода:
В scala:
def methodName(...) = {...}
В kotlin возможны два варианта — как в scala (со знаком =) и как в java (без него), но эти два способа объявления неэквивалентны друг другу и работают немного по-разному, я однажды кучу времени потратил на поиск такой "особенности" в коде.
Null-safety: интересная штука, но на практике во все публичные методы добавляются проверки для каждого аргумента на null. Мне кажется несколько избыточным — и есть подозрение, что это негативно влияет на производительность. (Там не просто проверка, а вызов статического метода с передачей объекта-параметра и его имени как строки, чтобы можно было кинуть понятное исключение)
Что понравилось в kotlin:
1) inline. Никаких созданий объектов, просто подстановка тела функции.
2) extensions. Тоже удобная штука — и никаких потерь производительности.
В Scala похожая функциональность сделана через implicit-преобразования, из-за чего при вызове создаётся ещё один объект.
+2
Или, например, синтаксис объявления метода:В скале на самом деле тоже есть синтаксис без "=", но он deprecated.
В scala: def methodName(...) = {...}
В Scala похожая функциональность сделана через implicit-преобразования, из-за чего при вызове создаётся ещё один объект.В scala есть extends AnyVal. Для таких классов тоже не создается экземпляров, если это возможно.
В Linker собираются автоматически добавлять extends AnyVal к классам.
0
Можно написать пару маленьких программок и декомпилировать.
геттеры и сеттеры будут реализованы как методы getVarName() и setVarName(...)
Даже это делать не надо. В IDEA есть отдельное окно, показывающее, во что превратиться написанный на Kotlin'е исходник. Очень удобно и наглядно демонстрирует качество компилятора...
Null-safety: интересная штука, но на практике во все публичные методы добавляются проверки для каждого аргумента на null. Мне кажется несколько избыточным — и есть подозрение, что это негативно влияет на производительность. (Там не просто проверка, а вызов статического метода с передачей объекта-параметра и его имени как строки, чтобы можно было кинуть понятное исключение)
Это может и выглядит несколько нагружено, если смотреть в байткоде. Но на деле JVM отлично знает как это оптимизировать. И в рантайме никаких издержек выявить не удалось. Более того, если вы попробуете посмотреть, что получется уже на уровне машинного кода после JIT, то навряд ли найдёте эти проверки в явном виде. Где-то про это даже доклад был на одной из конференций.
0
Я понимаю мотивацию дизайнера языка, который принял решение о таком переопределении операторов. Максимального ограничение возможностей переопределения операторов — это максимальная изоляция программиста от ошибок связанных с такие переопределением, однако минимальнонеобходимая возможность переопределения есть. К тому же это позволяет сохранить достаточно однообразный код.
По поводу объявления метода, то в котлине
идентичны. Однако вот такой код
не скомпилируется, потому что возвращаемое значение будет ()->Int
Единственное различие которое я знаю: если использовать expression вариант, то возвращаемое значение можно не писать, а в блочном варианте компилятор такое уже не пропустит.
По поводу объявления метода, то в котлине
fun func():Int = 10
// и
fun func():Int { return 10 }
идентичны. Однако вот такой код
fun func():Int = { 10 }
не скомпилируется, потому что возвращаемое значение будет ()->Int
Единственное различие которое я знаю: если использовать expression вариант, то возвращаемое значение можно не писать, а в блочном варианте компилятор такое уже не пропустит.
0
Я подразумевал следующее:
Чтобы вывести "surprise", придётся написать test3()(). Вариант вызова test3() тоже нормально компилируется, только сработает не так, как ожидалось — добавление "лишних" скобочек кардинально меняет логику программы.
Из-за этих граблей переход со скалы на котлин оказался немного болезненным — иногда "по привычке" в объявлении какого-нибудь метода пишу знак равенства, а потом приходится искать ошибки.
fun test(){ println("it works") }
fun test2() = println("it works too")
fun test3() = {println("surprise!")}
Чтобы вывести "surprise", придётся написать test3()(). Вариант вызова test3() тоже нормально компилируется, только сработает не так, как ожидалось — добавление "лишних" скобочек кардинально меняет логику программы.
Из-за этих граблей переход со скалы на котлин оказался немного болезненным — иногда "по привычке" в объявлении какого-нибудь метода пишу знак равенства, а потом приходится искать ошибки.
+3
Не понимаю, зачем нужно переопределять операторы. В С++ этим увлекался, но там есть смысл это делать. А в языках типа джавы — зачем?
0
Уже пару раз в статьях про Kotlin его пытались сравнить со Scala, а как насчет Groovy? По моему, Kotlin и Groovy ближе друг к другу, чем Kotlin и Scala.
0
Груви — это всё-таки динамический язык, из за этого с котлином их не часто сравнивают, а чаще со скала. Честно говоря, мне не очень нравится дизайн груви, это коненчно субъективно, но всё же. У меня на нем не получается внятно "выразить мысль", часто спотыкаешься о какие-то неприятные мелочи.
+3
Groovy умеет и в статическую типизацию тоже, просто надо объявлять типы. И если взять Groovy со статической типизацией, то он становится очень похож на Kotlin (или Kotlin на него).
0
Достаточно посмотреть доклады Евгения Борисова и Баруха Садогруского про Groovy, чтобы понять чтО это за язык. При кажущемся простом и логичном выражении мысли постоянно спотыкаешься о всевозможные грабли.
Кроме того статическая типизация там пятым колесом — все динамически типизировано. А динамика меня угнетает (даже с умной Idea).
Кроме того статическая типизация там пятым колесом — все динамически типизировано. А динамика меня угнетает (даже с умной Idea).
+3
Что-то меня пугает синтаксис Конлина. Обилие двоеточий, какие-то стрелочки. Вроде даже символ @ видел в доке. Еще немного и получится руби. Который меня своим синтаксисом в свое время и отвернул от себя в пользу питона :)
+1
Я бы тоже хотел узнать, чем отличается Котлин от Груви, т.к. судя по доке Груви, — он умеет почти все из современных штук-дрюк. ;D
+1
fun passTen(func: (Int)->Int ): ()->Int {
return { func(10) }
}
Вот такой синтаксис действительно кажется плохо читаемым, но по факту я никаких проблем с "распаршеванием" таких выражений не испытываю. Наверное сказывается некоторая практика, но всё же.
0
Почти такой же синтаксис сейчас в swift'е и относительно похожий в Rust'е. Это уже своего рода тенденция, как в свое время Java и C# были похожи на С++ и друг на друга. Видимо тут сказывается накопленый опыт в разработке новых языков программирования. Короче говоря, мозг тут придется один раз сломать, но зато после этого ему будут доступны для понимания, по крайней мере, сразу 3 языка программирования.
К тому же в них ещё много общего есть.
К тому же в них ещё много общего есть.
+4
Меня всегда смущало расположение типа после имени. Тут ради этого применяется еще и дополнительный символ — двоеточие, чтобы как-то разделить. Когда тип указывается до имени, то двоеточия не нужно, читабельность выше. Ты сразу понимаешь с чем работаешь, а потом уже смотришь на имя. А тут ты смотришь на имя, которое по сути вторично, а потом уже понимаешь, какого типа эта переменная, а какой тип возвращает функция. Я понимаю, что дело привычки, но не могу понять, почему так делают :)
Вот на примере того же Груви, я вот щас полистал доку их. Там есть все то же самое, что в Котлине. Но синтаксис кажется чище и менее многословным. Объявление конструктора в продолжении названия класса — для меня вообще дикость :) Т.е. ты перечисляешь в шапке класса его поля в одну строку. В Груви ты просто опускаешь конструктор, но описываешь поля в теле класса, и можешь инициализировать объект, просто передав туда именованные параметры в виде ключ-значение.
Вот на примере того же Груви, я вот щас полистал доку их. Там есть все то же самое, что в Котлине. Но синтаксис кажется чище и менее многословным. Объявление конструктора в продолжении названия класса — для меня вообще дикость :) Т.е. ты перечисляешь в шапке класса его поля в одну строку. В Груви ты просто опускаешь конструктор, но описываешь поля в теле класса, и можешь инициализировать объект, просто передав туда именованные параметры в виде ключ-значение.
+2
Подобные проблемы с читабельностью обычно решаются нормальным форматированием. Например поля класса в Котлине никто в одну строчку не пишет, разбивают на несколько. Код в статьях призван просто демонстрировать принципы, и не всегда похож на "промышленный".
0
Но все равно шапка класса получается перегруженной. Там итак могут находиться родительский класс, интерфейсы/трейты и т.д., а тут еще и весь конструктор. Сомнительное решение, но я сужу лишь со своей колокольни.
0
На практике проблем не возникает. Наоборот, библиотека котлина одна из немногих, исходный код которой приятно читать.
Говорю за себя, но Котлин нужно пробовать. Он как хорошие кроссовки — и подошва удобная, и сами не слишком тяжелые. А всевозможные теоретические аргументы за четыре года успели обсудиться, и тот дизай который есть сейчас уже доказал свою оправданность на реальных больших проектах.
Говорю за себя, но Котлин нужно пробовать. Он как хорошие кроссовки — и подошва удобная, и сами не слишком тяжелые. А всевозможные теоретические аргументы за четыре года успели обсудиться, и тот дизай который есть сейчас уже доказал свою оправданность на реальных больших проектах.
0
При всем уважении, но на чистой java куда больше реальных больших проектов, но это не значит, что текущее состояние языка идеально за счет этого фактора :P
Но я как-нибудь попробую, при случае, чтобы все же не «оценивать фильм по чужим рецензиям» :)
Но я как-нибудь попробую, при случае, чтобы все же не «оценивать фильм по чужим рецензиям» :)
0
По мне, так имена несут основную смысловую нагрузку в коде, а типы — это скорее инструменты. Вы можете сделать стул из дерева или из алюминия, но главным будет всё-таки назначение предмета, а не материалы, из которых он сделан. В данной аналогии стул — это имя сущности, а дерево и алюминий — типы (скажем, Int и Double).
Вот вам наглядный пример в коде:
Очевидно, не правда ли? Куда проще, чем:
Вот вам наглядный пример в коде:
import A.B
import A.C
sealed class A<T> {
class B<T>(val c: A<T>, val d: A<T>): A<T>() {
override fun toString(): String = "($c $d)"
}
class C<T>(val e: T): A<T>() {
override fun toString(): String = "$e"
}
}
fun main(args: Array<String>) {
val f = B(C(1), B(C(2), C(3)))
println("$f")
}
Очевидно, не правда ли? Куда проще, чем:
import BinaryTree.Node
import BinaryTree.Leaf
sealed class BinaryTree<T> {
class Node<T>(val left: BinaryTree<T>, val right: BinaryTree<T>): BinaryTree<T>() {
override fun toString(): String = "($left $right)"
}
class Leaf<T>(val value: T): BinaryTree<T>() {
override fun toString(): String = "$value"
}
}
fun main(args: Array<String>) {
val tree = Node(Leaf(1), Node(Leaf(2), Leaf(3)))
println("$tree")
}
+2
А как вы будете делать стул, если вам не важен материал? С металом одна технология работы, а с деревом другая. Метод `add(List items)` сразу говорит нам, что мы добавляем список. Даже не нужно вникать в название параметра. Уже все ясно.
Но дело вкуса, конечно. Не зря же полно популярных языков с обоими стилями.
Но дело вкуса, конечно. Не зря же полно популярных языков с обоими стилями.
-1
Когда тип указывается до имени, то двоеточия не нужно, читабельность выше. Ты сразу понимаешь с чем работаешь, а потом уже смотришь на имя.
Но ведь… Разве имя не более точно указывает, с чем работаешь? :)
Синтаксис с типом после имени более удобен тем, что его можно опускать, если в языке есть вывод типов. Опустить тип, который указан перед именем переменной, получается как-то не очень красиво.
+6
Почти везде, где есть вывод типов, используются ключевые слова, вроде def, let, var. В случае Котлина получается val в начале, а тип — в конце. А так был бы только тип вначале :) Целое слово сэкономили. В случае с Груви, мы пишем def, когда тип выводимый, а если нужно строго привести, то можем явно указать тип на месте слова def. Такая запись лично мне кажется более логичной.
0
Статически типизированный код:
class Actor
def do_it( a, b )
just_todos = {
"remark" => ->self.remark(String | Int32),
"accept" => ->self.accept(String | Int32),
}
just_todos[a](b)
end
def remark(p)
puts p
end
end
0
Язык?
0
Вы что-то конкретное хотели этим сказать?
0
Думаю, это то, что ждет Kotlin, — автоматический вывод типов параметров и результатов, где возможно.
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Немного о Kotlin