Ads
Comments 48
+1
Понятно, что в официальном блоге JetBrains такого прямого противопоставления не будет, но от коммьюнити хотелось бы четкого списка пунктов «Почему Kotlin, а не Scala».

Насколько я понимаю, в Scala недостаточно хорошо продумана обратная совместимость — как между версиями самой Scala, так и интероперабельность из Java в Scala, и наоборот. Например, те же properties в Scala не используют геттеры и сеттеры по умолчанию, а для совместимости нужно использовать какие-то дополнительные аннотации. Как на уровне байткода будут выглядеть геттеры и сеттеры в Kotlin?

С функциональщиной тоже порой коммьюнити Scala несет слишком далеко — невозможно разобрать, что все эти хитрые операторы и комбинации на самом деле означают. Как итог — высокий порог вхождения.

Как со всем этим делом в Kotlin? Правильно ли я понимаю, что стараются устранить недостатки Scala, и это и есть основная киллер-фича?
0
UPD. На официальном сайте есть краткое сравнение: https://kotlinlang.org/docs/reference/comparison-to-scala.html
+9
Основная киллер-фича котлина, что они не пытаются делать киллер-фич, а нацелены на разработку простого и удобного инструмента для реальной разработки. Конечно, можно пытаться сравнивать скалу и котлин, и в каждом посте обязательно про это бывает, но лично я не вижу большого смысла про это много писать — в статье по этому поводу раздел "Простой и совместимый". Это же и главные отличия от скалы, на мой взгляд.
+2
Как на уровне байткода будут выглядеть геттеры и сеттеры в 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-преобразования, из-за чего при вызове создаётся ещё один объект.
0
Или, например, синтаксис объявления метода:
В scala: def methodName(...) = {...}
В скале на самом деле тоже есть синтаксис без "=", но он deprecated.
В Scala похожая функциональность сделана через implicit-преобразования, из-за чего при вызове создаётся ещё один объект.
В scala есть extends AnyVal. Для таких классов тоже не создается экземпляров, если это возможно.
В Linker собираются автоматически добавлять extends AnyVal к классам.
0
Можно написать пару маленьких программок и декомпилировать.
геттеры и сеттеры будут реализованы как методы getVarName() и setVarName(...)

Даже это делать не надо. В IDEA есть отдельное окно, показывающее, во что превратиться написанный на Kotlin'е исходник. Очень удобно и наглядно демонстрирует качество компилятора...

Null-safety: интересная штука, но на практике во все публичные методы добавляются проверки для каждого аргумента на null. Мне кажется несколько избыточным — и есть подозрение, что это негативно влияет на производительность. (Там не просто проверка, а вызов статического метода с передачей объекта-параметра и его имени как строки, чтобы можно было кинуть понятное исключение)

Это может и выглядит несколько нагружено, если смотреть в байткоде. Но на деле JVM отлично знает как это оптимизировать. И в рантайме никаких издержек выявить не удалось. Более того, если вы попробуете посмотреть, что получется уже на уровне машинного кода после JIT, то навряд ли найдёте эти проверки в явном виде. Где-то про это даже доклад был на одной из конференций.
0
Я понимаю мотивацию дизайнера языка, который принял решение о таком переопределении операторов. Максимального ограничение возможностей переопределения операторов — это максимальная изоляция программиста от ошибок связанных с такие переопределением, однако минимальнонеобходимая возможность переопределения есть. К тому же это позволяет сохранить достаточно однообразный код.

По поводу объявления метода, то в котлине

fun func():Int = 10
// и
fun func():Int { return 10 }

идентичны. Однако вот такой код

fun func():Int = { 10 }

не скомпилируется, потому что возвращаемое значение будет ()->Int
Единственное различие которое я знаю: если использовать expression вариант, то возвращаемое значение можно не писать, а в блочном варианте компилятор такое уже не пропустит.
+3
Я подразумевал следующее:

fun test(){ println("it works") } 
fun test2() = println("it works too")
fun test3() = {println("surprise!")}

Чтобы вывести "surprise", придётся написать test3()(). Вариант вызова test3() тоже нормально компилируется, только сработает не так, как ожидалось — добавление "лишних" скобочек кардинально меняет логику программы.

Из-за этих граблей переход со скалы на котлин оказался немного болезненным — иногда "по привычке" в объявлении какого-нибудь метода пишу знак равенства, а потом приходится искать ошибки.
+2
Да, я отлично понял о чем вы. Но это актуально только для совпадения двух не очень часто встречающихся факторов: когда функция ничего не возвращает, а вы много лет писали на скала :-)
0
Не понимаю, зачем нужно переопределять операторы. В С++ этим увлекался, но там есть смысл это делать. А в языках типа джавы — зачем?
0
Уже пару раз в статьях про Kotlin его пытались сравнить со Scala, а как насчет Groovy? По моему, Kotlin и Groovy ближе друг к другу, чем Kotlin и Scala.
+3
Груви — это всё-таки динамический язык, из за этого с котлином их не часто сравнивают, а чаще со скала. Честно говоря, мне не очень нравится дизайн груви, это коненчно субъективно, но всё же. У меня на нем не получается внятно "выразить мысль", часто спотыкаешься о какие-то неприятные мелочи.
0
Groovy умеет и в статическую типизацию тоже, просто надо объявлять типы. И если взять Groovy со статической типизацией, то он становится очень похож на Kotlin (или Kotlin на него).
0
У груви есть "строгий режим", да. Но он создает массу ограничений и видно, что приделан сбоку к языку. К тому же я не уверен, но вроде этот режим включается только на уровне каждого отдельного класса, не на уровне компиляции, которая у груви насколько я помню динамическая.
+3
Достаточно посмотреть доклады Евгения Борисова и Баруха Садогруского про Groovy, чтобы понять чтО это за язык. При кажущемся простом и логичном выражении мысли постоянно спотыкаешься о всевозможные грабли.
Кроме того статическая типизация там пятым колесом — все динамически типизировано. А динамика меня угнетает (даже с умной Idea).
0
А можно где-нибудь эти доклады (или выжимку из них) не посмотреть, а почитать?
0
Ну, можно попробовать закантактироваться с самими авторами, чтобы прислали или залили куда-нибудь свои презентации. Если Вы пишите на Груви, то посмотреть их тоже не помешает.
+1
Что-то меня пугает синтаксис Конлина. Обилие двоеточий, какие-то стрелочки. Вроде даже символ @ видел в доке. Еще немного и получится руби. Который меня своим синтаксисом в свое время и отвернул от себя в пользу питона :)
+1
Я бы тоже хотел узнать, чем отличается Котлин от Груви, т.к. судя по доке Груви, — он умеет почти все из современных штук-дрюк. ;D
0
fun passTen(func: (Int)->Int ): ()->Int {
    return { func(10) }
}

Вот такой синтаксис действительно кажется плохо читаемым, но по факту я никаких проблем с "распаршеванием" таких выражений не испытываю. Наверное сказывается некоторая практика, но всё же.
+4
Почти такой же синтаксис сейчас в swift'е и относительно похожий в Rust'е. Это уже своего рода тенденция, как в свое время Java и C# были похожи на С++ и друг на друга. Видимо тут сказывается накопленый опыт в разработке новых языков программирования. Короче говоря, мозг тут придется один раз сломать, но зато после этого ему будут доступны для понимания, по крайней мере, сразу 3 языка программирования.

К тому же в них ещё много общего есть.
0
Да, в этом смысле котлин хорош тем, что не пытается придумать какой-то свой синтаксис, а максимально использует распространенные способы записи — это сильно упрощает вход, большинство синтаксиса интуитивно понятно для того, кто имел дело с си-подобными языками.
+2
Ну как минимум в Kotlin зачем-то ввели конструкцию fun вместо распространенных def (Python, Ruby, Scala).
С другой стороны, в Swift тоже своя конструкция func, а в Rust так вообще fn :)
+2
Меня всегда смущало расположение типа после имени. Тут ради этого применяется еще и дополнительный символ — двоеточие, чтобы как-то разделить. Когда тип указывается до имени, то двоеточия не нужно, читабельность выше. Ты сразу понимаешь с чем работаешь, а потом уже смотришь на имя. А тут ты смотришь на имя, которое по сути вторично, а потом уже понимаешь, какого типа эта переменная, а какой тип возвращает функция. Я понимаю, что дело привычки, но не могу понять, почему так делают :)
Вот на примере того же Груви, я вот щас полистал доку их. Там есть все то же самое, что в Котлине. Но синтаксис кажется чище и менее многословным. Объявление конструктора в продолжении названия класса — для меня вообще дикость :) Т.е. ты перечисляешь в шапке класса его поля в одну строку. В Груви ты просто опускаешь конструктор, но описываешь поля в теле класса, и можешь инициализировать объект, просто передав туда именованные параметры в виде ключ-значение.
0
Подобные проблемы с читабельностью обычно решаются нормальным форматированием. Например поля класса в Котлине никто в одну строчку не пишет, разбивают на несколько. Код в статьях призван просто демонстрировать принципы, и не всегда похож на "промышленный".
0
Но все равно шапка класса получается перегруженной. Там итак могут находиться родительский класс, интерфейсы/трейты и т.д., а тут еще и весь конструктор. Сомнительное решение, но я сужу лишь со своей колокольни.
0
На практике проблем не возникает. Наоборот, библиотека котлина одна из немногих, исходный код которой приятно читать.

Говорю за себя, но Котлин нужно пробовать. Он как хорошие кроссовки — и подошва удобная, и сами не слишком тяжелые. А всевозможные теоретические аргументы за четыре года успели обсудиться, и тот дизай который есть сейчас уже доказал свою оправданность на реальных больших проектах.
0
При всем уважении, но на чистой java куда больше реальных больших проектов, но это не значит, что текущее состояние языка идеально за счет этого фактора :P

Но я как-нибудь попробую, при случае, чтобы все же не «оценивать фильм по чужим рецензиям» :)
0
Все что мешало работать — убрали до 1.0, имел в виду это.
+2
По мне, так имена несут основную смысловую нагрузку в коде, а типы — это скорее инструменты. Вы можете сделать стул из дерева или из алюминия, но главным будет всё-таки назначение предмета, а не материалы, из которых он сделан. В данной аналогии стул — это имя сущности, а дерево и алюминий — типы (скажем, 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")
}
-1
А как вы будете делать стул, если вам не важен материал? С металом одна технология работы, а с деревом другая. Метод `add(List items)` сразу говорит нам, что мы добавляем список. Даже не нужно вникать в название параметра. Уже все ясно.
Но дело вкуса, конечно. Не зря же полно популярных языков с обоими стилями.
+6
Когда тип указывается до имени, то двоеточия не нужно, читабельность выше. Ты сразу понимаешь с чем работаешь, а потом уже смотришь на имя.

Но ведь… Разве имя не более точно указывает, с чем работаешь? :)

Синтаксис с типом после имени более удобен тем, что его можно опускать, если в языке есть вывод типов. Опустить тип, который указан перед именем переменной, получается как-то не очень красиво.
0
Почти везде, где есть вывод типов, используются ключевые слова, вроде def, let, var. В случае Котлина получается val в начале, а тип — в конце. А так был бы только тип вначале :) Целое слово сэкономили. В случае с Груви, мы пишем def, когда тип выводимый, а если нужно строго привести, то можем явно указать тип на месте слова def. Такая запись лично мне кажется более логичной.
0
Экономии там нет, т.к. идеологи Котлина хотят final везде, где только можно. Иммутабельность рулит. И котлин решает это введением var и val.
final int size = list.size();
val size = list.size();
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

Думаю, это то, что ждет Kotlin, — автоматический вывод типов параметров и результатов, где возможно.

0
Думаю, в котлине уже есть автоматический вывод типов везде где это нужно, делать это везде где возможно только потому, что это возможно — не котлин вэй. Явные типы параметров и результатов нужны по многим причинам и никуда в котлине они не денутся.
Only those users with full accounts are able to leave comments.  , please.