Comments 52
"Скорее всего, будет расти как снежный ком" — пиар, пока о котлине я слышу разговоры только среди разработчиков под андроид. К тому же я не нашел ответа на главный вопрос зачем нужен этот язык, кроме замены java при разработке под андроид и здесь лишь по причине отсутствия собственного толстого SDK.
Те кого устраивает Java — инертные пользователи и крупные корпорации не будут массово переходить, те кто хочет остаться на jvm и получить великолепный язык для быстрого и лаконичного выражения своей мысли используют Scala. К тому же Scala имеет в дополнении к Java экосистеме свою собственную, очень крутую.
Подытоживая, массам разрабатывающим типовые CRUD не нужно ничего кроме Java, профессионалы если хотят уйти от Java, в основном выбирают Scala.
А можно посмотреть на график в начале текста и увидеть, что по факту он _уже_ растёт как снежный ком — вопрос только в том, до каких масштабов дорастёт.
очень много удобных штук по сравнению со Scala
Вот тут очень бы хотелось увидеть конкретный список.
What Kotlin has that Scala does not
- Smart casts
У Скалы есть Smart casts, только он не через if работает, а в сопоставлении с образцом.
value match {
case str: String => str.charAt(0)
case int: Int => int + 10
case Some(int: Int) => int * 10
case _ | None => 0
}
в вашем примере он ничего не запоминает
Почему не запоминает? Изначально у value тип Any. В первой ветке идет проверка на то, что value имеет тип String. Если он таковым является, то значение привязывается к str
и во всей ветке считается за String. Если бы такого преобразования не было бы, не имелось бы возможным вызвать метод charAt
, который есть только у String. Тоже самое касается Int, и Some (аналог Котлиновского null type).
В результате какая разница? Это особенность работы паттерн матчинга. Значения которые по нему сопоставляются привязываются к новому имени. Это спор против мутирования старой переменной или создания новой измененной (считай импер. программирования против функц.). В результате вы так же сделали проверку и у вас произошел неявный каст (не ненужно самому кастовать, как пришлось бы в Java).
очень много удобных штук по сравнению со Scala
Не скажу что прям *много*, но все же они есть:
— В Kotlin хорошо реализован null safely, который встроен в сам язык, что, например, позволяет заменить Option в большинстве случаев на nullable типы, с которыми удобнее работать (тот же?. вместо flatMap, map ..) да и к тому же нет оверхеда на создание лишнего класса.
— Отличный интероп с java. Java из Kotlin выглядит как Kotlin и наоборот. Нет конвертации типов между языками, тот же List в Kotlin это ArrayList в Java и т.д.
— Меньший размер библиотеки (особенно существенно для Android)
— За счет того что язык проще, для него лучше туллинг в студии (не последний фактор также и то что язык от той же студии) и быстрее время компиляции.
— Смарт касты. Ниже в обсуждении Fervus привел пример что в Scala они также есть, это не совсем так, все же они немного разные. В Scala это только работает в pattern matching, а в Kotlin можно проверить неизменяемую переменную на null в if, и внутри ветви с ненулевым значением переменной она будет автоматически приведена к non-nullable типу (тоже самое с проверкой типа переменной). Также smart cast в Kotlin работает в when, где по сути получается урезанный вариант pattern matching из Scala.
— Extension методы, в какой-то мере это аналог implicit из Scala (для задач расширения функциональности существующих классов). В Kotlin также можно передавать extension методы в качестве lambda параметров методов (что весьма удобно, например, для построения DSL)
— функции стандартной библиотеки, всякие apply, run, with, let, also и т.п.
— обратная совместимость, можно всегда жить на свежих релизах не дожидаясь пока все библиотеки переедут на новую версию :)
— достаточно простой результирующий байт код, со всем просто разобраться что во что компилируется.
— inline с «нелокальными» переходами
— делегаты
А так, можно привести комментарий Андрея Бреслава про сравнение Kotlin/Scala из обсуждения: Kotlin, кому что нравится :)
Extension методы, в какой-то мере это аналог implicit из Scala (для задач расширения функциональности существующих классов).
Если extension methods аналог implicit для задачи расширения функциональности, то как можно с помощью Котлиновского extension methods расширить класс внешним интерфейсом?
Отличный интероп с java. Java из Kotlin выглядит как Kotlin и наоборот.
Я задавал этот вопрос в другой теме, но ни кто не ответил. Но все таки повторюсь.
Как сохраняется интероп с java при использовании корутин?
Есть ли возможность вызвать suspend function из java?
Как бы например выглядело использование buildSequence в java?
Ещё по поводу интеропа. Каждый раз слышу заявления про 100% интероп (так же заявлено на сайте Котлина), но в том же докладе Антона Кекс (Kotlin-паззлеры) часто упоминается разного рода косяки при вызове даже java кода и наоборот. Интероп конечно лучше чем у Скалы, но он точно не 100%.
делегаты
А это по правде интересная вещь. Правда, до сих пор не до конца разобрался как это работает.
Если extension methods аналог implicit для задачи расширения функциональности, то как можно с помощью Котлиновского extension methods расширить класс внешним интерфейсом?
C помощью extension методов думаю никак, но можно это сделать делегатом:
class Test: IAddition by Addition() {
//many code
}
interface IAddition {
fun add(): Int
}
class Addition: IAddition {
override fun add(): Int = 100500
}
Как сохраняется интероп с java при использовании корутин?
Корутины сделаны средствами языка, это те же extension методы и т.п.
Есть ли возможность вызвать suspend function из java?
Насколько я знаю — нет, также как и не вызвать inline функции.
Как бы например выглядело использование buildSequence в java?
Вполне себе неплохо.
object Fibonaci {
@JvmStatic
fun fibonacciSeq(): Sequence<Int> = buildSequence {
var a = 0
var b = 1
yield(1)
while (true) {
yield(a + b)
val tmp = a + b
a = b
b = tmp
}
}
}
Sequence<Integer> integerSequence = Fibonaci.fibonacciSeq();
Вполне себе неплохо.
Скорее всего вы меня неправильно поняли. У нас есть функция, под названием buildSequence
, которая реализована на языке Котлин, из пакета стандартной библиотеки. Как эту функцию можно вызвать и пользоваться ей в Java коде. При 100% интеропе такая возможность должна быть.
Я попытался вызвать эту функцию из Java и среда разработки предложила мне такой вариант:
Sequence<Integer> seq = SequenceBuilderKt.buildSequence(new Function2<SequenceBuilder<? super Integer>, Continuation<? super Unit>, Object>() {
public Object invoke(SequenceBuilder<? super Integer> sequenceBuilder, Continuation<? super Unit> continuation) {
return null;
}
});
Корутины сделаны средствами языка, это те же extension методы и т.п.
Не смотря на то, что extension methods это часть языка, их можно вызвать из Java кода явно передав туда объект на котором вызывается метод.
C помощью extension методов думаю никак, но можно это сделать делегатом
Мне не очень понятно где здесь внешние расширение. Вы явно наследуетесь от интерфейса.
Что если у вас есть класс из какой-нибудь библиотеки Java, к которому у вас нет доступа изменять исходники и интерфейс из совершенно другой библиотеки. Можно как-нибудь расширить класс из первой библиотеки другим интерфейсом, например:
// из первой библ
class Robot {}
// из второй библ
interface Moveable{
def move(): Unit
}
// мой код с расширением
implicit class ExtMoveableRobot(val self: Robot) extends Moveable{
def move(){}
}
Как эту функцию можно вызвать и пользоваться ей в Java коде
Напрямую вызывать ее неудобно, но я не вижу кейсов где вам нужно именно так это делать. Да и при большом желании, как вы и привели пример, это сделать можно. Или можно воспользоваться моим примером вызова.
Что если у вас есть класс из какой-нибудь библиотеки Java, к которому у вас нет доступа изменять исходники и интерфейс из совершенно другой библиотеки
В таком случае сделать это прям как в Scala нельзя, но можно, например, сделать обертку, а для генерации обертки сделать extension метод на нужном классе (не очень красиво, но и не прям очень страшно будет)
Gradle уже сейчас позволяет билд скрипты писать на Kotlin, код почти не отличается от Groovy.
А можете привести пример где нужно именно AST трансформация?
Интересно — помогло ли?
На днях попробовал. Нужно было сериализовать объект в мап чтобы дальше пустить в бд. Рефлекшен не дает достаточно информации. Да и вообще, зачем решать проблему в рантайме когда вся информация для ее решения есть во время компиляции? Или плодить кучу вонючих орм как в яве? Язык без макросов в 2017-м нельзя называть новым и современным.
Можно подробнее?
Рефлекшен не дает достаточно информации.
Обычно через рефлекшн можно вытащить всё, разве что дженерики стираются, но, как правило, это можно обойти.
Да и вообще, зачем решать проблему в рантайме когда вся информация для ее решения есть во время компиляции?
Оч странная постановка вопроса, вы вроде сами пошли через рефлекшн, а потом спрашиваете зачем?)
В Java стеке есть annotation processing (и в Kotlin он работает), который позволяет для таких задач часть информации вытаскивать в компайл тайм и генерировать код, который, в вашем случае, может сериализовать/десериализовать объект без инспекции объектов в рантайме.
Так же можно инспектировать/изменять/генерировать код в компайл тайме через систему сборки, но макросы в самом языке действительно были бы интересны.
Через рефлекшен можно было вытащить все, но там много мусора в апи, плюс всякие сгеренированные методы, я хз, так за час и не разобрался как получить именно поля дата класса а не все подряд. Лезть в явовский рефлекшен тем более не интересно, так как это нарушение идеологии языка — интеропом пользоваться. Как я потом в яваскрипт к примеру откомпилирую? Сама либа котлиновского рефлекшена весит под три метра. С фига ли? Это уже скала почти монструозная.
Ну аннотейшен процессоры — это то что я и имел в виду под «вонючими орм». Много кода, много аннотаций, проблемы с ИДЕ и всякими инстант ранами, сам знаешь эта технология — костыль, хоть тулинг по большей части уже доведен до ума, но все равно костыль.
Макросы решают эту проблему на корню — ты просто пишешь функцию, которая пишет процедуру генерации, и все. Базы данных, сериализация в джейсон, все — во время компиляции, в рантайме все происходит мгновенно. А еще можно и синтаксис расширять, к примеру вот мы получили эти асинки в котлине. Пришлось ждать версии компилятора. А макросом эту проблему можно было решить изначально (к примеру кложуровские каналы — это макросы). Самое интересное — в котлине у них что-то вроде макроса в итоге и вышло, только они сделали это на уровне компилятора и нам поиграться не дали.
// Епрст, скинь мне уже список своих ников))
Сама либа котлиновского рефлекшена весит под три метра. С фига ли?
Насколько я знаю, они до сих пор тянут тяжелую реализацию protobuf, поэтому так много весит https://youtrack.jetbrains.com/issue/KT-12636 (голосовать здесь)
Все вопросы снимаются, портируемость компайлтайм инспекции кода и кодогенерации на JS и Kotlin Native платформы, имхо, важная часть языка, осталось понять насколько это в планах команды Kotlin, особенно с учетом того, что разработчики на native платформах любят макросы cc abreslav?
Самый-самый толстый protobuf-java весит немного больше 500К, так что я сомневаюсь, что дело в нем :) Наш рефлекшен требует на рантайме большой кусок компилятора, который умеет работать с типовой информацией, вот он и весит так много. Мы будем работать над его уменьшением, конечно, но это потребует какого-то времени.
Все вопросы снимаются, портируемость компайлтайм инспекции кода и кодогенерации на JS и Kotlin Native платформы, имхо, важная часть языка, осталось понять насколько это в планах команды Kotlin, особенно с учетом того, что разработчики на native платформах любят макросы cc abreslav?
Макросов как таковых мы делать не хотим. Нормальный тулинг для статически типизированного языка с макросами написать крайне сложно (какие-то более-менее успешные примеры есть, но их мало, и они весьма непросты по реализации). Наш Scala-плагин к IDEA, например, использует черта в ступе и черную магию для этих целей, причем получает не 100% результат, насколько мне известно. Мы так не хотим :)
Однако это не значит, что у нас не будет никакого метапрограммирования. Во-первых, мы рассматриваем возможность поддержать что-то вроде expression trees, как в C#, но это все-таки рантаймовый механизм. Во-вторых, для compile time мы постепенно будем развивать инфрастурктуру плагинов к компилятору. То есть можно будет написать свой наикрутейший фреймворк, который порождает наихитрейший код во время копиляции, но не на макросах, а в виде compiler plugin. Чем это лучше макросов? Тем, что код, выполняемый в процессе компиляции, не является частью самого компилируемого проекта, поэтому тулинг не должен сойти с ума, чтобы его понять. Плюс, API таких плагинов можно сделать таким, чтобы они одновременно работали и в компиляторе, и в IDE, что, опять же, гарантирует качественную инструментальную поддержку.
Makes sense.
Планируется ли закладывать мультиплатформенность в плагины для компилятора, чтобы плагином можно было генерировать/изменять именно котлин код и не привязываться к конкретной платформе в виде JVM, Native, JS и наоборот, чтобы можно было влиять на компиляцию для конкретной платформы если потребуется?
По поводу API для одновременной поддержки и компилятора и IDE —это мастхев (я так понимаю, напрашивается что-то вроде language-server архитектуры), тк писать плагин для компилятора и потом ещё отдельный для IDE совсем не хочется, но тогда не очень понятно как оно будет жить с разными системами сборки тк от них во многом зависит поведение IDE…
Я понимаю, что сейчас это больше теоретический разговор, но хотелось бы, чтобы комьюнити могло повлиять на дизайн решения до того, как вы потратите кучу ресурсов на первую реализацию и будет сложно что-то менять :(
Кстати, kotlin-reflect можно попробовать аккуратно пошринкать через ProGuard
Планируется ли закладывать мультиплатформенность в плагины для компилятора, чтобы плагином можно было генерировать/изменять именно котлин код и не привязываться к конкретной платформе в виде JVM, Native, JS и наоборот, чтобы можно было влиять на компиляцию для конкретной платформы если потребуется?
Скорее да, чем нет, но пока это довольно расплывчатые планы.
Я понимаю, что сейчас это больше теоретический разговор, но хотелось бы, чтобы комьюнити могло повлиять на дизайн решения до того, как вы потратите кучу ресурсов на первую реализацию и будет сложно что-то менять :(
Мы как всегда обязательно всем все расскажем перед тем как непоправимо облажаться :)
Кстати, kotlin-reflect можно попробовать аккуратно пошринкать через ProGuard
Уже :)
Вот, кстати, лично мне очень не нравится переход к плагинам компилятора вместо языковых конструкций.
Compiler plugins for
- making classes open by default
- generating no-arg constructors by default
- extension lambdas in SAM conversions
Есть ощущение что здесь будет такая же проблема, что и в реализациях java-nullability через дополнительные xml — очень легко у разных разработчиков запустить билд с разными параметрами и получить другой результат.
Скажу другими словами: мне не нравится что плагины не добавляют новую функциональность (например, lombok в зависимостях забыл, у тебя проект просто не соберется), а изменяют семантику имеющегося. Т.е. проект у тебя все-равно успешно соберется, только работать будет иначе.
Думаю кейсы подобные вашему могут решиться в будущем с релизом Kotlin Serialization Framework — https://discuss.kotlinlang.org/t/kotlin-serialization/2063
P.S. про макросы и современные языки все же очень спорное утверждение
Макросы решают эту проблему на корню — ты просто пишешь функцию, которая пишет процедуру генерации, и все.
+1
А еще можно и синтаксис расширять, к примеру вот мы получили эти асинки в котлине. Пришлось ждать версии компилятора.
А без расширения синтаксиса этого невозможно сделать?
Не думаю. Нужно приостанавливать выполнение в какие-то моменты, а это значит нужно разбивать синтаксис на кусочки и вставлять паузы — всякие мутексы и другие многопоточные примитивы, как я это вижу. Я в этом далеко не спец, у нас на яве особо не попрыгаешь, рхявой обходимся.
Вообще-то, JVM хорош тем, что синтаксис новый не нужен — достаточно просто поинструментировать байт-код. Собственно, мы в Kotlin так и делаем (аналогично делает какой-нибудь quasar).
Котлин ведь позиционируется не только как нашлепка на JVM? Значит ему нужен JVM-независимый способ делать такие вещи. Вот яваскрипт скоро станет многопоточным. Или вебассембли. Просто мысли вслух.
Внутри у любого современного компилятора есть IR, да не один. У Java это — байт-код, у LLVM — биткод или текстовый IR, у gcc — какой-нибудь gimple и т.п. Над IR, в виду его простоты, достаточно тривиально делать подобные трансформации. В Kotlin есть свои IR, но они ещё не устаканились, так что мы пока не публикуем их. В данный момент трансформация в state-машины в реализации корутин для JS и JVM делается по-разному, возможно, когда-нибудь мы сделаем универсальную трансформацию. Основная проблема в том, что на уровне байт-кода (или любого подобного представления) трансформация делается тривиально, а на уровне AST (который удобен для генерации JS) та же трансформация выглядит значительно менее тривиальной, и оставляет в коде значительно больше мусора.
Вообще, наличие простого как пробка байт-кода у Java — это то, за что я так люблю эту платформу. Стоит лишь не полениться его выучить, овладеть такими инструментами как asm, и можно творить такую магию, что никаким lisp не снились (к сожалению, очень мало разработчиков знают о такой киллер-фиче и умеют её правильно готовить). По опыту работы с JS, как с целевой платформой, для него как раз такой фичи очень не хватает. Надеюсь, мы когда-нибудь дойдём до того, что сделаем этакий платформо-независимый kotlin bytecode.
И если к примеру ты захочешь сделать свой асинк только с блекджеком, то макросом это можно было бы сделать еще несколько лет назад. А тут народ можно сказать страдал и ел кактус. )))
«Скорее всего, будет расти как снежный ком» — Андрей Бреслав и Антон Кекс о Kotlin