Comments 319
Врут
потому что if требует скобок, больше текста, имхо он менее четаем
Если не надо вычислять первое значение, то можно коротко так:
var t = Value0.takeIf(expression) ?: Value1
костыль, но чем-то похож на тернарный...
val max = if (a > b) a else b
это возможно потому, что блоки внутри if возвращают значение
Тут можно много о чем спорить, но большинство ваших негодований выглядят притянутыми за уши либо звучат как «смотрите какой быдлокод я могу написать, а язык не мешает».Проблема в том, что в этом — сама суть Java: язык, в котором на разработчика надевают смирительную рубашку, так что даже человек без опыта, изучивший Java на двухнедельных курсах может что-то писать по данному ему техзаданию.
Kotlin в эту нишу не вписывается от слова «совсем»… с чем разработчикам, долго работавшим на Java, тяжело смириться.
P.S. Собственно если вы посмотрите на историю развития Java — то это постоянная борьба между людьми, которые хотят новые фичи, так как это позволяет писать более читабельный код — и людьми, которые пытаются их не допустить, потому что они также позволяют читать менее читабельный код…
Кто эти люди, которые признали? Рейнхольд? Роуз? Гёц? Мне checked exceptions как раз кажутся очень крутой фичей, всегда их юзал.
Проблема checked exceptions в том, что в конструкции throws нельзя использовать типы-параметры.
Как следствие, если тип Either ErrorsADT еще можно выразить на Java, то тип Either в рамках вашего изоморфизма уже невыразим.
Собственно, на этом все ФП и заканчивается.
Всё что есть — сделано через динамическую типизацию и обмазку в компайл-тайм.
через динамическую типизацию
Не совсем. Просто компилятор при получении из метода T get()
проставляет приведения Integer i = (Integer)get();
к заранее известным типам. Дальше доступ к полям и методам идёт как обычно. Поиска по имени метода при каждом вызове, к счастью, нет.
T get()
нету. Есть вовсе даже Object get()
. И он, вообще говоря, может вернуть что угодно. Динамическая типизация в чистом виде.А приведение типов и выброс исключения — это как раз та «обвязка», о которой я говорил.
Это неплохо работает, на самом деле, TypeScript устроен почти так же. Просто нужно не забывать об ограниченности такого подхода.
Статическая и динамическая типизация — это фичи языка, а не рантайма. И вернуть что угодно он не может: компилятор гарантирует*
, что этот каст всегда будет успешным (при соблюдении некоторых правил).
*
на самом деле нет. Чертовы массивы все портят. Вот за каким надом их решили сделать ковариантными?
То, что язык позволяет загружать динамические библиотеки, не делает его языком с динамической типизацией. Это динамическая компоновка.
По аналогии, даже в С/С++ можно подсунуть неправильную dll (но в нормальных сборках всё-таки используют корректные dll, а не какие попало), но язык динамическим от этого не станет. Подозреваю, что даже в хаскеле так можно.
Проблема checked exceptions в том, что в конструкции throws нельзя использовать типы-параметры.
Вполне можно. Вот такой код прекрасно скомпилируется, по крайней мере в Java 8:
public void <E extends Exception> void func() throws E {
}
Хм, почему-то я об этом не знал… И ведь даже вывод типов работает! В таком случае еще не все потеряно.
В рантайме-то там никакого E не будет, будет просто
throws Exception
(плюс, возможно, автозаворачивание checked exceptions в unchecked).А какая разница что там в рантайме? Компилятор же гарантирует, что "левым" исключениям просто неоткуда взяться.
А .jar, с которым вы собираете ваш проект вовсе не обязан совпадать с тем, что будет реально задеплоено.
j2ee — это та технология, от которой в конце концов отказался Oracle, выпилил все её следы из OpenJDK (включая даже такие повсеместные мелочи как java.bind.xml) и отдал на спасение в Eclipse Foundation? =)
отличный пример, чудесный
А про спрингбут… Да я видел много всего, но по ходу пришел к выводу, что уберджары на спрингбуте — это самое крутое и удобное :)
И этот подход даже не про джаву, а вообще, про жизнь. Например, я использую GNU/Linux и вижу, насколько круче юзабилити у докерных контейнеров или «все свое тащу с собой» по сравнению с пакетным менеджером. Сейчас у меня есть несколько сайтов про разные вещи, и я везде перешёл к хранению важного софта в self-contained докерах, включая базу данных. Это не просто какой-то непонятно откуда взявшееся утверждение, а моё личное глубокое убеждение о том, как должно выглядеть правильное решение, как я делаю и буду делать у себя.
если я использую какую-то технологию, то жду, что технология поддерживает это убеждение. Если нет, она просто не подходит, надо брать другую.
Компилятор казалось бы гарантирует, но на самом деле для того, чтобы "левые" исключения спокойно появились где угодно достаточно подключить Lombok
По этой же причине не будет и throws Exception — для JVM просто нет такого понятия, все проверки check exceptions совершает компилятор.
Плюс, эксепшны в любом случае плохо работают с многопотоком. В распространенных языках вставляют всякие известные костыли вроде
To make it easier for developers to write asynchronous code based on Tasks, .NET 4.5 changes the default exception behavior for unobserved exceptions. While unobserved exceptions will still cause the UnobservedTaskException event to be raised (not doing so would be a breaking change), the process will not crash by default. Rather, the exception will end up getting eaten after the event is raised, regardless of whether an event handler observes the exception.
Мы обсуждаем вот это:
А во вторых, они слабо совместимы с функциональным стилем программирования.
Рассказывать о том, что на джаве тяжело новичку написать лапшекод — это даже не смешно.Лапшекод, написанный на Java, те не менее, будет работать и его можно будет понять. А вот в JavaScript можно такого понаписать, что в коде вообще ничего ни понять, ни исправить будет нельзя… Kotlin находится где-то посередине…
И между этими двумя проблемами развитие Java и прыгает… Kotlin же с самого начала сказал: «да, применяя эти фичи бездумно можно написать непонятный код… и это — не наша проблема», что, собственно, и привело автора статьи в уныние…
Проблема в том, что в этом — сама суть Java: язык, в котором на разработчика надевают смирительную рубашку
Да ладно, ничто этому человеку не помешает написать хренову кучу фабрик, адаптеров, визиторов и прочих абстракций на ровном месте. И читать эту писанину потом также тяжело.
А ему — нормально, когда один класс без методов расширяет другой класс, тоже без методов, который расширяет ещё один (тоже, разумеется без методов).
Может это как-то связано с Open/Closed Principle из SOLID? На новую функциональность нужно пилить новый класс, старый класс менять нельзя. В Java понятно, что сам факт создания класса есть новая функциональность, ибо описание класса доступно через рефлекшен. Может, там где-то RTTI, или даже полноценный RTTR?
Самое весёлое — что всё это было нужно для поддержки разных платформ и вполне можно было бы обойтись буквально парочкой ifdef'ов и некоторым количеством шаблонов.
Но в Java нет ifdef'ов и шаблонов, а для их замены есть… вот это.
Есть ощущение, что это дело привычки.
Есть ощущение, что это от переизбытка умных книжек и бездумного им следования.
Претензия к reduce/fold непонятна и выглядит надуманной. Javascript, Haskell, C# — никто не возвращает Optional! Это же попросту неудобно в большинстве случаев!
Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад и должны быть запрещены в декларации о правах человека. И это считается идиоматично, Карл. В отличие от нормального if. Читать такой код невозможно.Знаете, «идеоматично» и «рекомендовано» — это как бы сильно не одно и то же.
На примере JS, посмотрите, какой код компилирует Babel или тот-же Kotlin. Он на 100% идеоматичен, но за написание такого кода руками в приличном обществе принято очень больно бить ногами и эти самые руки отрывать.
Очень странный момент — возможность не указывать возвращаемый тип методаВо-первых, вам никто не запрещает его указывать, это как минимум правило хорошего тона, даже в языках с динамической типизацией.
Во-вторых, если мне память не изменяет, в Котлине тип dynamic поддерживается только для JS, а для jvm — это вывод типов на этапе компиляции. То есть геморрой с отладкой будет, но скомпилироваться в нерабочий код оно не сможет.
Совершенно очевидно, что среднестатистический быдлокодер забьёт писать свои классы на второй день использования, и код превратится в кошмарную пародию на лисп.Хорошая предъява к языку.
Когда-то из языков изгоняли GOTO, чтобы не было ВОЗМОЖНОСТИ писать путанный код.
Язык не только не должен провоцировать писать плохо, он должен провоцировать писать ясно.
не обязательно для mapOf
:
typealias MyPair= Pair<Int, String>
infix fun Int.to2(that: String): MyPair = MyPair(this, that)
fun main() {
val m = mapOf(1 to2 "we")
val l = 2 to2 "test"
}
это удобнее, чем городить data-класс из двух полей и не зависит от конкретной реализации Map.
А ещё это позволяет "городить" именованный перебор key-value коллекций
PS: и да, olegchir забыл упомянуть, что есть не только Pair, но и Triple...
От интеропа с джавой кровь идёт из глаз. А тут всякие JvmStatic и JvmName, и код превращается в цирк с конями.
Согласен, но мое личное мнение — не надо писать на котлине для джавы. Нужно писать на котлине для котлина. Тогда и проблем с интеропом не будет
Давайте ещё навалим про библиотеку. Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? Это ж прямое поощрение плохого кода.
Совершенно очевидно, что среднестатистический быдлокодер забьёт писать свои классы на второй день использования, и код превратится в кошмарную пародию на лисп
В некоторых ситуациях Pair вполне себе подходит (когда нужно передать пару значений буквально на одной промежуточной операции). Не городить же отдельный бессмысленный дата-класс под каждый такой случай. А что до быдлокодеров — если вы насильно уберет у них Pair это не означает что они сразу станут писать хорошо (или не сделают этот Pair себе сами)
Не стоит забывать, что возможность сделать что-то, не означает необходимость делать это.
Я сам, когда начинал знакомиться с Котлином, думал насколько же там все непривычно и неудобно сделано по сравнению с Java. Но спустя пару месяцев я привык, а теперь уже обратно возвращаться не хочу
В Java ты чаще понимаешь по узкому контексту, что происходит. a = b — запись в поле или локал, a[1] = 2 — запись в массив ..
Да, частично это так. Но с помощью такой записи можно существенно упростить код:
Как самый простой пример:
HashMap<String, Map<String, String>> someMap = new HashMap<>();
//Java
someMap.put("key","value");
//Kotlin
someMap["key"] = "value"
Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях, создавая кучу промежуточных неленивых копий
Возможно только по незнанию и на первых порах. Одинаковый api позволяет делать многие сложные преобразования гораздо проще. И чтобы не плодить промежуточные цепочки (хотя иногда и они нужны) достаточно просто перейти к sequence.
Кстати, об IDE. Насколько хороша поддержка Kotlin в IntelliJ IDEA? Она действительно лучше, чем для Java?
Да, она хуже :) но не сильно. Если даже сравнивать с тем же Go, то поддержка языка лучше сделана. (да и сам релиз Kotlin состоялся только в 2016, пара лет всего прошло)
Котлин форсит использование it, что приводит к нечитаемому коду. Что-нибудь типа seq.map { it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }. Что это вообще было?
Это может вызывать трудности только по началу. It значительно упрощает написание лямбд с одним аргументом. А в тех местах, где есть сложные преобразования всегда можно перейти к именованному аргументу.
Цепочки вроде ?.let { foo(it); }?.let { bar(it); }
Аналогично, сложности только по началу возникают. И Если таких цепочек становится много, то скорее всего что-то делается не так, и скорее всего можно сделать по другому.
От интеропа с джавой кровь идёт из глаз
Отчасти согласен, что местами не очень удобно, но все довольно просто и понятно. Даже тот же @JvmStatic по больше части не нужен, и функция просто выносится на уровень файла.
Экстеншн-методы загрязняют публичный интерфейс такими вещами, о которых автор и подумать боялся.
Автору и не нужно думать :)
Это лишь способ сделать api удобнее у классов, которые чаще всего используются в проекте. И в любом случае от них гораздо больше пользы чем вреда.
Библиотека местами не продумана. Например, reduce.
Тот пример с reduce/fold, что вы привели, это довольно устоявшаяся конструкция. И как уже было сказано выше, текущая реализация вполне удобна.
Давайте ещё навалим про библиотеку. Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? Это ж прямое поощрение плохого кода.
Как же без пар :) Они весьма полезны. И в коде очень часто возникает необходимость вернуть именно два аргумента, и для этого отлично подходит пара. Да, не везде их нужно использовать, и порой лучше сделать еще один «data class», но для «write once» или просто прототипирования они подходят отлично. А говнокод можно сделать и без них.
Очень странный момент — возможность не указывать возвращаемый тип метода (особенно публичного).
Иногда его и правда можно опустить. Как возвращение того же when, или простые однострочные конструкции. Но по большей части хорошей практикой считается явное указание возвращаемого типа.
так и не выучив функционального программирования
Не совсем понятно причем здесь ФП
Прочитал первый абзац и заключение и понял что автор толком ни в чем не разобрался и наверное не особо хочет разбираться.
Без IDE ничего не поймёшь. А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.
Ситуация высосана из пальца. Не соглашусь в корне, в Java тоже есть много всякого во что без IDE сложно въехать. Перегрузку операторов вас никто не заставляет использовать, на Котлине можно писать в Java 6 стиле если очень хочется, дятловать или нет вам решать.
не просто хочу разбираться, а в некотором смысле это теперь моя работа.
Не совсем понимаю, в чем проблема "Без IDE ничего не поймешь".
Иногда выбор языка основан на степени "ВАУ, СКОЛЬКО ВСЕГО В IDE ДЕЛАЕТСЯ ЗА МЕНЯ!".
Отбрасывать инструмент, с которым проводишь рабочее/личное время потому, что когда-то, возможно, в каком-то поезде, где почему-то нет возможности зарядиться, (но есть необходимость работать) ноутбук сядет на пару часов быстрее — как-то… необычно.
Совершенно очевидно, что среднестатистический быдлокодер забьёт писать свои классы на второй день использования, и код превратится в кошмарную пародию на лисп.
Я писал AWS Lambda на Котлине в ФП стиле с карированием, композицией функций и использованием исключительно функций. Так вот если вы вменяемы и пишите тесты до или после ваших функций, то ваш код выглядит не хуже а даже лучше привычного SOLID-ного кода, ибо тут не один класс, одна ответственность, а одна функция.
Не вижу смысла использовать классы как средство защиты от быдлокодеров, это как минимум предвзято. Шедевры такого рода на несколько порядков чаше встречаются, как раз, в мире ООП откуда и родились анти-паттерны типа God Object итд.
Вы невнимательно читали комментарии, правильная цитата должна выглядеть вот так:
ну так никто не заставляет Вас её так использовать.
Вот есть в языке фича — давать переменным произвольные имена. Хорошая она или плохая? Я считаю что хорошая — я могу дать переменным осмысленные имена, и код станет понятнее. Но кто-то другой даст всем переменным имена a1, a2 и a3 — и код будет непонятным. Становится ли фича "давать переменным произвольные имена" от этого неоднозначной?
А что не так со стабильностью и долговременной поддержкой? И какое отношение к ней имеют выдуманные претензии из обсуждаемого поста?
Вот три пункта из комментария ниже от Dveim — и те выглядят куда серьезнее...
Про долговременные проекты на Kotlin ничего не знаю, поскольку пишу на другом языке. Но половина «ужаснейших» с точки зрения автора поста фич давным-давно есть в C#, на котором написан тот же Stack Overflow. C 2008 года и по наше время — это достаточно долгоживущий проект?
Черезмерное увлечение «стабильностью и долговременной поддержкой» приводит к тому, что хорошие разработчики начинают язык избегать (из-за многословности и невозможности писать код кратко и понятно)… и вот это влияет на проекты, в долгосрочной перспективе, куда сильнее, чем возможность писать быдлокод.
1) Он появился давно и альтернатив было немногоВообще-то тогда (как и сейчас) количество языков исчислялось сотнями. Так что про «отсутствие альтернатив» — не стоит. Вспомните хотя бы Pascal, на котором первые продукты, написанные не на ассемблере, и Apple и Microsoft писали.
2) За 20+ лет появились вот такие вот монстры на основе пота и крови сотни разработчиковУ старых Java проектов — тоже Style Guide'ов хватает, так что мимо.
Мне очень понравились комментарии:
в Котлине есть вот такая вот неоднозначная фича
ну так никто не заставляет Вас её использовать.
Секундочку, Котлин язык прагматичный, построенный, между прочим, на опыте использования Java (зачастую на неоправданных ожиданиях), следовательно язык включает в себя некоторые плюшки и даже дуализм в плане парадигмы. Фичи не лежат мертвым грузом, ими очень активно пользуется сообщество.
Но вот если вас похитили злобные цыгане индо-россияне и заставляют использовать Котлин а вам вообще в ломы, потому что Java это ваше все, то тогда и только тогда, пишите на Котлине в Java 6 стиле, вам ничего не мешает. Иначе зачем вообще переходить на Котлин?
Интересно зачем вводить фичи в язык, которые не нужно/не надо использовать?
Потому что такого никто не говорил. Не нужно передергивать и использовать эти фичи так, как описал автор. Вот с теми же экстеншенами, например, можно на порядок облегчить ряд задач в разработке, улучшить читаемость кода. Нужно просто понимать когда и как эту фичу применять. Написать плохой код можно и без них.
Я пишу на C#, и треть пунктов из поста уже давно и успешно применяются в этом языке. Никто не засоряет код лишними экстеншенами, лишними перегрузками операторов, про ленивое апи коллекций и с чем его кушать могут не знать разве что стажеры/редкие джуны до первого ревью.
Любой кейс, архитектурно требующий дженерик. Go предлагает россыпь костылей, плодящих типы.
Review the requirements
Step back and revisit the requirements. Review the technical or functional specification (you should have one). Do the specs really demand the use of generics? Consider that while other languages may support a design that is based on type systems, Go’s philosophy is different
…
Этот совет не работает на проектах сложности чуть выше микросервиса. Страдают как раз типы, которые надо или плодить, или вынимать рефлексией и тегами.
Статья смешная во многих аспектах. Анти-паттерны приводятся как жизнеспособные стратегии для борьбы со сложностью.
Да я вообще удивлен, откуда у этой истерики положительный рейтинг.
Не обязательно. Ведь первый совет… Та даааам
Меня особенно порадовал этот пример как аргумент сложности Дженериков:
List<dictionary<string,IEnumerable<HttpRequest>>>
Значит, у нас есть массив, который содержит словарь массивов, в которых лежат реквесты. То есть три уровня вложенности. Я долго старался представить бизнес-требования задачи, для которой написался этот код.
Увы, моей фантазии хватает только на два уровня.
Dictionary<string,IEnumerable<HttpRequest>>
— где ключ коллекции — это айдишник соединения, а значение — все реквесты, которые совершило это соединение.
Тем не менее я допущу, что фантазия у меня недостаточно хороша и бизнес-требования под эти три уровня есть. Как итог — мы имеем задачу с бизнес-требованиями в три уровня сложности, которая по своей сути довольно сложна.
К счастью, C# довольно мощный язык, который позволяет относительно легко и понятно типизировать её. На Go — это был бы типичный неподдерживаемый говнокод. Такие «вроде аргументы за, а на самом деле против» только делают языку ещё хуже.
Как если бы АвтоВАЗ заявляли: «заботясь о клиентах мы не ставим в автомобиль кондиционер, ведь он жрет электроэнергию и вместо этого оставляем множество щелей для проветривания».
Если все что нужно с этим foo?.bar?.x
сделать — это вернуть, то вы правы. Но в иных случаях там будет еще и две лишних переменных:
var x : X? = null
if (foo != null) {
val bar = foo.bar
if (bar != null) {
x = bar.x
}
}
Причем проблема тут не только в переменных, но и в отсутствии идиоматического способа написания: этот код можно написать 6 разными способами (два способа с ветками else и 1 без них умножить на переменную bar, которая может быть как внутри, так и снаружи), и это только нормальные способы! А ведь есть еще и вот такие:
var x : X?
if (foo == null) {
x = null
} else {
x = null
val bar = foo.bar
if (bar != null) {
x = bar.x
}
}
На этом фоне достоинство foo?.bar?.x
еще и в том, что такое написание единственно, и к нему можно просто привыкнуть, вместо того чтобы раз за разом разбирать очередной порядок написания условных операторов.
А как вам такое: в Kotlin нет checked exceptions. А в JVM-реальности они есть.
Таки в Java-реальности или в JVM?
1) котлин хочет усидеть на 3 стульях (js, jvm, native) сразу, и это несовместимо с совместимостью с джавой (put intended). Логика простая: появлятся pure kotlin библиотеки, которые будут частично дублировать функционал уже существующих, но их можно будет использовать в мультиплатформенных проектах. Это приведет к расслоению экосистемы, так как все эти библиотеки будут развиваться независимо друг от друга. Что, в свою очередь, убивает совместимость с джавой; речь не про техническую возможность вызвать код, а про удобство этого процесса.
Пример такого расслоения — scala, где либо есть отдельные java-api, либо библиотеку невозможно использовать из джавы. История повторяется?
Само по себе это разделение не является плохой вещью, но исчезает позиционирование языка как «better java». Что, в случае котлина, является основной selling point для не-андроидоводов.
2) туда же корутины и так называемый kotlin-dsl — библиотека, фундаментально построенная на этом, не будет использоваться из джавы. За примером далеко ходить не надо — ktor пишут лучшие котлинисты мира, но java api там нет.
3) mobile-first развитие => отсутствие плюшек из более новых jvm (8+) в сгенеренном байткоде.
Сделать свой стандарт ничего не мешает, но мне кажется это тоже процесс небыстрый.
Собирает, но без "мелочей" типа invokedynamic (сейчас анонимные классы, по-старинке) и всего остального, что недоступно на текущей андроид jvm.
Возможная аргументация: тогда будет весьма проблематично распостранять библиотеки. Скажем, написал кто-то утилиту под 11 jvm, с ранее несуществующими фичами, залил jar, и при попытке использовать на старой jvm эти самые ранее недоступные фичи будет ошибка. Даже не при попытке, а при подгрузке байткода.
Это решается (можна снова глянуть на пример скалы), но ценой некоторого удобства пользователя. Ну и таких фич немного. Тем не менее, "android-first" развитие, если в 12 jvm выпустят что-то этакое, то котлин очень нескоро будет генерировать соответствующий байткод.
Оказалось, в C++ можно не писать return в методе, который согласно сигнатуре должен что-то возвращать. Это не синтаксическая ошибка согласно стандарту, а undefined behavior. Соответсвенно, программа в рантайме падает с произвольной ошибкой. Чудесный язык — в нем есть специальный синтаксис для неработающих методов.
Я как-то натыкался в коде на такое:
int something;
...
if (blabla...) {
return x;
} else {
return something;
}
другой консультант (проф. М. В. Шулейкин), напротив, скептически относился к самой проблеме и подвергал суровой критике все наши опыты. Этим он принес делу большую пользу.
Теперь по пунктам:
it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }
Ну да, конечно же вместо it, который ни разу не обязателен просто нельзя написать нормальное имя переменной, которое будет понятно читающему такой код, да?
fun List<String>.filterValid(): List<String>
fun List<Int>.filterValid(): List<Int>
Можно было просто написать инлайн класс и на него повесить экстеншн. И да, это и нужно в том числе для того, чтобы решить проблему с кучей экстеншнов в коде. И опять же — ну не нравится — не используй. Всегда можно написать top-level функцию и не мучаться.
Давайте ещё навалим про библиотеку. Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? Это ж прямое поощрение плохого кода.
Ну да, делали по сути для двух вещей, для destructuring declarations и для того, чтоб olegchir писал плохой код. В принципе сразу убили двух зайцев одним выстрелом.
Но стоит только поменять определения функций на вот такие:
Явно указать нельзя разве? Или мешает чего?
В общем как итог — вброс конечно же удался, но в целом котлин предоставляет необходимую гибкость нормальным разработчикам и не ставит перед собой цель забить всех в рамки, где шаг влево, шаг вправо —
Так что ты всегда можешь обмазаться if вместо ?.let
Не всегда, подобные конструкции приходится применять, когда идёт работа с nullable мутабельным полем, т.к. компилятор считает, что они могли измениться после чтения и стать null, не оставляя никакой возможности сказать ему, что разработчик сам заботиться о синхронизации доступа к таким полям. На мой взгляд это самая спорная фича компилятора. Вот тут пытался обсуждать, если интересно подробнее. В итоге обмазываться приходится всеми этими ?.let.
не оставляя никакой возможности сказать ему, что разработчик сам заботиться о синхронизации доступа к таким полям.
О, да ты еще и про котлиновские контракты не слышал! Грустно жить, когда все достижения человечества проходят мимо?
Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях
Проблема программиста же. Если ты не понимаешь, как что-то работает под капотом, или это работает неожиданно, то имеет смысл винить себя и того, кто ревьювил код (если ревью было).
Котлин форсит использование it, что приводит к нечитаемому коду.
Все нормально читается. Неявная подстановка названия итерируемой переменной вполне удобна. Если уж не нравится, то в требования к коду вводить обязательное использование лямбд с именованным(и) параметром(ами).
Цепочки вроде ?.let { foo(it); }?.let { bar(it); }
Их применение зависит от ситуации. Если вам не важно, в каком месте возник null, то вы напишете так, в противном случае шансы увидеть подобный код после адекватного ревью будут достаточно низкими.
Он должен думать обо всех экстеншн-методах, которые любые люди могут добавить в тот же класс?
Вы перевернули все с ног на голову. Я как автор класса используемой библиотеки о таком задумаюсь с очень малой долей вероятности. Если кто-то экстендит мой класс — его право. Однако, изменение данного класса — моя привилегия и если вы хотите продолжать использовать мою библиотеку, то либо меняйте свои расширения, либо используйте старую версию.
Библиотека местами не продумана. Например, reduce
А вот тут я соглашусь с вами. Мне гораздо удобнее вариант, когда аккумулятор ты создаешь сам. К примеру, как это реализовано в Clojure.
Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? Это ж прямое поощрение плохого кода.
Опять же, если код прошел какое-либо ревью, либо не проходил его (если так принято), то это проблема организации бизнес-процессов компании / квалификации ревьюверов и т.д.
p.s. в данном посте обращение «ты» использовано не в панибратских целях, просто изначально свою мысль сформулировал таким образом.
Если не вдаваться в детали, описанные автором, то можно согласиться с общим месседжем: код на Kotlin-е становится вцелом менее понятный, чем на Java, особенно при наличии определенного знакомства с языком. При первом знакомстве все пишут как на Java, поэтому все достаточно просто. Но узнав немного язык, разработчик начинает теряться в возможностях, код становится более плотным и лаконичным, но менее структурированным. В частности постоянно возникают вопросы:
- Один класс на файл или несколько классов в файле?
- Как правильно использовать пакеты?
- Когда использовать статические определения вне классов, а когда делать их в компаньонах?
- Использовать экстеншн-методы, или методы класса?
- Делать ли инициализацию полей при объявлении, или выносить отдельно в init{} — блок? И вообще в Котлине нет четкой границы между определением и поведением. При определении поля можно его и проинициализировать объектом, и сконфигурировать при помощи .apply(), и там же навесить хендлеров и листенеров. Структура класса превращается в кашу.
- Нагромоздить однострочник или разбить все по действиям?
- Для операции с объектом переопределить сеттер на свойство или создать отдельный вменяемый метод?
- Общие рекомендации по стилю.
kotlinlang.org/docs/reference/coding-conventions.html
этот язык отвязал программистов от версии jvm
Ничего подобного. Внезапно выяснилось, что некоторые конструкции валятся в runtime на 6-ом андроиде, при этом работают на 7 и выше.
Например, мы в команде столкнулись с тем, что map.forEach { (a, b) -> foo() } — матчится в Map.Entry<K, V> (который есть в 6-ой Java), а map.forEach { a, b -> foo() } в BiConsumer<? super K,? super V> (которого нет в 6-ой). При это проект собирается абсолютно без ошибок.
А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.
Белые люди в поездах не кодят, они в поездах отсыпаются или, в худшем случае, кину смотрят.
2. Я мог бросить лошадь посреди поля, и она сама ела бы траву. Машины нужно заправлять дурацким топливом — что за бред, кто вообще на это пойдет
3. Когда я ездил на коне, я носил шпоры. Теперь шпоры мешают мне нажимать на педали. Их идиотская форма не смогла предусмотреть такую очевидную вещь, которой пользуются все наездники
4. Машина требует дороги! На лошади я могу залезть на любой холм, для машины же требуется специально готовить трассу, заниматься освещением, инфраструктурой и остальными вещами. Раньше я мог остановиться посреди поля и заночевать с конем, а теперь привык пользоваться ортопедическим матрасом и 3-звездной гостиницей.
Разница этого рассказа и спора между Kotlin-Java в том, что Kotlin забирает очень мало, но дает очень много нового и не отказывается от поддержки старого. Впрочем в статье жалоба «мне так непривычно, а значит плохо» — прослеживается слишком явно.
нужно было делать авто в форме лошади
Так ведь сделали. Мотоцикл называется :)
подковы вешать некуда
чем не подкова? http://drive2moto.ru/uploads/blogs/14525/orig-img1402a.jpg
Раз уж автор попросил в Kotlin Community о конструктивной критике по сути, то она у меня есть.
По пунктам с цитатами из статьи:
В Котлине за любым простым выражением может стоять сколь угодно сложный код из-за всяких умностей вроде перегрузки. Без IDE ничего не поймёшь. А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.
Начать с того, что в чтении/написании Java кода без IDE тоже далеко не уедешь. Что касается самой перегрузки операторов, то в ней особо проблем нет, это всего лишь другой способ объявления методов, который позволяет единообразно писать выражения независимо от типов данных, над которыми эти выражения вычисляются.
Классический пример — BigDecimal в Java:
BigDecimal hundredAndOne = BigDecimal.ONE.add(BigDecimal.TEN.multiply(BigDecimal.TEN));
То же в Kotlin:
val hundredAndOne = BigDecimal.ONE + BigDecimal.TEN ** BigDecimal.TEN
Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях, создавая кучу промежуточных неленивых копий. Стримы в джаве специально введены для различия между ленивой и неленивой коллекцией. Да, есть инспекция в IDE для этого — потому что инспекции призваны исправлять недостатки языков.
В Java, чтобы получить список целых чисел, каждое из которых на 1 больше соответствующего числа из исходного списка нужно будет сделать так:
List<Integer> plusOne = xs.stream().map(x -> x + 1).collect(Collectors.toList());
В Kotlin:
val plusOne = xs.map { it + 1 }
При этом быдлокодеры люди прекрасно в Java делают collect на каждый чих (на прошлом Joker про это был один из докладов) и никакая IDE инспекция им об этом не говорит.
Котлин форсит использование it, что приводит к нечитаемому коду. Что-нибудь типа seq.map { it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }. Что это вообще было?
Действительно, что это было такое? Если уж использовать it, то не надо стрелочек тогда (рак из точек с запятой в конце каждой строки тоже можно убрать). Надо так:
seq.map { foo(it, 1) }.map { bar(it, 2) }.filter { it.getBaz() > 0 }
Если серьёзно, то в лямбдах (они обычно очень короткие) параметры тоже именуют коротко. Давать длинное имя единственному аргументу, который тут же и используется — много чести. По использованию и так понятно, что в нём, либо же для задачи это не важно.
Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад и должны быть запрещены в декларации о правах человека. И это считается идиоматично, Карл. В отличие от нормального if. Читать такой код невозможно.
Да, с if наверно читаемее:
arg?.let { foo(it) }?.let { bar(it) }
будет равносильно
if (arg != null) {
val foo = foo(arg)
if (foo != null) {
bar(foo)
} else null
} else null
От интеропа с джавой кровь идёт из глаз. А тут всякие JvmStatic и JvmName, и код превращается в цирк с конями.
Тут соглашусь, использование Kotlin из Java достаточно не удобно просто потому, что очень многих фич Kotlin нет, вот и приходится лишние приседания делать. BTW, обычно Java библиотеки используются из Kotlin, а не в обе стороны. Очень странен проект, где оба языка будут на равных правах присутствовать.
А как вам такое: в Kotlin нет checked exceptions. А в Java-реальности они есть. Отряд специального назначения «Боевые протезы» имеет честь представить новый самоходный костыль @Throws:
Да, в Kotlin выпилили то, что даже в мире Java уже считают антипаттерном (checked exceptions). Для interop сделали аннотацию. В чём проблема?
Автоматические геттеры/сеттеры с добавлением английского слова get и первой буквой проперти в большом регистре (видимо, в локали ENGLISH? Ведь регистр букв системно-зависим) — это страшно.
Чем страшно-то? Геттеры/сеттеры генерируются согласно устоявшимся в Java мире конвенциям.
Экстеншн-методы загрязняют публичный интерфейс такими вещами, о которых автор и подумать боялся.
Экстеншн-методы не загрязняют никаких интерфейсов. По факту это просто статические хэлпер методы, которые вызываются через точку.
Библиотека местами не продумана. Например, reduce.
reduce так работает во всех языках, где он есть. Я верно понял, что и в Haskell (foldr1, foldl1), и в ruby, и в python они не продуманы, если верить автору?
Давайте ещё навалим про библиотеку. Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары?
Во-первых для mapOf(...), да и вообще, для тех же целей, для которых в Java есть Map.Entry
Очень странный момент — возможность не указывать возвращаемый тип метода (особенно публичного).
Тип выводится только для методов, которые объявлены как выражение. Если метод абстрактный и тип не указан (в публичном интерфейсе, например), то он Unit по умолчанию. Если нужен не Unit — придётся в интерфейсе явно указать, так что проблема надуманная, как мне кажется. BTW, конвенции по коду в языках с выведением типов (Haskell, Scala, Kotlin и т.д.) требуют указывать их для публичного API
Резюмирую: всё как-то мимо с критикой в статье. Похоже автор плохо разобрался в объекте своей критики.
А так-то да, не писать тип проэкспортированных из библиотеки функций — это даже не дурной тон, за такое морду бить надо.
Да, кстати, в Хаскеле вообще нет различий между лямбдой и именованной функцией, равно как и нет различий между функциональными типами данных и остальными. Более того, так как он тотально ленив, то то, что по типу данных является числом, представляется в памяти как thunk (что-то вроде Supplier в терминах Java). Число вместо него в памяти появляется только тогда, когда понадобилось в других вычислениях.
Правда, я не вижу проблем проставлять значения типам всем функциям, чтобы быть уверенным, что в случае ошибки она не пролезла через 10 уровней коллстека откуда-то из кишков.
В любом случае, код, использующий функцию, не скомпилируется, если ожидаемый тип возврата не совместим с выведенным по телу функции.
То же верно и для пулбичных val/var деклараций.
Вы сейчас описали фичи C#:
вроде перегрузки
Котлин даёт одинаковый API для коллекций и сиквенсов,
в Kotlin нет checked exceptions
Автоматические геттеры/сеттеры
Экстеншн-методы
форма без identity кидает исключение для пустой коллекции.
Все они добавлены для того, чтобы не плодить тысячи строк boilerplate-кода. Вы, случайно, не из тех, кто был против var
, ведь нипанятнаже
?
вроде перегрузки
Напомню, что в Java есть перегрузка + для строк, как special case, да и [index]
имеет логику внутри, а не просто складывает указатели. Вы используется велосипедный конкатенатор строк, чтобы, не дай бог, не пропустить выделение памяти?
Перегрузка гарантирует унификацию. .get(...)
/ .elementAt(...)
/ .charAt(...)
заменяются на [...]
. Наличие обращения к методу вам ничего особо не даёт — в большинстве случаев вы обращаетесь как раз к геттеру / сеттеру, даже если там внутри простое присваивание / чтение поля. В значительной части случаев, реализация скрыта из интерфейсом — знание факта вызова ничего не даёт, в нём может происходить всё, что угодно.
Автоматические геттеры/сеттеры
это страшно
// call getFirstDayOfWeek()
- Чем может помочь знание того, что при обращении к объекту вызывается метод, а не происходит обращение к свойству с неявным вызовом метода?
- Во всех языках с перегрузкой полагают, что любое обращение к стороннему объекту — вызов метода.
- В большинстве случаев, для объектов с поведением, как раз плодятся методы вида
get*
/set*
и унификация доступа к ним снижает количество визуального мусора.x.Width *= 2
вместоx.setWidth(x.getWidth()*2)
.
Ведь регистр букв системно-зависим
Вы слышали о такой вещи, как инвариантная локаль?
@JvmName("filterValidInt")
Да, костыль над type erasure, чтобы иметь возможноть перегружать методы, как в языках с нормальной vm, а не плодить множество разных имён, но при этом иметь читаемый код в Java, а не генерировать суффикс-хэш автоматически.
Экстеншн-методы загрязняют публичный интерфейс такими вещами, о которых автор и подумать боялся.
Работа экстеншн-методов возможна, даже если автор специально сделал финальный класс, явно показав, что не хочет сторонних расширений
Он должен думать обо всех экстеншн-методах, которые любые люди могут добавить в тот же класс?
Это не так работает. Extension-методы — синтаксический сахар, после компиляции они становятся обычными вызовами статических методов, не влияя на целевой тип. Доступа к private / protected членам у них тоже нет.
Без IDE ничего не поймёшь.
без IDE в любом мало-мальски сложном приложении фиг поймёшь
А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир
Jetbrains позаботился о батарейке твоего ноутбука и сделал режим "Power safe mode". Более того — он ещё и настраиваемый.
Без какого IDE?
Без любого, умеющего делать подсветку/проверку синтаксиса, форматирование кода и запуск на выполнение. Хотя без последнего тоже можно жить, держа рядом консоль.
Arduino IDE вон тоже IDE, хотя по сути это просто "продвинутый редактор" — даже autocomplete нет
Настроить всё это достойно в «продвинутом редакторе» дело не лёгкое и проблематичное. И всё, что это даст — аналог IDE с чуть большей скоростью в ущерб функционала, поскольку добиться такого же качества едва ли выйдет.
А вообще в 2018 не писать андроид приложения на котлин это моветон.
Это провоцирует людей писать нечленораздельную лапшу, в которой и ничего не понятно.
А не надо чтоб было понятно, надо чтоб было быстро. Все равно код всегда непонятный, если он будет еще чуточку непонятнее, это совершенно ничего не изменит. А вот выкинуть весь чужой непонятный код и быстренько его переписать своим непонятным кодом это кой-чего да стоит.
P.S. А гетеры/сетеры-то чем не понравились, я так и не понял?
Есть интересная статья (и единственная за тоннами хайпа) — Kotlin vs Java The Whole Story. Там сделана попытка обьективно оценить целесообразность перехода на Котлин.
Работа экстеншн-методов возможна, даже если автор специально сделал финальный класс, явно показав, что не хочет сторонних расширений.Котлину можно простить всё. И этого очень не хватает в каком нибудь Go. Ибо меня, как программиста, очень мало интересует мнение автора библиотеки.
Спасибо за быстрое и лаконичное введение в фичи Kotlin-а от лица всех C#-разрабов :)
Как шарпист заявляю: более половины из описанных "странностей" мне очевидны. Kotlin делает так, чтобы писать на Java шарписту стало не отвратительно :) Тут, конечно, есть ещё над чем поработать, но JetBrains движутся в правильном направлении.
Посмотрите на том же тостере сколько вопросов по Джаве и сколько по Котлину — почти ноль.
Дожили! В JugRu теперь платят людям, чтобы они ругали Котлин!
А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.И как часто вы работаете в поезде?
Кстати, об IDE. Насколько хороша поддержка Kotlin в IntelliJ IDEA? Она действительно лучше, чем для Java? Есть большие сомнения.Ну это аргументация уровня Рен-тв. Совпадение? Не думаю.
Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад
Нормально будет, если в цепочке только один такой оператор. И то от безысходности, если не можешь изменить код библиотечной функции. Рекомендую избегать смешивать nullable и nonNullable типы.
Работа экстеншн-методов возможна, даже если автор специально сделал финальный класс, явно показав, что не хочет сторонних расширений.Экстеншн методы не про наследование, а про расширение функционала. Финальный класс так и останется финальным. Его никто не наследует и не заберётся в кишки protected.
что будет, если в следующей версии библиотеки автор добавит методы с теми же именами, но с другим возвращаемым типом?Вам стоит ещё раз почитать документацию. Сработает оригинальный метод.
Да или хоть null вернуть, раз уж это null-friendly язык.
Ага, а потом опять работать с цепочками ?.let { }?.map { }?.filter { }
fun c(check: Int) =
Пожалуйста, не делайте так. Это синтаксис для однострочной лямбда функции.
По пунктам:
В Котлине за любым простым выражением может стоять сколь угодно сложный код из-за всяких умностей вроде перегрузки.
И? То, что вы не знаете как ведёт себя тот или иной оператор в данном конкретном случае — ваша вина. Язык тут ни при чём. RTFM, наконец!
Без IDE ничего не поймёшь.
А то ж вы в проекте на Java/PHP/C#/C++ много чего без IDE поймёте.
А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.
Ну тут мне остаётся только посочувствовать Java-разработчикам (уж не знаю — то ли потому что на поездах ездят, то ли потому что IDE отжирает столько батареи) :)
Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях, создавая кучу промежуточных неленивых копий.
Меня удивляет ваше стремление решать что другие люди должны делать и чего не должны. Вообще-то это неприлично. Вы злоупотребляете? Нет? Ну вот и ладушки. Остальные, полагаю, сами разберутся без вашего мнения.
Котлин форсит использование it
… а вы всё никак не хотите научиться правильно его использовать facepalm
Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад и должны быть запрещены в декларации о правах человека.
Вот опять, обратите внимание. Откуда в вас это стремление запрещать что-либо всем людям сразу, если конкретно вам непонятна эта конструкция? Избавляйтесь от этой черты характера. Некрасиво это.
От интеропа с джавой кровь идёт из глаз.
Это справедливое замечание. Равно и про компаньоны. Равно как и про type erasure.
А как вам такое: в Kotlin нет checked exceptions.
И в C# нет. И в C++ нет. И много где ещё их нет, потому что checked exceptions — это как раз та бессмысленная часть, которая и делает Java многословной без веских на то оснований. В большинстве реальных случаев вам, признайтесь, по барабану список исключений, которые выбрасывает метод. У вас или все ошибки обрабатываются одинаково, или же вы предпочтёте выбрать пару исключений, которые обрабатываются как-то иначе (а все остальные — всё равно одинаково). Тащить за собой список исключений в этой ситуации не имеет никакого смысла. Практической пользы от него — 0.
Автоматические геттеры/сеттеры
Господи, ну наконец-то хоть у одного JVM-языка появилась эта функциональность. Наконец-то на JVM-языке (вкупе с перегрузками) можно писать point2.Z = (point1.X + point1.Y)*(point2.X - point2.Z)
, а не point2.setZ(Point.mult(Point.sum((point1.getX(), point2.getX())),Point.subtr(point2.getX(), point2.getZ())))
. Да здравствует человеческая инфиксная запись! Ура, товарищи! К 2018 году JVM-язык научился делать то, что человечество использует уже несколько веков.
Экстеншн-методы
Читайте что такое примеси и будете вознаграждены. Не надо критиковать то, чего не понимаете — вас на смех поднимут. Тем временем, примеси — мощнейший механизм, который позволяет делать шедевральной компактности и выразительности код. Немного терпения в освоении и вам откроется истина. Шарписты гарантируют.
reduce и fold
Эм… Вообще-то они так работают во всех языках, в которых они есть. В C# вот reduce-а в явном виде нет. Но его легко можно написать. И когда вы это сделаете — вы поймёте почему reduce на пустой коллекции должен бросать исключение.
Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары?
Чтобы вы не захламляли код коммерческого приложения своего работодателя отрядами классов из двух пропертей/полей. Знаете, если вы работаете в команде лоботрясов, то один лоботряс сделает свой Pair для использования в паре мест, второй сделает такой же, третий… И вот у вас уже 10 разных Pair-ов с разными типами и именами, в разных пакетах от разных лоботрясов. Чтобы этой фигни не происходило, люди придумали встроенные в язык туплы (а заодно и деконструкцию классов, просто потому что могут).
Если же вас тянет использовать туплы чтобы заменить ими ВООБЩЕ ВСЁ — вы больны. Не надо проецировать свою болезнь на других. Специально чтобы технически оградить психически нестабильных персоналиев от подобной ереси, встроенные туплы обычно ограничивают 4-8 тип-аргументами. Если вам нужно больше — обычно это говорит о том, что надо делать отдельный промежуточный класс (дата или не дата — это уже вам решать).
Очень странный момент — возможность не указывать возвращаемый тип метода
Очередной привет людям, которые пугаются type inference-а. Идите к группе людей вон там в углу, которые не понимают зачем нужен var. И предложите им оперировать хэшмапой тупл-> список generic-интерфейсов без var и с явным указанием результата работы метода. Ах, вы такого в жизни никогда не писали? Так может имеет смысл чуть по-дольше поработать в индустрии прежде чем критиковать?
Для публичных методов явная спецификация API должна быть
Она и есть. Метод возвращает kotlin.Any
. Это очевидно компилятору, очевидно пользователям kotlin, очевидно пользователям библиотек с такими заковырками. Всем, кроме вас.
В общем, автор. Ничего личного, но поднимайте, пожалуйста, квалификацию.
В C# вот fold-а нет
Есть. Он называется Aggregate.
double product = doubles.Aggregate(1.0, (prod, next) => prod * next);
Она и есть. Метод возвращает kotlin.Any.
Ну уж нет! Метод с автоматически выведенным типом результата возвращает этот самый автоматически выведенный тип, но никак не kotlin.Any.
Нене. Я подозреваю что там type inferer ищет максимально общего предка для всех возможнных return-значений. Если, как автор в своём примере, мы подпихиваем совершенно несовместимые типы, то в результате разумно предположить что будет kotlin.Any
. То есть это было сказано про данный конкретный случай, а не вообще. Хотя надо пощупать это поведение.
Иными словами, kotlin не запрещает стрелять себе в ногу. И это правильно.
//java
int sum = point1.getX() + point1.getY();
int subtr = point2.getX() - point2.getZ();
point2.setZ(sum * subtr);
//kotlin
var sum = point1.x + point1.y;
var subtr = point2.x - point2.z;
point2.z = sum * subtr;
//Java
point2.setZ((point1.getX() + point1.getY()) * (point2.getX() - point2.getZ()));
//Kotlin
point2.z = (point1.x + point1.y) * (point2.x - point2.z)
Если вам нужны локальные переменные только для упрощения чтения кода — это уже о чём-то да говорит.
Если они нужны где-то ещё далее по коду, то пусть будут конечно. Только в Kotlin рекомендуется использовать val
.
Ребят, обратите внимание что мой пример — он демонстрирует акцессоры вкупе с перегруженными операторами. X/Y/Z в общем случае могут быть не числами.
В данном моменте я как раз за Kotlin.
А уж если предположить что поля Point
не наследники Number
, то в Java и математические операторы использовать нельзя.
В случае такого варианта:
//Java (и то только если X/Y/Z иммутабельны)
point2.setZ(point1.getX().add(point1.getY()).multiply(point2.getX().subtract(point2.getZ())));
//Java (с переменные для "читаемости")
CustomNumber sum = point1.getX().add(point1.getY());
CustomNumber subtr = point2.getX().subtract(point2.getZ());
point2.setZ(sum.multiply(subtr));
//Kotlin
point2.z = (point1.x + point1.y) * (point2.x - point2.z)
Что в первом что во втором варианты Java-кода я не могу сходу опередить что там происходит — код приходится парсить глазами.
При этом Kotlin-код читается без проблем.
Очень странный момент — возможность не указывать возвращаемый тип метода (особенно публичного).
fun width() = right - left
Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад и должны быть запрещены в декларации о правах человека.
В конкретно этом примере правильнее скорее будет
?.let(::foo)?.let(::bar)
.Хотел проехать по всем пунктам, но не стал. Напишу лучше только что мне самому не нравится в этом языке:
1. Такая форма записи не сработает
val color: Int = 0xFFFFFFFF
.2. Наличие type-erasure доставшееся от того же JVM.
3. Ограничения с наследованием
data class
-ов.3. Конфликты с методами-сеттерами и обычными полями с сеттерами.
open class Foo {
fun setValue(value: int) {}
}
class Bar: Foo() {
var value = 10
}
После Java язык как глоток свежего воздуха.
Но если б это было так, то в вакансиях первым делом висело скорость печатания — от 800 знаков в минуту, скорость чтения от 1000 слов в минуту.
Вживую, я ни разу не видел программиста, который соображает быстрей, чем печатает или читает. Никогда, еще на моем веку скорость понимания программы или починки бага не упиралась в скорость чтения или печати.
Большинство программистов, упираясь в лаконичный тернарный оператор, ставят пальчик в экран и морщат в лоб. Многие развертывают лямбды в анонимные классы. Никогда написание и чтение стримов не занимало меньше времени, чем переборка.
Банально с = a>b?a:b — это гораздо хуже, чем с = if(a>b) a else b; хотя тут и букв больше.
Есть определенные пределы сокращения кода, когда он перестает быть читаемым. В идеале скорость чтения должна совпадать со скоростью понимания. Потому что в мозгу вы все равно развертываете тернарную конструкцию в if и на это тратится ценное время.
Каждый раз, когда я читаю про краткость и лаконичность кода, вот честно хочется плакать и менять проффессию. Все программисты представляются хакерами из кино, которые 8 часов в день фигачат код со скоростью 1000 знаков в минуту.Дело не в скорости написания кода. Дело в скорости и простоте его чтения.
Злоупотреблять конечно не стоит, но когда вы не можете написать что-то без boilerplate в 3 раза длиннее полезного кода — это ещё хуже.
Потому что в мозгу вы все равно развертываете тернарную конструкцию в if и на это тратится ценное время.На своём коде я привык так, что тернарный оператор — по определению простой и односложный, максимум одно сравнение и одна операция. А если встречается if, значит дальше пойдёт какая-то жесть, и надо переключаться с быстрого чтения на вдумчивое.
Первый — ненавистный бойлерплейт, но простой как лом и пишется и читается, второй посложней, но увидя код надо напрячь мозги, чтоб понять что он делает, а если с рефлексией давно дел не имел, то скорей всего придется гуглить синтаксис, чтоб написать. Но покороче и потяжелей, но более гибок, не надо дописывать метод, если добавилось новое поле.
Что правильней писать?
Третий — с помощью рефлекшна пробежаться по полям и скомпилировать делегат, который будет делать нужную работу с качеством написанного ручного кода. Когда-то делал такую штуку, чтобы сравнить все поля двух объектов в тестах.
Четвертый — сделать нормальное АОП, где просто можно атрибутом повесить #[derive(Default)]
и получить то же самое от компилятора.
вопрос собственно в том что лучше: написать двадцать тупых сеттеров, или рефлексией пробежаться по полям ставя им нолики.Как я уже говорил, зависит от…
А если есть поля, которые надо не трогать?
А если правило заполнения может поменяется?
А если типы у полей разные?
А может ли сигнатура у объекта поменяться и если да, то надо ли новые пполя тоже забивать нулями.
Кроме того, использование 20 тупых сеттеров, — это не бойлерплейт.
Бойлерплейт это объявление 20 тупых сеттеров. То есть у вас тело объявления дата-класса разрастается с 20 строк до 140, если делать всё «по феншую».
ИМХО, в данном случае лучше вообще сделать для объекта метод clear и сохранить значения полей напрямую.
Делаете конструктор и заполняете нулями.
По-умолчанию дефолтным состонием объекта можно быть что угодно. Вон, вам "null" например не подходит, хотя именно это дефолтное состояние для типа "ссылка". Вам подавай проинициализированную ссылку.
Учитывая, насколько задача искусственна, нет смысла надеяться на встроенный способ решения.
Вы так все фичи записали в баги.
Экстеншн-методы загрязняют публичный интерфейс такими вещами, о которых автор и подумать боялся.
Всего лишь синтаксический сахар к вызову функции. Позволяет писать более лаконичный, но менее понятный код. Как и автовывод типов. Как и многие другие фичи. Но да, для их использования нужно знать язык, чтобы код был понятным.
Есть, конечно, и сомнительные фичи, но совсем не так много, как вы пишете.
Автоматические геттеры/сеттеры с добавлением английского слова get и первой буквой проперти в большом регистре (видимо, в локали ENGLISH? Ведь регистр букв системно-зависим)
Что значит "системно-зависим"? Не слышал, чтобы .toUpperCase/.toLowerCase работали как-то, отклоняясь от правил юникода.
Без IDE ничего не поймёшь. А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.
Высасывает батарейку не "свинговый жабоинтерфейс", а разбор кода и куча инспекций. Вы же используете эти IDE не из-за любви к странным интерфейсам, а потому, что они помогают решать задачи?
Оказалось, в C++ можно не писать return в методе, который согласно сигнатуре должен что-то возвращать. Это не синтаксическая ошибка согласно стандарту, а undefined behavior. Соответсвенно, программа в рантайме падает с произвольной ошибкой.
Я буду читать предупреждения от компилятора
Я буду читать предупреждения от компилятора
Я буду читать предупреждения от компилятора
Настраивать же «варнинги как ошибки» получается нужно очень дотошно, потому что список варнингов многие тысячи, и все их нужно просмотреть, и понять, какие из них на самом деле должны были быть ошибками.
По сути, претензия к тому, что для подобной «фичи» выбран неверный уровень опасности. Это не просто «предупреждение», это полноценный баг в 100% случаев.
— Бтв, интересно, какие ваши действия, когда вы приходите на новое место, а там сборка проекта выплевывает тысяч 10 ворнингов? Переключаете по одному на ошибку раз в неделю на протяжении 25 лет?
Смириться с этим и по мере возможностей заворачивать в фантики
Как-то так.
Если, несмотря на предупреждения, основнове время таки-реально уходит на новые фичи — то тут можно только снять шляпу и удивляться.
Но обычно если ворнингов тысячи, то «взрывается» постоянно и чинится 90% времени. А на новые фичи остаётся 10% времени. И тут можно уже что-то обсуждать.
P.S. Конечно перед этим нужно потратить какое-то время на анализ. Потому что сами такие компании бывают абсолютно уверены в том, что у них 100% времени уходит на новые фичи. Но если копнуть и выяснить почему некий Вася добавляет иконку не час, а две недели — то выяснится, что бóльшую часть времени — он чинил баги, которые проявились, когда он оную иконку добавил. И вот это-то время нужно отделить от времени добавления новых фич.
Это не предупреждение должно быть, а ошибкой.К сожалению C++ не Java. В Java — это ошибка. В C++ — нет.
— Бтв, интересно, какие ваши действия, когда вы приходите на новое место, а там сборка проекта выплевывает тысяч 10 ворнингов?Как правило из этих 10 тысяч 90% — это какая-нибудь непринципиальная чушь, которую можно отключить.
Типа использования ols-style cast вместо
static_cast
/const_cast
/reinterpret_cast
.Но вообще — ворнингов быть не должно. Если вы какую-то вещь не хотите энфорсить (скажем обработку всех вариантов
enum
'а во всех switch
'ах), то эти ворнинги нужно отменить. -Werror
можно (и нужно!) выключать только в релизе (так как не всегда известно каким компилятором его будут собирать), во время разработки он должен быть включён.К сожалению C++ не Java. В Java — это ошибка. В C++ — нет.
То что С++ это разрешает, не делает это ошибкой в меньшей степени :) Ошибка по сути в спецификации С++, которая разрешает такое поведение.
Как правило из этих 10 тысяч 90% — это какая-нибудь непринципиальная чушь, которую можно отключить.
Для этого их надо все проанализировать, а на это обычно никто не соглашается, и так работает ведь, и вообще отключи их показ и все нормально (с).
— Бтв, интересно, какие ваши действия, когда вы приходите на новое место, а там сборка проекта выплевывает тысяч 10 ворнингов?Увольянюсь из такой говноконторы.
Думал некоторое время, отвечать или нет на очевидный вброс, тем более, что выше уже отписались. Решил, что добавлю то, чего выше, как мне показалось, недостаточно расписали.
Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях, создавая кучу промежуточных неленивых копий. Стримы в джаве специально введены для различия между ленивой и неленивой коллекцией. Да, есть инспекция в IDE для этого — потому что инспекции призваны исправлять недостатки языков.
С этим можно согласиться. С другой стороны, тот же C# неявно аллоцирует внутренние коллекции во всяких GroupBy/SortBy, но не помню, чтобы на каком-то проекте это вызывало проблемы. Читаемость и корректность важнее, ради этого собственно итераторы и придуманы.
Котлин форсит использование it, что приводит к нечитаемому коду. Что-нибудь типа seq.map { it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }.
Но вы его почему-то не используете… Кстати, тут косяк котлина, что он не сказал
- что можно написать короче
- что вы используете ключевое слово как имя переменной
Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад и должны быть запрещены в декларации о правах человека. И это считается идиоматично, Карл. В отличие от нормального if. Читать такой код невозможно.
Я может чего-то не знаю, но разве в котлине нельзя создавать промежуточные локальные переменные?
От интеропа с джавой кровь идёт из глаз. А тут всякие JvmStatic и JvmName, и код превращается в цирк с конями.
Не пишите на котлине библиотеки для джавы, вот и все. Пишите на котлине приложение, и те библиотеки, которые будут использоваться только из котлина.
То же относится к вопросу сеттеров (и вообще всего интеропа)
Экстеншн-методы загрязняют публичный интерфейс такими вещами, о которых автор и подумать боялся.
Я правильно понимаю, что если автор библиотеки не предусмотрел метод swap, то нам убиться теперь, потому что нам не разрешено написать свою реализацию? А если нам можно написать свою реализацию, нам прям обязательно её вызывать через ListHelper.Swap
?
Библиотека местами не продумана. Например, reduce.
Эксепшн вместо Maybe на пустой коллекции это, как мне кажется, косяк. Но в жава-мире вроде вообще принято использовать эксепшны там, где можно было бы обойтись Maybe, так что вроде все справедливо.
Давайте ещё навалим про библиотеку. Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? Это ж прямое поощрение плохого кода.
Вот на это хотел возразить особо.
Вот код, который я написал буквально вчера. Это обработчик телеграм-бота, который пытается понять, была ли в сообщении какая-нибудь картинка, и если есть, пытается получить id картинки в наилучшем разрешении:
let processing_info = match (&update.message.from, &update.message.document, &update.message.photo) {
(Some(ref from), Some(ref document), _) => Some((from, &document.file_id)),
(Some(ref from), _, Some(ref photo)) => photo
.iter()
.max_by_key(|x| x.file_size.unwrap_or(0))
.map(|x| (from, &x.file_id)),
_ => None,
};
let (user, file_id) = match processing_info {
Some(x) => x,
None => {
info!("No values!");
return;
}
}
Вы правда считаете, что мне стоило вводить на верхнем уровне тип UserWithFileId, просто чтобы вернуть пару значений?
А знаете, что бы вы сделали в java мире? Вы бы объявили пару отдельных переменных, присвоив им null. Потом написали бы кучу ифов, которые при верном значении перезаписывали бы соответствующий null на значение. После всего этого вы бы проверили что из этого null, ну и дальше какие-то действия… В итоге превратилось бы это в простыню раза в 2 длиннее, и ненадежнее из-за перезаписи.
Очень странный момент — возможность не указывать возвращаемый тип метода (особенно публичного).
С этим согласен
Заключение: большинство из этих "багов" — фичи, без которых рождаются AbstractAnnotationConfigDispatcherServletInitializer
Ну нет, исключение в reduce — не ошибка. Этот метод просто не предназначается для пустых коллекций, и все. Точка.
Вот, допустим, мне нужно найти произведение шести чисел, и я решил использовать reduce. Мне в ответ вернуло Optional — что мне с ним теперь делать?
Можно, конечно же, дописать какой-нибудь .orElse(1)
— но подобный код подразумевает, что коллекция из шести чисел может оказаться пустой! А если бы она могла оказаться пустой, я бы использовал fold
вместо reduce
. Поэтому я не будут вызывать .orElse(1)
, я вызову .get()
— и получу для пустой коллекции то самое исключение, от которого меня хотели уберечь.
Вот, допустим, мне нужно найти произведение шести чисел, и я решил использовать reduce. Мне в ответ вернуло Optional — что мне с ним теперь делать?
Что угодно. Можно вернуть orElse и получить значение в любом случае. Да, я возможно ожидаю, что элементов может не быть, и хочу предусмотреть это в логике. Писать же логику на try catch плохая идея. В итоге я в любом случае не могу нормально воспользоваться стандартным методом без логики к catch.
А если бы она могла оказаться пустой, я бы использовал fold вместо reduce.
Ну так, reduce([])
в таком случае вернет None, reduce([0])
— 0. А fold(0, [])
вернет то же, что и fold(0, [0])
— ноль. А мне может быть важно уметь различать эти случаи.
Функция reduce нужна для тех случаев, когда коллекция не может быть пустой. Никакой логики на try/catch писать не требуется. Если нужна какая-то "логика" — нужно использовать fold.
А мне может быть важно уметь различать эти случаи.
Тогда пишите fold(None, ...)
и настанет вам щастье!
Тогда пишите fold(None, ...) и настанет вам щастье!
Тогда в редьюсере аккумулятором будет Maybe, что делает его использование не таким удобным. Сравните fold(|acc, x| acc + x)
и fold(None, |acc, x| acc.map(|value| value + x))
.
Если такое нужно часто — можно и свой хелпер завести.
Наверное, это раст мне мозг перестроил. Я теперь на эксепшны смотрю с ужасом)
Вот только что-то я не могу в документации на std::iter::Iterator
найти аналог fold без указания начального значения аккумулятора...
Что-нибудь типаseq.map { it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }
.
Кстати, тут косяк котлина, что он не сказал
- что можно написать короче
- что вы используете ключевое слово как имя переменной
Это не косяк Kotlin-а — это, в лучшем случае, отсутствие варнинга со стороны IDE.
it
это не ключевое слово
, а дефолтное имя для переменной, попадающей в ламбду, если это имя явно не переопределить самому
. И никто не запрещает использовать it
где-либо ещё (что как раз является одним из ограничений ключевых слов) и, уж тем более, никто не запрещает использовать его в качестве имени переменной указанной руками.
Но инспекцию на такое поведение со стороны IDE было бы не плохо иметь.
Кстати, инспекция скоро будет: https://youtrack.jetbrains.com/issue/KT-28631
Приятно видеть, что кто-то ещё помнит творчество группы «Беломорс».
в C++ можно не писать return в методе, который согласно сигнатуре должен что-то возвращать
Меж тем у компилятора (gcc по крайней мере) есть ключик -Werror=return-type, превращающий соответствующий warning в полноценный error. Очень рекомендую.
Вот тут хорошо сказали про ключи компилятора относительно варнингов
Поиск очередной серебрянной
И это проблема, т.к. ситуация приносит выгоду только желающим «погреть лапу» на семинарах/пиаре/продажеКнижОнок.
Появление новых фреймворков и языков практически не имеет никакого отношения к решению технических проблем, которые стоят перед разработчиками.
О том, что это всё — ни разу НЕ НОРМАЛЬНО — надо периодически напоминать.
Потому, что, согласитесь, задача языков программирования и it-технологий — это всё таки не радость неофита от прикосновения к сакальному и его вкусовые очущения от синтакс-сахара, а решение технических задач, однозначность кода, обеспечение прогнозируемости поведения и прозрачности структуры системы.
И это все станет популярным через 2 года. На пару месяцев. И десятке-другом продуктовых серверов. Хорошо, если только этим все и ограничится, и не нам с вами это поделие разгребать и поддерживать, после того как их авторы срулят, заметая следы, в туман рубить бабла на впаривании очередное откровения. Или просто отмахиваясь куда-подальше от монстра который они создали.
С другой стороны, Kotlin позволяет писать гораздо быстрее. А еще у него из коробки идет nullSafety, что автоматически принуждает разработчика думать о том, что и куда он сует. И много других вещей, позволяющих писать более гибкие и продуманные сценарии поведения приложений.
При этом никто не кричит, что то же самое можно сделать на Java — просто в Java nullPointer пропустить можно по невнимательности, а в Kotlin — намеренно приколотив гвоздями неопциональное значение. Заметьте, никто не навязывает парадигму — хотите, пишите по старому. Более того, хотите, юзайте Java — она тоже совместима с Kotlin. Нет — все равно будем топить любое новшество просто потому что «у меня есть инструмент, я им решаю свои задачи, изучать новые инструменты мне не надо».
Задачи языков программирования — очень разные, и уж точно не регламентируются третьими лицами в кулуарах обсуждений на тематических форумах.
гм… при чем тут «регламентирование»?
мы обсуждаем вектор классификации языков программирования, которые претендуют на «хоть сколько то» практическое применение. надо же как то оценивать и сранивать направленность языков и возможность их применния?)
и где и как вырабатывать такую классификацию, если не в обсуждениях между специалистаи в паблик форумах (хабр же не кулуарное обсуждение, согласитесь?)
С другой стороны, Kotlin позволяет писать гораздо быстрее. А еще у него из коробки...стоп стоп стоп ))) пАгадите… при чем тут котлин? я нигде не наезжал на котлин, и не упоминал его, ведь правда? заметьте.
я поднимал проблему обоснованности появления новых языков, и то, что причины появления подавляющего числа языков не имеют прямых технических оснований — фичи большей части (имхо, если не подвляющего числа) «новоделов» не решают каких либо технических проблем, а скорее отражают взгляд авторов на мир и их собственную вкусовщину. с попеременным успехом это находит отклик в сердцах неофитов или склонных к чувственному восприятию мира людей. имхо.
я рад за тех кому это нравится. серьезно. но мне, признаться, несколько «все равно», из коробки там пара фич или нет… ну честно, извините. я настолько наслышан от предыдущих проповедников о прелестях их новых языков, и настолько видел, «куда эти языки ушли» вопреки рекламным проповедям, что простите, я вам, конечно, верю, но в вашу веру обращаться не буду, как минимум пока не будет на это технической необходимости. а технических оснований я не вижу — НАМ переводить джава проекты на котлин дорого и рискованно. не те ставки. не курсачи пишем.
я рад что вам это зашло, и что вам нравится (судя по вашему посту), но я вот, стараюсь выбирать средства разработки не по удобству или вкусовщине, а по степени пригодности для решения возникающих задач.
для андроида, возможно когда нибудь и переключусь с джавы, но пока вот более заходит Qt, просто потому, что более кросплатформенно.
и если говорить про котлин и моё «имхо»(если вы пытаетесь меня в чем-то убедить) то вот вам мое имхо: извините, не обижайтесь, но я не вижу чем котлин принципиально отличается от другиз jvm-языков.
ну вот сами посмотрите:
* был груви? был. где он? мода прошла, груви уходит на обочину истории оставясь узким нишевым языком для гиков.
* была скала? была. где она? мода прошла, скала уходит на обочину историию оставаясь нишевым языком для гиков.
* был клюжЮр? был… где он?..
и так далее — там еще в ряду, если верить википедии jPython, jRuby и далее…
и котлин ничем принципиально о них не отличается. извините, имхо.
если в чем и есть отличие ситуации с котлином, так это только то, что гуглю нужен «запасной аэродром» из-за постоянных нападок оракла. ни больше ни меньше, и это ни разу не связано с прелестями языка. это ситуация, «так карты сложились». они плотно дружат c jetBrains и «почему бы нет», я полностью понимаю выбор гугля…
но прошу вас, не надо рассказывать про прелести языка… если завтра гугль и оракл достигнут мирного соглашения — котлин с 90% вероятностью пойдет на обочину истории, как и его предшественники. в нишевость, в гиковость. и там ему пусть будут рады, я рад за тех кому это зашло.
все равно будем топить любое новшество просто потому что «у меня есть инструмент, я им решаю свои задачи, изучать новые инструменты мне не надо».«не надо навешивать на меня ваших крокодильчиков» ) я такое не говорил.
я за разумные новшества, обоснованные, не приводяшие к технической неоднозначности или непонятности для разработчиков. я ЗА новшества. но за взвешенное к ним отношение.
давайте поясню на примере джавы: диамонд синтаксис — классная вещь, потому что понятно
«что пропускается» и где смотреть что пропущено, код по прежнему однощначен. это было хорошее нововведение.
А вот лямбда функции — «полнейшая фигня»)) очень сомнительно. потому что без IDE, читая текст примера кода на сайте — вы можете только догадываться о типах передаваемых параметров. это было плохое нововведение. из исходного кода делись определения тип аргументов и какие они долджны быть — из текста исходного кода не понятно.
Или вот взять проблемы перехода с 6й джавы на 7ю. Болезненно для многих, особенно банковских и др интерпрайз систем, да. Но извините, доступ к приватным методам класса через рефлекшен — это не просто не правильно, это не описано в стандарте, и по сути, сродни багу платформы. Это и убрали в 7й джаве, но сохранили обратную совместимость в рамках описанного в стандарте поведения.
понятно мое отношение к новшествам? )
(а вот теперь я буду наезжать на котлин )
Кстати, где у
извините — я это у всех проповедников новых языков программирования спрашиваю, вы не первый.
не обижайтесь, но давайте с котлином подождем года 2 или 3. вдруг там оракл и гуглем помирятся?)
или вы согласитесь со мной, в моем понимании, "почему джава успешно пережила не один сезон «убивцев джавы», и почему котлин, сам по себе, ни разу не исключение". или котлин займет нишевое положение эксклюзивно-платформенного языка, так же как свифт с иосом) — посмотрим, время покажет, не торопитесь, в общем.
вам заходит, — классно. пилите свои проекты, глядишь толк из этого выйдет. но не надо агитировать моих стажеров или меня. и хайп вокруг языка — ну прошу, не надо вот этого.., столько раз уже было, ну надоело, честное слово… глупое это дело… да и показывает вас с не лучшей стороны.
мы сами к вам придем коли потребуется.
спасибо что дочитали. удачи вам.
Если ты в треде о котлин пишешь длинную тираду «я не о котлине вообще писал» — дальше можно не распинаться, слушать ужа на сковороде никто не будет.
Именно это я в первом посте и написал. разве нет?
а вы начали воспринимать это как оскорбление колтина, попытку регламентирвоать назначение языков, порывались засыпать меня перечнем «фич из коробки»… ну и зачем так? ))
ладно. проехали. Нравится обсуждать конкретику, без попыток сопоставить с ситуацией вообще за пределами отдельно взятой узкой темы — ок, я не против, но я не с вами в данном направлении. Мой комментарий совсем не про конкретные микрофичи языка. не поняли друг друга, бывает.
Есть некоторые приколы. Например==
используетequals
, который работает совсем не так, как ожидает пользователь, например для BigDecimal. ПоэтомуBigDecimal("1.00") != BigDecimal("1.0")
. В принципе не существенная проблема, но могли бы и обёртки для таких случаев добавить.
Тут всё как раз просто и логично и проистекает из начального тезиса: ==
использует equals
Всё остальное зависит от реализации equals
в конкретном типе.
В данном случае эти действительно два разных числа и, кстати, они не равны по equals
с BigDecimal("1")
, и пользователь, раз уж использует специфические классы, должен об этом знать.
Это не проблемы языка.
В общем случае Comparable
это про порядок, а не про идентичность и никто не гарантирует что если x.compareTo(y) == 0
, то и x.equals(y) == true
.
В JavaDoc Comparable.java описание контракта декларирует It is strongly recommended (though not required) that natural orderings be consistent with equals.
Так что использовать результат compareTo
для идентичности так себе идея и правильно что её не используют.
А обёртки могут и появиться в следующих версиях или уже присутствовать в библиотеках. Это мало связано с языком как таковым.
А как быть с теми же Double? которые и в Comparable могут "врать" и приходится указывать допустимую погрешность вида "d1-d0<0.01", при которой мы считаем, что эти два объекта ещё "равны".
Так что да, "==" есть equals и как уж там реализует его конкретный тип — проблема этого типа, а не языка.
Точно так же, когда в доднете появились лямбды и var, народ выл, что код станет непонятным. Прошло сколько-то лет, и уже никто не мыслит себе разработку без них, хотя вот как раз таки с дотнетовскими closures выстрелить себе в ногу проще простого
Лично для меня переход с c# на жаву был крайне болезненный, после .net 4.7 смотреть на откровенно убогие дженерики и ужасные streams было мучительно больно. Плюс совершенно безумная verbosity жавы — каждый раз, когда я писал код, возникало ощущение, что обьясняю что делать умственно отсталому ребенку.
А вот котлин стал натурально лучом света, причем некоторые вещи сделаны даже лучше чем в дотнете.
Иными словами, котлин — удобный и да, сложный тул для профессионалов. Но ставить ему это в в недостатки — это примерно как винить автомобиль за повышенную опасность в сравнении с телегой на конной тяге.
А вот котлин стал натурально лучом света, причем некоторые вещи сделаны даже лучше чем в дотнете.не холивара ради, а интереса для. А какие?
Вот мой список, в порядке от более желаемого к менее:
- Инлайн-лямбды;
- Null Safety;
- дата-классы, которые в C# уже четвертую версию языка обещают да ввести не могут;
- Delegated Properties;
- Неизменяемые переменные (val);
- Корутины без await.
Null Safety;Вот из-за этого я очень жду C# 8.0
Корутины без await.Вот это выглядит как костыль, как по мне. Если это так классно, то зачем IDE отдельно помечает такие вызовы?
Вот из-за этого я очень жду C# 8.0
в котлине null safety реализована через, вобщем-то, отсутствие классического null. В К. это обьект типа Nothing?, т.е., можно смело писать null.toString(), например
Интересно, как дотнет с этим справится
null.toString()в obj-C в купе с его динамической типизацией это вызывало проблемы в рантайме.
C# пошёл по пути swift, в проекте можно поставить флаг, и все переменные станут не нулабельные, пока явно не скажешь это. string? s = null;
Они решили проблему в пользу(частично) object. T.e., null — это обьект. В то же время, это часть системы типов, часть языка.
Флаг же в проекте(как в свифте) вызывает у меня ассоциации с древним рантайм кастом в с++(dynamic_cast), когда надо было подключать доп. библиотеку, чтоб его делать:)
Ну и вот в этой презентации про null safety в Котлин как раз описывается то, что будет в С# 8 и как это есть сейчас в Swift…
www.hwsw.hu/kepek/hirek/2017/08/hwswfree_20170829/03_Farago_Janos-nullsafety_in_kotlin.pdf
Флаг меня смущает тем, что это какая-то внешняя проверка, на которую, вобщем-то, полагаться нельзя. Один дев будет кодить с флагом, второй без, и либо у одного проект не будет компилироваться, либо вылезут артефакты на рантайме.
Один дев будет кодить с флагом, второй без, и либо у одного проект не будет компилироваться, либо вылезут артефакты на рантайме.Эм… проект-то тоже в системе контроля версий. Если один включит, а другой выключит, то не будет компилироваться у кого-то. Ну, если один будет на одной версии языка писать, а другой на другой — тоже самое получится.
Флаг нужен для того, чтоб braking changes не ломали проект при переходе на новую версию языка (как это у свифт постоянно выходит). Никто не хочет рефакторить старый код, который работает.
Никто не хочет рефакторить старый код, который работает.
какая тогда польза от этого флага?
Включили галку, внутри вашего проекта все фичи есть, если что-то приходит снаружи, то оно nullable. всё то же самое, что в K, просто существующий код не сломается, если вы этого не захотите.
Представьте, если бы в котлине вдруг решили, что await перед корутиной всё же надо писать, и включили это новое поведение по-умолчанию для вашего кода. хорошо бы было?
Включили галку, внутри вашего проекта все фичи есть, если что-то приходит снаружи
под «снаружи» я имею в виду жава классы в том же проекте/пэкедже. Проект один и тот же, иначе говоря
А объекты из подключённых библиотек, тоже будут опционалами, если библиотека не поддерживает данную фичу.
Ещё раз, галка нужна для тех, кто хочет использовать новые фичи c#, новый компилятор и рантайм, но не переписывать существующий код на опционалы.
в .NET нельзя просто так взять и писать исходники сразу на двух языках
Я знаю, и именно об этом и говорю. Перейти с упоминавшихся миллионов строк легаси на жаве на котлин иначе было бы просто невозможно, никто б не стал так рисковать. А так — берешь старый прожект и просто пишешь новые фичи на новом языке. Ну или конвертишь старые классы в К. — это несложно
А тут, уж простите. И легаси код на c# и новый на C# как понять где string s, может принимать null, а где нет? Галкой: вот в этом проекте string s — нублабельно, а вот в том изволь писать string? s;
пишешь новый код в новом проекте, или рефакторишь старые классы — это не сложно.
Никто не будет переписывать миллиарды строк кода, особенно когда фикс совсем нетривиальный.
К слову, это еще и не ошибкой, а ворнингом будет.
Это, кстати, обалденная фишка К. — в одном не то что проекте, в одном package могут быть классы как на жаве, так и на котлине
А реактор кстати мне вполне понравился, потому что часто бывает задача выбросить таску, которая уже не нужна. В дотнете (и других языках) приходится вводить костыли типа CancellationToken'ов, а с реактором достаточно выкинуть таску и она больше не будет ничего дергать.
Да их и предлагаю.
Отсутствие await сильно напрягает.
Например, я затащил в свое время разработчиков C# 5.0 на обсуждение
введения async/await в расте, и там был интересный момент озвучен:
We thought about precedence a lot with 'await' and we tried out many forms before setting on the form we wanted. One of the core things we found was that for us, and the customers (internal and external) that wanted to use this feature, it was rarely the case that people really wanted to 'chain' anything past their async call. In other words, people seemed to strongly gravitate toward 'await' being the most important part of any full-expression, and thus having it be near the top. Note: by 'full expression' i mean things like the expression you get at the top of a expression-statement, or hte expression on the right of a top level assign, or the expression you pass as an 'argument' to something.
The tendency for people to want to 'continue on' with the 'await' inside an expr was rare. We do occasionally see things like(await expr).M()
, but those seem less common and less desirable than the amount of people doingawait expr.M()
.
This is also why we didn't go with any 'implicit' form for 'await'. In practice it was something people wanted to think very clearly about, and which they wanted front-and-center in their code so they could pay attention to it. Interestingly enough, even years later, this tendency has remained. i.e. sometimes we regret many years later that something is excessively verbose. Some features are good in that way early on, but once people are comfortable with it, are better suited with something terser. That has not been the case with 'await'. People still seem to really like the heavy-weight nature of that keyword and the precedence we picked.
So far, we've been very happy with the precedence choice for our audience. We might, in the future, make some changes here. But overall there is no strong pressure to do so.
Вернее ядро-то нормально поддерживает и M:N и N:1, но стандартная библиотека форсирует режим, когда N:1 треды эмулируют M:N. Сигнал отдельному потоку не послать, остановить его тоже нельзя и так далее. Всё в угоду тому, чтобы программы на #%#%^^%*& солярисе с M:N тредами тоже работали…
Что лучше в моем хитпараде — это имплементация high-ordered functions
T.e., чтоб обьявить функциональный параметр в сигнатуре другой функции я пишу что-то вроде
fun printFiltered(list: MutableList<String>, filterFunc: (String) -> Boolean)
т.е., не надо использовать Func<> or Action<>, обьявление более натуральное
Что еще лучше — наличие let и прочих. Дотнет сделал гигантский прыжок с ?., но на этом и остановились. Вернее, это все есть в f#, но для него нужен отдельный проект, что не очень удобно
Убрали декларацию типа из foreach, ну и прочее
Это навскидку
Т.е., парни просто пошли чуть дальше
if(int.TryParse("100500", out int i) && i > 200)
{
//...
}
foreach(var (key, val) in dictionary)
{
}
и всякие другие.
а про foreach не знал, спасибо!
про foreach не знал, спасибо!
на самом деле я немного слукавил. Для того, чтоб этот код сработал со словарём, нужно дописать метод-деконструктор. По умолчанию он есть только у кортежей.
public static void Deconstruct<K, V>(this KeyValuePair<K, V> pair, out K key, out V val)
{
key = pair.Key;
val = pair.Value;
}
(в C# 7.2 можно out заменить на in, получатся readonly reference)
Поверьте, если бы писали — таких странных вопросов бы не задавали.
И ни разу не натыкался на проблемы из-за того, что что-то излишне открыто. Даже не представляю, как такая проблема может выглядеть.Очень просто: вы меняете реализацию — и получаете 100500 жалоб на но, что у кого-то отвалился «такой же, но с перламутровыми пуговицами».
Простейший пример — класс как-то обрабатывает массивы и отдельные элементы. Вы перекрыли метод обработки отдельного элемента, а в новой версии обработку массива векторизовали и она функцию обработки отдельного элемента больше не вызывает. Вы будете себя винить или «криворуких разрабов», когда у вас после обновления всё перестанет работать?
Есть же языки, где инкапсуляции в принципе нет — и ничего.Да, ничего. В смысле — ничего хорошего. Можете поинтересоваться у разработчиков фронтэнда о количестве костылей, которые они наизобретали в попытках запретить-таки людям создавать «такие же, но с перламутровыми пуговицами» сущности. Количество угроханного на это времени и сил исчислению не поддаётся.
final был бы более адекватным в виде аннотации. Всё равно в яве куча способов его обойти, только геморройных.Ну если вы его явно обходите, то вы должны понимать, что делаете что-то неправильное и ССЗБ.
И всё еще не понимаю прелести передать юзерам моего кода сообщение «обрати внимание, версия поменялась, поведение тоже» через вставление им палок в колеса.Это не «передача сообщения». Это чёткое отделение вещей, которые они могут использовать, от подробностей реализации, на которые они полагаться не должны.
Посмотрите, скажем, на то, что bionic делает, чтобы через его хендлы никто не залазил во внутренние структуры. А когда-то давно наивные чукотские вьюноши просто отдавали ссылку на внутреннюю структуру… в которую куча разрабочтков немедленно начала лазать своими грязными лапками.
Это как повесить здоровенный замок на хлипкую дверь сарая.Тут согласен. Но поскольку разработчики Kotlin сараем не владеют, то это лучше, чем ничего. Если не поможет — начнут укреплять стены изнутри (опять-таки смотри bionic)…
А @Deprecated, например, чем менее чёткое отделение?Причём тут вообще @Deprecated? Как с его помощью запретить перекрывать метод? Не вызывать (это-то как раз нормально), а перекрывать?
Как вам уже написали: если вы не полагаетесь на то, когда и как сам объект вызывает свои методы — то вы можете использовать class delegation. Если же вы перекрываете методы так, что class delegation не работает — ты вы, автоматически, лезете во внутреннюю кухню класса. Что Kotlin, по умолчанию — вам запрещает. И правильно делает.
Да вообще всегда это должен понимать, когда взял другую версию, и не погонял тесты хотя бы.Ах… не хочу ругаться матрм. То есть для вас нормально, что новая версия библиотеки может что-то сломать?
Вот и выросло поколение, которые попросту забыло для чего существуют разделяемые библиотеки, раздельная компиляции и прочее.
Вообще-то ситуация, когда я беру новую версию библиотеки (о которой автор программы не имеет даже представления), заменяю её на тестовой системе — и через пару дней тестирования запускаю в production — нормальна и правильна. Сбои должны быть исключительно редки. Ситуация, когда вам нужно гонять тесты и что-то править при обновлении библиотеки — это нештатная ситуация! Да, так иногда случается, когда люди не читают документацию (хотя бы знаменитая история с memcpy), но это должно случаться очень-очень редко, буквально раз в год! Это не должно быть нормой!
В прод без тестирования вообще ничего попадать не должно. Ну если у вас хоть сколько-нибудь серьёзное промышленное приложение.Вы вообще название статьи читали? Я вас уверяю: из миллиона с лишним приложений, которые вы найдёте в Google Play «серьёзных промышленных приложений», которые следуют этому совету — меньше 1%.
А даже для них проблемы при обновлении версий — не должны быть нормой.
Типа такого: @Deprecated("запрещаю перекрывать этот метод")
И будет у вас это предупреждение выдаваться в любой программе, которая будет вызывать этот метод.Что ничуть не хуже, на мой взгляд, чем: /*не декомпилируй и не инструментируй этот класс позязя*/ final class Foo {}Не понимаю причём тут декомпиляция и инструментация вообще.
final не пресекает на корню мои поползновения сделать так, как мне надо, ну потому что ява, байткод, матаданные, инструментация.А ещё JIT. Внезапно, да. Который может даже отлично работать у вас в тестах — но отстрелить вам ногу в проде.
Это низводит его до просто маркера, пожелания, предупреждения, так же как и аннотации типа @Deprecated.Нет. Если вы проигнорируете @Deprecated — то вы не сломаете программу. Если вы, с помощью рефлекшна, метаданных и прочего обойдёте
final
— то вы закладываете под ваш бомбу замедленного действия.Если вы любите «код с запашком», готовый сломаться при изменении параметров запуска JVM — то для ваших услуг есть много других языков. JavaScript, например, или там PHP.
А так, бездумно натыканные final'ы изредка вынуждают меня извращаться с форканием кода либ и последующими мержами; декомпиляцией; инструментацией и тд. Чего ради?Ради того, чтобы внутренности библиотек оставались внутренностями. И да — это, в свою очередь, требует чтобы API у этих библиотек был достаточно продуман для того, чтобы не хотелось его обойти.
Кто-то якобы будет недоволен тем, что я ломаю совместимость. Ну так обычно я ее не просто так ломаю.Нет, практика показывает что ломастеры ломают совместимость именно что «просто так». То есть не раз в 5-10 лет, как у нормальных разработчиков, а пару раз в год.
Не нравится — сиди на старой версии.А вы её будете поддерживать 2-3 года, после выхода новой версии, или сразу бросите?
Как это обеспечить?Вы не поверите — но тщательно обдумывая API изначально и аккуратно обдумывая все изменения.
Вы ж не можете написать сейчас код и утверждать, что он будет работать в винде с 2000 по 3000.Не могу. Но это зависит не от меня, а от разработчиков Windows. Которые, в общем, о совместимости думают. Какой-нибудь Microsoft Bob, выпущенный больше 20 лет назад прекрасно работает на последней версии Windows 10. И, думаю, ещё долго будет работать — по крайней мере пока 32-битные версии будут поддерживаться.
Что вам позволит утверждать без вранья, что код, работавший с версией либы 1.0.0.1, будет работать в версии 1.0.0.2, если вы не тестили его с этой версией?А причём тут я? Если я соблюдал спецификации и делал всё, как описано в доке, то это у разработчиков либы 1.0.0.2 нужно спрашивать — как они собираются обеспечивать совместимость со всем кодом, который работал с либой 1.0.0.1
И если они этого обеспечить не могут — то пусть выпускают версию 2.0.0.0, как и положено, а не делают вид, что это 1.0.0.2
Внезапно что? Каким боком к тому, что я описываю, JIT?JIT имеет право полагаться на то, что финальный метод будет, внезапно, финальным. И никогда не будет перекрыт. И может, соотвественно, вставить его в то место, где он вызывается. И, более того, то, что он сегодня так не сделал — не означает, что завтра он так не сделает если какие-нибудь счётчики другое число насчитают.
То есть если вы залазите в код и заменяете в финальном классе метод, который, вообще-то, менять не положено — то вы просто напрашиваетесь на то, чтобы получить проблемы.
Если я выпилил из метаданных файналы, откуда jit узнает, что они там были когда-то?Например если кто-то, не зная о вашей фигурной стрельбе через гениталии, подложит JAR с оригинальным классом, то JIT, внезапно, «узнает» о файналах. А вы о них можете узнать после этого, только когда вас ночью поднимут что-нибудь упавшее срочно чинить.
Да, но нет. Если код меняли два человека, один из которых действовал по спецификации, а другой — вопреки ей, то сломал код тот, кто её нарушил. Иногда, правда, чинить приходится не ему: если новая версии библиотек сломает какой-нибудь Facebook или WhatsApp — то для их поддержки могут какой-нибудь костыль забить на некоторое время. А если приложение не столько популярно — то и заморачиваться не будут.Я-то не сломаю. Вы сломаете :)Или другой любитель подкладывать непротестированные либы.
JetBrains вон с идеей jre таскают, явно не от хорошей жизни.Почему «не от хорошей»? Как раз «от хорошей». Idea отлично работает и с другими JRE, однако им спокойнее, когда они используют ту JRE, что «принесли с собой». Но вот уже libc или там libstdc++ — даже они с собой не таскают.
Не уверен, что они там подхачили, swing вроде.Ничего не хачили, иначе бы не писали: It's recommended to use the bundled JRE (if available). In case you have any issues with the bundled version, you can switch to the latest Oracle JDK or OpenJDK 1.8 build available on your system.
Целый JetBrains не смог или не захотел спрашивать, похачил сам, а я пойду спрашивать. Хороший план.Как мы видим JetBrains ничего не хачил, так что я вообще не понимаю о чём вы. Если вы хотите что-то поправить в чужой библиотеке, то ничто не мешает вам её взять и сделать fork (если у вас есть исходники). Но в этом случае это будет уже ваша библиотека — и вы (и никто другой) за неё будете отвечать.
А вот если вы лезете в «кишки» и делаете то, что автор библиотеки не предписывал вам делать — то вы напрашиваетесь на неприятности.
Статья выглядит как крик типичного школьника/студента на тему "это не тот язык который я знаю и люблю, поэтому он плохой, и это никак не связано с тем, что я не смог его выучить".
Практической пользы от неё нет совсем.
Очень жаль, что кто-то может опубликовать такое от имени JUG.ru. Это мелко, низко, написано ради хайпа и не стоит той кармы, которую получил автор.
Идёт мобильный разработчик по лесу, видит — Котлин горит. Сел в Котлин и сгорел