Я хочу рассказать вам о языке программирования Kotlin и о том, почему вы должны рассмотреть его для своего следующего проекта. Раньше я предпочитал Java, но за прошедший год обнаружил, что кодирую на Kotlin при любой возможности, и сейчас я действительно не могу придумать ситуацию, в которой Java был бы лучшим выбором.
Он разработан компанией JetBrains, и тот факт, что именно эти люди стоят в создании такого набора IDE, как IntelliJ и ReSharper, в полной мере отражается и в Kotlin. Он прагматичен и лаконичен, делает кодирование приятным и эффективным занятием.
Хотя Kotlin компилируется как в JavaScript, а скоро и в машинный код, я сосредоточусь на его основной среде - JVM.
Итак, вот несколько причин, по которым вы должны полностью перейти на Kotlin (нумерация случайна, без определенного порядка):
Kotlin на 100% совместим с Java. Вы можете в буквальном смысле продолжать работать над своими старыми Java-проектами, используя Kotlin. Все ваши любимые Java-фреймворки по-прежнему доступны, и любой фреймворк, который вы напишете на Kotlin, будет легко принят вашим упертым другом, любящим Java.
Kotlin - это не какой-то необычный язык, рожденный в академических кругах. Его синтаксис знаком любому программисту, пришедшему из области ООП, и может быть более или менее понятен с самого начала. Конечно, есть некоторые отличия от Java, например, переработанные конструкторы или объявления переменных val
var
. В приведенном ниже сниппете представлена большая часть базового синтаксиса:
class Foo {
val b: String = "b" // val means unmodifiable
var i: Int = 0 // var means modifiable
fun hello() {
val str = "Hello"
print("$str World")
}
fun sum(x: Int, y: Int): Int {
return x + y
}
fun maxOf(a: Float, b: Float) = if (a > b) a else b
}
Это как если бы в язык была встроена более умная и читабельная версия функции String.format()
из Java:
val x = 4
val y = 7
print("sum of $x and $y is ${x + y}") // sum of 4 and 7 is 11
3# Вывод типов
Kotlin будет выводить ваши типы, если вы посчитаете, что это улучшит читабельность:
val a = "abc" // type inferred to String
val b = 4 // type inferred to Int
val c: Double = 0.7 // type declared explicitly
val d: List<String> = ArrayList() // type declared explicitly
4# "Умные" преобразования. (Smart Casts)
Компилятор Kotlin отслеживает вашу логику и по возможности автоматически выполняет приведение типов, что означает избавление от проверок instanceof
и явных преобразований:
if (obj is String) {
print(obj.toUpperCase()) // obj is now known to be a String
}
Вы можете перестать явно вызывать equals()
, потому что оператор ==
теперь проверяет структурное равенство:
val john1 = Person("John")
val john2 = Person("John")
john1 == john2 // true (structural equality)
john1 === john2 // false (referential equality)
Нет необходимости определять несколько одинаковых методов с разными аргументами:
fun build(title: String, width: Int = 800, height: Int = 600) {
Frame(title, width, height)
}
В сочетании с аргументами по умолчанию именованные аргументы избавляют от необходимости использовать строителей (builders):
build("PacMan", 400, 300) // equivalent
build(title = "PacMan", width = 400, height = 300) // equivalent
build(width = 400, height = 300, title = "PacMan") // equivalent
В качестве замены оператору switch используется гораздо более удобное и гибкое выражение when:
when (x) {
1 -> print("x is 1")
2 -> print("x is 2")
3, 4 -> print("x is 3 or 4")
in 5..10 -> print("x is 5, 6, 7, 8, 9, or 10")
else -> print("x is out of range")
}
Оно работает как выражение, и как оператор, с аргументом, и без него:
val res: Boolean = when {
obj == null -> false
obj is String -> true
else -> throw IllegalStateException()
}
9# Свойства
К публичным полям может быть добавлено кастомное поведение set и get, что позволит нам остановить загромождение кода бессмысленными геттерами и сеттерами.
class Frame {
var width: Int = 800
var height: Int = 600
val pixels: Int
get() = width * height
}
Это POJO (Plain Old Java Object) с функциями toString()
, equals()
, hashCode()
и copy()
, и в отличие от Java он не занимает почти 100 строк кода:
data class Person(val name: String,
var email: String,
var age: Int)
val john = Person("John", "john@gmail.com", 112)
Предопределенный набор операторов может быть перегружен для улучшения читабельности:
data class Vec(val x: Float, val y: Float) {
operator fun plus(v: Vec) = Vec(x + v.x, y + v.y)
}
val v = Vec(2f, 3f) + Vec(4f, 1f)
12# Объявления деструктуризации
Некоторые объекты могут быть деструктурированы, что, например, полезно для итерации карт:
for ((key, value) in map) {
print("Key: $key")
print("Value: $value")
}
13# Диапазоны
Для улучшения читабельности:
for (i in 1..100) { ... }
for (i in 0 until 100) { ... }
for (i in 2..10 step 2) { ... }
for (i in 10 downTo 1) { ... }
if (x in 1..10) { ... }
Помните, когда вам впервые понадобилось отсортировать List
в Java? Вы не могли найти функцию sort()
и вам в результате пришлось узнать о Collections.sort()
. А позже, когда вам нужно было написать String
с заглавной буквы, то это привело к написанию собственной вспомогательной функции, потому что вы не знали о StringUtils.capitalize()
.
Если бы только существовал способ добавить новые функции в старые классы; тогда ваша IDE смогла бы помочь вам найти нужную функцию при автоматическом дополнении кода. В Kotlin вы можете сделать именно это:
fun String.replaceSpaces(): String {
return this.replace(' ', '_')
}
val formatted = str.replaceSpaces()
Стандартная библиотека расширяет функциональность оригинальных типов Java, что особенно требовалось для String
:
str.removeSuffix(".txt")
str.capitalize()
str.substringAfterLast("/")
str.replaceAfter(":", "classified")
Java - это то, что мы должны назвать почти статически типизированным языком. В нем переменная типа String
не гарантированно ссылается на String
- она может ссылаться на null
. Несмотря на то, что мы привыкли к этому, это сводит на нет безопасность статической проверки типов, и в результате разработчикам Java приходится жить в постоянном страхе перед ошибкой NPE (NullPointerException
).
Kotlin решает эту проблему, проводя различие между ненулевыми (non-null) и обнуляемыми (nullable) типами. Типы по умолчанию являются non-null, и их можно сделать nullable, добавив ?
как, например:
var a: String = "abc"
a = null // compile error
var b: String? = "xyz"
b = null // no problem
Kotlin заставляет вас защищаться от NPE всякий раз, когда вы обращаетесь к nullable-типу:
val x = b.length // compile error: b might be null
И хотя это может показаться громоздким, на самом деле все просто благодаря нескольким особенностям Kotlin. У нас все еще есть умное приведение, которое преобразует nullable типы в non-null везде, где это возможно:
if (b == null) return
val x = b.length // no problem
Мы также можем использовать безопасный вызов ?.
, который производит проверку на нулевое значение вместо того, чтобы выбрасывать NPE:
val x = b?.length // type of x is nullable Int
Безопасные вызовы можно объединять в цепочки, чтобы избежать вложенных проверок if-not-null (если-не-ноль), которые мы иногда пишем в других языках, и если нам нужно значение по умолчанию, отличное от null
, то используем elvis-оператор ?:
:
val name = ship?.captain?.name ?: "unknown"
Если ничего из этого вам не подходит, и необходимость в NPE крайне велика, то скажите об этом явно:
val x = b?.length ?: throw NullPointerException() // same as below
val x = b!!.length // same as above
Это хорошая система лямбд - она идеально сбалансирована между читабельностью и сложностью, благодаря продуманному дизайну, с простым синтаксисом:
val sum = { x: Int, y: Int -> x + y } // type: (Int, Int) -> Int
val res = sum(4,7) // res == 11
А вот и толковые решения:
Скобки метода могут быть перемещены или опущены, если лямбда идет последней либо является единственным аргументом метода.
Если мы решили не объявлять аргумент одноаргументной лямбды, он будет неявно объявлен под именем
it
.
Эти факты в совокупности делают следующие три строки эквивалентными:
numbers.filter({ x -> x.isPrime() })
numbers.filter { x -> x.isPrime() }
numbers.filter { it.isPrime() }
И это позволяет нам писать лаконичный функциональный код - только посмотрите на эту красоту:
persons
.filter { it.age >= 18 }
.sortedBy { it.name }
.map { it.email }
.forEach { print(it) }
Система лямбд Kotlin в сочетании с функциями расширения делает его идеальным для создания DSL. Взгляните на Anko в качестве примера DSL, который призван улучшить разработку Android:
verticalLayout {
padding = dip(30)
editText {
hint = “Name”
textSize = 24f
}
editText {
hint = “Password”
textSize = 24f
}
button(“Login”) {
textSize = 26f
}
}
17# Поддержка IDE
У вас есть несколько вариантов, если вы собираетесь начать работу с Kotlin, но я настоятельно рекомендую использовать IntelliJ, который поставляется в комплекте с Kotlin - его возможности продемонстрируют все преимущество того, что и язык, и IDE разрабатываются одними и теми же людьми.
В качестве небольшого, но показательного примера, вот что всплыло, когда я впервые попытался скопипастить Java-код со Stack Overflow:
IntelliJ заметит, если вы вставите Java-код в файл Kotlin
Завтра состоится открытое занятие «Основы классов и объектов», на котором мы посмотрим на принципы проектирования классов и их реализации в Kotlin. Поймем, как объектно-ориентированное программирование работает изнутри, создадим свои структуры и посмотрим на возможности языка Kotlin. Спроектируем объектную модель данных. Регистрируйтесь по ссылке.
Комментарии (55)
mayorovp
21.09.2022 13:59+28Я хочу рассказать вам о новом языке программирования под названием Kotlin
Вы шутите или издеваетесь?
А не, вы просто перевели публикацию 2017го года...
sshikov
21.09.2022 18:58+1Раньше я предпочитал Java, но за прошедший год обнаружил, что кодирую на Kotlin при любой возможности
Я бы и про автора оригинала сказал, что он просто имеет узкий кругозор, и не знает например про скалу или груви. Да собственно и котлину уже 11 лет, в 2017 он был далеко не новым уже.
Я просто думаю, что все вот эти переводы (по качеству это кстати хорошо заметно — чуть нетривиальная тема, начинаются косяки) пишут люди, которые сами никогда близко не стояли ни к котлину, ни к любой другой теме. Ну переводчики это… просто переводчики. Хорошо если не гугло… И какие темы на самом деле интересны и актуальны, они в итоге не понимают. Не думаю что это сознательное издевательство, просто в этом процессе нет обратной связи как таковой. Вы посмотрите хоть на автора этого текста — 1.4к публикаций, и при этом 34 кармы (и 257 комментариев). Никому это качество текстов (из заказчиков автора) не нужно.Politura
22.09.2022 05:06+1Тут не только перевод, статья с медиума, а на этом ресурсе в ТОПе как правило статьи предельно низкого качества с хайповыми заголовками, типа "Почему ты должен...", "X вещей, которые ты обязан делать в языке Y", "ХХХ мертв и вот почему.", и тд., ну а у переводчиков нет знаний оценить качество, переводят из топа и получается то, что получается.
nordligeulv
22.09.2022 09:37А чем Scala интереснее по сравнению с Kotlin? Не подкол, просто вопрос.
sshikov
22.09.2022 18:53+2Ну, прежде всего — это субъективное мнение… Я скорее имел в виду не что она интереснее (хотя в ней есть и такое), а что уже очень давно в этой же нише (языков для платформы JVM, если на то пошло) есть куча вариантов, а те кто при этом бездумно предпочитает Java — они зачастую просто не в курсе того, какие у них есть возможности.
Я бы сказал, что у них есть свои преимущества у обоих. Скала посложнее, но в тоже время мы вот пишем на Spark Shell, это по сути скала + спарк API, и получается как правило так, что эти сложности нам не видны вообще, а есть у нас в наличии более простая и чистая Java, где можно написать val a= «abcd», и никогда не нужно было указывать что это переменная/константа типа String, где можно написать Array(1, 2, ...).map{}, и не думать, что за тип у этой коллекции, и почему у нее вдруг нет .stream()?
Ну и груви примерно из этой же оперы. Литералы для мапов и списков, ламбды от рождения, DSL на раз-два, и все это уже 17 лет кажется. Синтаксически все проще и удобнее, а семантически доступно все тоже самое.
Chuvi
21.09.2022 14:16+2почему вы должны рассмотреть его для своего следующего проекта
я обычно прошивки для разных МК пишу. Мне точно надо зафеячивать туда котлин? С++ уже не в моде?
Revertis
21.09.2022 14:29Да, где минусы? Тот же companion object, например?
LordDarklight
21.09.2022 15:00Не трожьте компаньонов - может синтаксис и не удачный - зато единственный вариант определения статических членов класса. Да и не так уж он ущербен такой синтаксис - просто дело привычки. В некоторых языках вообще нет статических членов классов - нужно только отдельно синглетоны определять для этого. А из компаньонов классов Котлина может ещё со временем раззовётся что-то более толковое!
Мне вот больше не понравилось, что все члены и классы по умолчанию закрытые - нельзя наследоваться и переопределять! Я понимаю, что это ради производительности. Но уж больно снижает гибкость разработки при использовании сторонних библиотек без исходников.
Или очень ограниченные кортежи
Ну и нет таких широких возможностей по раскрытию неограниченных параметров функций, как в Питоне.
hMartin
21.09.2022 14:30+1Статья настолько свежая, что несколько пунктов уже завезли или в процессе завоза)
t38c3j
21.09.2022 14:46+22Меня забавляет что школа онлайн образования продвигая курсы, переводит статьи, вместо того чтоб со своими ведущими экспертами как написано на сайте, сообща написать новую техническую статью
Pastoral
21.09.2022 14:51Переводя статью 2017 года автор решил тонко обратить взор читателей на историю социа-демократического движения? Тогда при чём тут Хабр?
hard2018
21.09.2022 14:55-3Что насчёт переносимости? Если я хочу запустить веб на основе приложения или наоборот. Здесь, наверное, java лучше. Или проекты на java не переносятся.
Sigest
22.09.2022 07:56+1Что имеется в виду про переносимость? Взаимодействие с джавой работает в большинстве случаев хорошо. Есть косяки конечно, у меня как-то не получилось в котлине отнаследоваться от джава класса, кажется причина была в том, что котлин имплиситно вставляет гетеры/сеттеры и override джавовского гетера ломался, но точно не помню. Если под переносимостью понимается конвертация джава классов в котлин, то в Idea это выполняется одним кликом мыши. Не идеально конвертится, но руками потом можно поправить. Ну и с каждым разом команда Jetbrains эту конвертацию улучшает. Хотя перечитал ваш вопрос еще раз, какой-то бред написан, но раз уж настрочил я ответ, то запощу его
codecity
21.09.2022 15:10В C# почти все эти фишки добавили в новые версии языка - не нужно никуда переходить. Возможно и в Java добавят - так что бегать туда-сюда смысла нет, лучше подождать.
igrishaev
21.09.2022 15:32+10Очень жаль, что у компаний нет кармы. Иначе ее бы быстро спустили в минуса за подобный треш.
SerJook
21.09.2022 15:36-1Какие реальные преимущества Kotlin перед Java, если отбросить синтаксический сахар?
Другими словами, что можно написать на Kotlin, чего нельзя на Java?LordDarklight
21.09.2022 16:15+3Что можно написать на Java, что нельзя написать на С++ или С, а что можно написать на них, чего нельзя написать на Asm
В современных ЯП в рамках одной парадигмы (хотя они сейчас как раз-таки становятся мультипарадигменными) всё так или иначе определяется синтаксическим сахаром и моделью выделения памяти и контроля ссылок - но если с памятью всё, в первую очередь, определяется средой выполнения и компилятором, то в рамках одного движка остаётся только удобство написания программ и удобство их тестирования и отладки - что, в итоге, и определят скорость и качество кодирования алгоритмов, и отчасти их производительность и надёжность (как составляющие качества). Так же есть некоторые современные тренды - ЯП развиваются - люди их изучают у ЯП идёт конкуренция за программиста - поэтому ЯП частенько тырят разные синтаксические фичи друг у друга - что приводит к их некоторой унификации и образованию некоторого обобщённого слоя общих семантических подходов. И чем консервативнее ЯП - тем больше он удаляется от этого слоя и тем более становится не интересным!
И ещё - сейчас императивные типизированные ЯП делятся на две группы
Левая типизация как у Java , или Dart, или С++, или C#, или Perl / PHP
Правая типизация как у Kotlin или Rust, или Go, или TypeScript, или Pascal
И у каждого подхода есть сторонники и противники (вот тут есть на хабре небольшой анализ). Но в целом сейчас тенденция в современных ЯП к переходу к правосторонней типизации. Вот такой заменой и является Kotlin для Java. Ну и в Kotlin ещё завезли более продвинутую модель ООП (в первую очередь со свойствами и с делегатами свойств) и "рефлексию" - вот, кстати, расширенных метаданных в Java библиотеках нет, и тут много взаимовытекающих ограничений.
LordDarklight
21.09.2022 16:50(не уложился в таймаут редактирования написанного комментария - добавка к моему комментарию)
...взаимовытекающих ограничений (например отсутствие значений по умолчанию у функций; или при использовании дженериков)
С дженерик определениями тоже - куда удобнее при правосторонней типизации (дженерик определение слева, а его использование справа - читаем слева на право).
Так что я всецело за правостороннюю типизацию.
А ещё за правосторонние директивы и аннотации (ну или на выбор - по удобству слева или справа - оба варианта можно сделать допустимыми одновременно).
И вообще на эту тему давно хочу написать свою статью!
Но пока ЯП Kotlin нет в даже в 20-ке самых популярных языков программирования, по версии Tiobe, в 2022 году. Java на почётном 3-тьем месте. А вот, на первое неожиданно вырвался Python. А на втором - неожиданно находится язык Си. Даже второй прямой конкурент (после Java, в области клиент-серверной и мобильной разработке) как Swift находится на 16 месте. А Kotlin оф язык мобильной Android разработки и мультиплатформенный, а Swift - практически только инфраструктура Apple
Pastoral
21.09.2022 21:48Есть и другие рейтинги, равно как и более менее взвешенный подход к ним. Эппл, естественно, побеждает везде где участвует, но и Котлин не выглядит настолько безнадёжно насколько это кажется только по Tiobe.
Мне, по наивности конечно, кажется, что сейчас инициатива у Эппл которая фактически предлагает писать на Swift для Мака, iPad и бэкенда и забыть про всё остальное, с упором на «всё». На что конкуренты отвечают кроссплатформенностью, которая вынуждает их а) включать и платформы Эппл, так что Эппл в эту игру выиграть может а проиграть нет, и б) грызться между собой, так что как только Rust допускают в ядро Линукс, так Гугол кидается лепить Carbon.
И преимущество в этих условиях будет иметь не тот язык который лучше (кстати, даже вынужденная кроссплатформенность снимает аргумент «для каждой задачи свой», который я всегда считал придумкой маркетологов, ибо задача одна - записать обнаруженное способом понятным и человеку и машине), а тот, у которого лучше инструментарий. В случае Котлин это означает - подождём выхода Kotlin Multiplatform на релиз.
sshikov
21.09.2022 19:03-1Оставаясь в рамках JVM, ничего нового вы не напишете конечно, что нельзя было бы на Java. Но котлин умеет в разные среды исполнения компилить (например в JS). Пожалуй только этим принципиально отличается.
Left2
22.09.2022 15:46+1Попиши на Джаве асинхронный код. С CompletableFuture и цепочками .then(). И так чтобы условий там в цепочках было побольше, циклов и т.п. Очень, очень быстро захочется нормальных корутин, и очень сложно будет вернуться назад на чистую Джаву. Это самое главное преимущество, не считая приятных мелочей вида null-safety, extension functions и т.п.
BugM
23.09.2022 00:15-2Подождите до легких потоков. Все эти фьютуры не нужны в 99% процентов случаев. Хватит обычного потока. Он удобнее прям со всех сторон.
Котлиновские корутины это абсолютное зло для любого развесистого проекта. Раздебажить гонку в таком коде занимает какое-то неприличное время. А посадить баг с гонкой легче легкого.
n43jl
21.09.2022 16:19+1мне понравилось вот такое рассуждение о перспективах Котлина против джавы: https://www.reddit.com/r/java/comments/ndwz92/can_i_get_some_reasons_to_use_java_instead_of/
JavaFox
21.09.2022 22:54+5Все хорошо, только судя по всему после бурного успеха, Jetbrains стали типичной корпо и вместо того что бы фиксить баги в своем софте или добавлять важные фичи за которые любили раньше их ide , то сейчас с каждым новым выпуском добавляют всякую фигню, типо нейросеть теперь дописывает код за вас, совместное писание кода и т.д. А пофиксить баг, что в котлине во время дебага suspend функции нельзя нормальной пройтись по callstack и тебя будет кидать в .class файлы, а не в .kt или .java ? А добавить элементарную функцию в rider и resharper, раз вы поддерживаете blazor, возможность видеть только изолированные для данного компонента стили? Всегда все любили jetbrains за то что делалось программистами для программистов
numb
22.09.2022 09:33+1почему вы должны рассмотреть его для своего следующего проекта
А я только только Rust начал изучать(
tolik_anabolik
22.09.2022 09:35+1Некоторые пункты, приведенные как фичи котлина, уже реализованы в современных версиях java. Некоторые пункты, приведенные как плюсы, выглядят сомнительными, а местами это вовсе вкусовщина.
persons .filter { it.age >= 18 } .sortedBy { it.name } .map { it.email } .forEach { print(it) }
Называть подобный код более читаемым и поддерживаемым очень спорно. Более того, почему-то здесь не упомянуто про неочевидный return из лямбд, который в отличие от java делает возврат из окружающей функции. А чтобы выйти из лямбды придется вбивать возврат с меткой return@label. И такой код они называют читаемым, спорно, но поверим.
Другой момент с null-safe. При работе с БД у вас многие поля будут nullable. И тогда привет огород из элвисов: ?. ?. ?. Может и нечастый кейс, но точно нередкий.
Самая важное в этом всем. Котлин завязан на инфру java, но при этом развивается своим путем и добавляет свои фичи. И чтобы профессионально использовать среду вам придется хорошо разбираться и в java и в котлине.
sshikov
22.09.2022 18:56>Называть подобный код более читаемым и поддерживаемым очень спорно.
Где-то дело вкуса, да. Но в тоже время — это и котлин, и чистый груви, прямо вот без изменений. Ну разве что названия методов могут быть чуть другими — груви старше. И у такого стиля есть свои поклонники (и преимущества). Например, оно лучше компонуется.LordDarklight
23.09.2022 15:43У Groovy и Kotlin несколько разная модель типизации. Ну и Kotlin это не только JVM
Но большое влияние Groovy, Scala, C# на Kotlin не оспоримо!
sshikov
23.09.2022 18:30Ну да. Про то что это не только JVM, я уже где-то тут написал — кто-то поставил минус, без аргументов. И да, они все конечно разные. Я про другое чуть-чуть — что задолго до котлина (скала же 2004 год вроде?) у тех кто был не полностью удовлетворен Java (а она тогда развивалась не в пример медленнее, чем сейчас) была куча возможностей. У них был выбор. Даже JYthon вполне пригоден к применению, а уж тогда и подавно был. Кложа, опять же. Есть варианты на любой вкус, и давно. И когда автор в 2017 говорит: «Ой, я тут котлин обнаружил», мне кажется он просто был нелюбопытен предыдущие годы )))
Left2
22.09.2022 10:41+5Ко-ру-ти-ны. Даже если бы были только они и интероп с джавой - УЖЕ нужно бы было переходить. А тут - нашелся пункт для свистоперделок типа when (которые приятные и удобные - но все же МЕЛОЧИ) - а для самого главного - корутин и асинхронности на их основе - не нашлось.
Bakuard
22.09.2022 20:23+1Пункты 3,4,8,10,13 — есть и в Java, причем некоторые из этих пунктов присутствуют в языке давно. Пункты 5 и 11 — я бы не стал торопиться называть преимуществами. Создаётся впечатление, что последняя версия Java, с которой знаком автор, это в лучшем случае восьмая.
harios
Аж 18 плюсов и ни одного минуса? Прям пахнет "объективностью".
SuperTEHb
Под такое описание подходит С++, взятый 9 раз. И даже "объективностью" тоже попахивает (от слова "объект").
izogfif
Держите один: тернарного оператора (это который ?:) в Котлине нет. Вместо `a = (b == c) ? d: e` нужно писать `a = if (b == c) d else e` (точки с запятыми и фигурные скобки расставить по вкусу).
LordDarklight
Совсем некритичный недостаток - тут просто все современные языки делятся на два класса:
Инструкции = выражения - корни растут из функциональной парадигмы
Инструкции ≠ выражения - корни растут из императивной парадигмы
Ну и бывают гибриды, переходящие от группы 2. к группе 1.
Тернарный оператор обычно реализован во второй группе языков и редко в первой.
Kotlin в большей степени относится ко первой группе ЯП
И отсутствие тернарного оператора не так уж удлиняет код и не лишает его читаемости - зато ключевое слово when очень хорошо заменяет многоуровневый тернарный оператор
Тут всё лишь дело в привычке - но да - к тернарному оператору в императивных языках привыкли многие - и его, конечно, можно было бы добавить для упрощения перехода с других императивных языков - но это совсем не обязательно - и мне кажется, к концу века тернарный оператор вовсе отомрёт (в том виде, как мы его знаем)
LordDarklight
Добавка.
Первый раз тернарный оператор проявился в императивном языке программирования Алгол-60 (1960) и это как раз было выражение вида "res := if o1 then o2 else o3". Но в таком виде в императивных языках он далее почти не фигурировал. Может быть вплоть до императивно-функционального ЯП Scala (2004).
Что-то подобное именно оператору появилось чуть позже в языке BCPL (1967): res := (
o1 -> o2, o3
)Ну а самый распространённый синтаксис тернарного оператора появился уже только начиная с языка Си (1972): res := o1 ? o2 : o3
Но, замечу, что в самом популярном (в 2022 году) языке Python тернарный "оператор" (выражение) только такой: res := if o1 then o2 else o3
А в Swift, Java, JavaScript применяется Си-подобный тернарный оператор
sshikov
del
AnthonyMikh
Ну нет же
res = o2 if o1 else o3
LordDarklight
Век живи, век учись - спасибо что поправили
Вот вам - ещё один вариант синтаксиса - уже формально четвёртый в императивных языках - а ведь ещё что-то такое бывает res = if(o1, o2, o3) - привет от Visual basic .NET - а ещё такое бывает (оооочень редко) res = ?(o1, o2, o3)
Ну и разные вариации в функциональных ЯП.
LordDarklight
Добавка.
В Kortlin запись "a = if (b == c) d else e" можно интерпретировать так
Сначала срабатывает инфиксная функция else (более приоритетная чем if; захватывает левое и правое выражении) - которая получает на вход два лямбда-выражения - и объединяет их в новое лямбда-выражение - что-то типа { -> Pair<()->Unit,()->Unit>(d, e) } (вернее тут будет, конечно обобщённый тип результата) - т.к. фактически пакует два выражения в кортеж, который затем пакуется в анонимную функцию с замыканием аргументов исходных выражений.
А затем уже применяется функция if с двумя входными параметрами - один булевый, второй (правый) принимающий на вход лямбда-выражение с типом результата функции else (этот тип - лямбда-функция)
Так как это лямбда-функция на конце - то этот аргумент может быть пропущен при вызове, с захватом следующего (справа) блока кода - являющегося результатом функции else (тут я немного луквалю - т.к. у Kotlin тут есть нюансы реализации логики, ограничивающие прямую реализацию такого подхода на языке Kotlin - но компилятор может сделать для себя исключение)
Ну а далее внутри функции if проверяется условие и в зависимости от его результата распаковывается и вызывается то или иное выражение и возвращается его результат.
Далее привожу примерный код с некоторыми уточнениями и допущениями:
Упаковка в кортеж Pair<()->T,()->T>)(f1,f2) осуществляется встроенной в Kotlin инфиксной функцией f1 to f2 (в коде ниже будет написано "this to f2")
if и else заменены на If и Else соответственно, т.к. являются ключевым словом (компилятор же ключевые слова может обрабатывать как ему будет угодно, но пользовательском коде ключевые слова как идентификаторы запрещены)
Kotlin не подхватывает результат функции Else как лямбда-функцию, которую можно было бы захватить справа - поэтому закомментированная строка не компилируется (но внутри компилятора это может быть реализовано в виде исключения - каких-то логических препятствий для этого нет - всё корректно определено)
Поэтому передаю этот аргумент явно значением второго параметра функции If
По ряду синтаксических нелогичных ограничений языка Kotlin пришлось ввести вспомогательный обобщённый псевдоним типа "Fn = ()->T" (и позже это всё-таки ещё "встанет колом")
Вот рабочий код ниже:
Немного подумав - предлагаю вторую реализацию - уже с неявной передачей упакованных выражений во второй аргумент функции If:
Вариант с закомментированными строчками (вместо парных незакомментированных) тоже рабочий - но вызов явно выглядит хуже - но, может конфигуратор поступает так - тут другой возвращаемый тип у функции Else - без запаковки в анонимную функцию - которая образуется уже в виде явного Unit блока после вызова функции If - тогда этот блок захватывается компилятором и передаётся в качестве второго (правого) аргумента функции If.
Вот такое небольшое исследование того, как фактически (с точки зрения внутриязыковой логики) может быть устроен тернарный оператор в Kotlin. Тут, конечно, можно было бы исследовать дальше - например конструкции "val r = if (c1) exp1 else if (c2) exp2 else if (c3) exp3 else exp" а так же рассмотреть более сложный оператор when, а ещё напрямую заглянуть в исходники открытого компилятора Kotlin - но это уже явно потянуло бы на целую большую статью - написать которую я пока не могу.
Даже если в Kotlin всё устроено иначе, чем предположил я - то всё-равно, надеюсь приведённые мной выкладки будут полезны для понимания различных мощных синтаксических фишек языка, позволяющих быстро писать компактный, производительный и красивый код
sshikov
Ну и в скале так же, и чо? Это не более чем непривычно. Хотя еще как посмотреть — если у if есть результат, это как раз наоборот логично, и привыкнуть к этому можно быстро.
InsanusMokrassar
Это сделано специально, чтобы операция тернарника не сливалась с кодом. Плюс есть компиляторные сложности, но их можно обойти.
izogfif
Да там многое сделано специально. Специально for (int i = 0; i < n; ++i) удалили, специально классы по умолчанию нерасширяемые, специально static-методы и поля убрали.
Лично я еще ни одного собственного языка программирования не написал, и уверен, что этому всему была 1000 и одна веская причина, одна другой весче. Но жутко
беситутомляет, когда ты уже лед -дцать гладил кошку и она урчала, а тут только руку поднес, а она тебе внезапно человечьим голосом молвит: "допускается использовать только тыльную сторону руки, и да, не забудьте мизинец оттопырить, иначе я благим матом орать буду вместо урчания".Возможно, еще через -дцать лет удастся привыкнуть и не придется сначала по привычке писать код как привык делать в C++, потом в JavaScript, потом Java (куда только кривая дорожка программиста не заводила), а затем спохватываться, удалять и писать "как тут принято".