Comments 154
вещи, которые мне лично действительно усложняют жизнь.
Правильно ли я понял что наличие на ваш взгляд бесполезного for-цикла усложняет вам жизнь? Или все это сарказм с начала и до конца?
Усложняет, разумеется, не наличие бесполезного цикла, а отсутствие полезного.
Усложняет, разумеется, не наличие бесполезного цикла, а отсутствие полезного.
Фор — это очень удобная и краткая конструкция с двумя выражениями и одним условием.
Она короткая и очень наглядная.
Для меня лично, разворачивание его в while с отдельным описанием переменных, значительно снижает читаемость кода.
Имхо, за использование цикла for
для чего-либо кроме итерации по элементам коллекции надо бить по рукам во всех языках. По семантике и смыслу for
это именно цикл итерирования. Всё остальное и должно делаться через while
.
Вот только for (int i=x; i>0; i=(i-1)&i)
— это тоже своего рода итерация по элементам коллекции...
Что значит "своего рода"? Если это индексы, то можно использовать обычный итеративный for
. Честно говоря, проблема мне вообще кажется надуманой, хотя я и не пишу на kotlin. Если это не итерация, то нефиг использовать для этого for
.
// котлин-вей (как я себе его представляю)
generateSequence(x) { (it - 1) and it }.takeWhile { it > 0 }.forEach {
// todo
}
// для тех кто экономит на итераторах
var i = x ; while (i > 0) {
i = (i - 1) and i
// todo
}
// если очень хочется в три блока как в старые добрые
inline fun <T> oldScoolFor(initValue: T,
condition: (T) -> Boolean,
next: (T) -> T,
body: (T) -> Unit) {
var i = initValue
while (condition(i)) {
i = next(i)
body(i)
}
}
oldScoolFor(x, { x > 0 }, { (it - 1) and it }) {
// todo
}
Ceylon и тот поддерживает "нормальные" generics, а Kotlin увы...
Действительно, в нем декларируется "Ceylon's type system is fully reified at runtime" и в качестве примера приводят именно "if (is Map<String,Object> map)".
Интересно, какой ценой они этого добились?
Я цейлон не изучал, но на их сайте указан таргет JVM и в описательной части приведены примеры для использвания с Java EE. Прямые их примеры использования жавы тут:
https://github.com/ceylon/ceylon-examples-jdk
Как это может работать без совместимости с Java?
Согласен, break реализовать в коде сложно.
Даже try-catch блок, exception в котором никогда не бросается совсем не бесплатен, он мешает делать оптимизации во время выполнения, соответственно, он может стоит от ничего до сильного проседания производительности. Поэтому нет, исключения сильно сажают производительность не только из-за сбора трейса, но и из-за того что вместо заставляете JVM отключать многие оптимизации кода.
В целом, использовать исключения для выхода из цикла это почти всегда плохо.
Да что вы там за низкоуровневый мега-производитеььный код пишете на Java? Задолбали уже с этой темой медленного Exception. Масса операций (запрос к БД, чтение с диска, обращение к сети) на несколько порядков медленнее генерации и ловли исключения, тем не менее все эти операции выполняются по тысяче раз в секунду. Никто не будет в здравом рассудке генерить сотни тысяч исключений в секунду, а жалкая пара сотен вообще никак не скажется на производительности.
Да что вы там за низкоуровневый мега-производитеььный код пишете на Java
Приходилось писать для BigData (Hadoop, Apache Spark), HighLoad (веб сервис отвечающий на десятки и сотни тысяч запросов в секунду). Там экономия даже 10% позволяет покупать не 100 серверов, а 90. Сразу отвечаю, предлагать использовать C/С++ для этого не стоит, по многим причинам правильно настроенная Java предпочтительнее С++ и прочих относительно низкоуровневых языков (Java на самом деле достаточно производительная, если у программистов прямые руки, и к тому же очень надежна, если руки тоже не из одного места).
Никто не будет в здравом рассудке генерить сотни тысяч исключений в секунду, а жалкая пара сотен вообще никак не скажется на производительности.
Это только кажется, я видел в реальных приложениях алгоритмы вида "сделаем sql select из таблица с миллионными записей, потом в Java отфильтруем по имени и возьмем десяток записей". Причем писали их опытные программисты, проблема в том что сначала была таблица на сотню записей и так было вроде бы проще, потом записей стали миллионы, а память приложение стало есть со страшной силой и приложение отвечало медленно и печально.
Автор выше предлагает выходить из for-а через кидание exception'a, предположим автор реализовал это для for-а какой-нибудь функции, а потом другой программист использовал её в цикле сотню тысяч раз (не вникая в детали реализации функции) и вот у нас уже неожиданно в система появилось сотня тысяч исключений в секунду. Причем об этом мы узнаем только когда у нас система начнет "провисать".
Поэтому там где возможно стоит оставить исключений только для обработки реальных ошибок, не стоит забивать гвозди микроскопом. Поверьте, в Java много способов получить проблемы с производительностью даже в довольно простом приложение, а ловить потом утечки памяти и провисания производительности лишняя трата времени и сил.
https://kotlinlang.org/docs/reference/returns.html#return-at-labels
(0..10).forEach one@ {
(0..10).forEach two@ {
if (0 == 0) {
return@one
}
}
}
import org.jetbrains.annotations.Nullable;
public class jHelper {
public static @Nullable jHelper jF() { return null; }
public void M() {}
}
fun main(args: Array<String>) {
val a = jHelper.jF()
a.M() // Compile error: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type jHelper?
}
ИМХО тут автор как раз прав — сакральный смысл таких "манипуляций" стремится к нулю. В тот же Spring таких аннотаций не понаставишь. Логичнее как раз считать всё "чужеродное" максимально "небезопасным", если не указано иное.
Вообще странно. Год или полтора назад я читал, что в Kotlin специально решили считать все ссылки родом из Java — Nullable по умолчанию. И даже гоняли над JRE специальный верификатор, который составлял "карту аннотаций". Решили не заморачиваться, что ли?
Наоборот.
Специально для внешнего кода убрали проверки.
Находил описание, когда наступил на эти грабли в каком-то парсере и начал искать почему в коде нет проверок.
Полное содержимое кейса я не помню, но смысл был в том, что при наличии проверок ломался какой-то код.
Сейчас нулабле-тип объявленный в Kotlin имеет тип "?", а из Java "!".
//KOTLIN:
fun sNULL() : String? = ""
//JAVA:
public static String F1() { return null; }
val k = sNULL() // тип переменной String?
val j = jTest.F1() // тип переменной String!
Для первого проверки производятся, и выдаются все предупреждения, а для второго нет.
Тогда смысл "не-нуллабельности" вообще нулевой получается — с учётом что 95% нынешнего кода не на Kotlin. Я при работе с внешним кодом не получаю от компилятора абсолютно никакой помощи, и точно так же могу схлопотать NPE почти в любой момент. Смысл был бы как раз заставить проверять инварианты на внешнем коде или прогонять какой-то статический чекер. А так синтаксис покрасивше, не более.
Смысла нет
Об том и спич :)
У поддержки null типов в Котлин в том виде в котором она там есть два очень весомых "смысла":
1) Устраняется "распространение" null-ов. Основная боль в Java в том что null может быть незаметно передан из функцию в функцию, из поля в поле, и NPE вылетит совсем не там где null возник. Для этого рекомендуется всюду ставить null-проверки, но не все это делают. Kotlin имеет null-assert-ы во всех публичных методах, и распространение null-ов предотвращается на самом раннем этапе.
2) Null-типы позволяют декларировать nullability явно и удобно (в отличие от аннотаций). Это сильно упрощает использование кода на Kotlin.
Чегож вы все читаете не то что написано, а то, на что знаете ответ?...
Где написано что нулевые типы в котлин — это плохо?
Где написано что я не понимаю откуда берется null?
Претензии к текущей реализации 2:
Ошибка компиляции, исключение из случаев safe-check, фактическая польза от которой, на практике, составляет мизерную часть тех мест в которых ее выбразывает компилятор, а во всех остальных она вызывает только раздражение.
- То, что все эти проверки НЕ РАБОТАЮТ с данными, пришедшими из жавы!
А далее можете цитировать себя сами со всем обилием возможных проблем.
Вот пример именно Ваших слов с последствиями.
fun Dispatch( obj:GuiObject) {
... // тут будет проверка параметра на null
}
Dispatch( JavaCodeClass.getCurObject() ) // Тут проверки НЕ будет
В текущей архитектуре код хлопнется на проверке парамеров внутри "Dispatch" в РАНТАЙМЕ, т.е. все танцы с "?" и "!!", которые я вынужден использовать в исходниках, оказываются совершенно бессмысленными.
Решили не заморачиваться, что ли?Поняли, что задача в общем виде нерешаемая из-за generics. Андрей Бреслав — Kotlin: самое сложное — совместимость
Я так понимаю, ты намекаешь на то, что никто не мешает исправить существующие классы Java и добавить в них недостающие аннотации?
Ясно.
Ты серьезно считаешь, что работу с миллионом существующих библиотек Java я долен начинать с их реверс-инжиниринга в попытках понять как они работают и чем чревато их использование с последующей их модификацией наружними аннотациями?
Ты сам-то так пробовл делать?
Сколько времени остается на ту работу за которую платят зарплату?
В IDEA внешнюю аннотацию можно добавить в пару кликов при вызове библиотечного метода. Не трогая те методы, которые не нужны. Если же вы не разобрались, может ли вернуть null используемый метод, то вместо работы, за которую платят зарплату, вам вскоре придётся дебажить NPE в продакшне.
Эти "пара кликов" займут 0.0001% времени в задаче "понять куда, как и когда их надо воткнуть".
Я подозреваю, что задачами по адаптации чужого кода, написано непойми как хз сколько лет назад, без коментариев и, возможно, даже без исходников Вы если и занимались, то не часто.
Просто поверьте, что тупое втыкание аннотаций к чужому коду может привести к проблемам по крайней мере не менее сложным чем "дебажить нпе в продакшине"
Мне непонятно главным образом 2 вещи:
1) Зачем делать что-то хуже, чем уже было в Java (for, ?: оператор, приведение типов и т.д.)
2) Почему создатели языка считают, что только компилятор должен решать как обезопасить код, а не программист?
Серьезно, эта ошибка просто мозг выносит.
Smart cast to 'Int' is impossible, because 'value' is a mutable property that could have been changed by this time.
Я знаю про более идиоматический вариант с ?.let{ }, но не считаю, что в таких ситуациях этот подход повышает читаемость, за что так борется Kotlin.
2) Почему создатели языка считают, что только компилятор должен решать как обезопасить код, а не программист?
Потому что в современном мире компилятор должен пытаться максимально решить проблемы на стадии компиляции, а не оставлять их на рантайм. Пришли времена, когда компилятор может быть действительно умным, а не просто транслятором одного кода в другой. Поэтому сейчас так популярны статические проверки и генерация кода за счет аннотаций специальных. Где-то это может небольшое неудобство, но оно приносит очень много пользы в перспективе. И null-safety как раз пример этого. Rust так вообще кардинально пошел по этому пути — там вся модель работы с памятью на аннотациях и статических проверках
Smart cast to 'Int' is impossible, because 'value' is a mutable property that could have been changed by this time.
Я считаю, это хорошая штука. Потому что язык должен быть предсказуемым. Поведение должно быть чётко специфицировано. Вот напишите соответствующую главу в спецификации языка: когда повторное чтение поля язык может считать безопасным, а когда нет. Написать в спецификации явно, что безопасен только такой фрагмент кода if(VAR == null) {return/throw ...} return VAR.xyz
? А любой другой фрагмент опасен? Завтра кто-то пожалуется, что if(VAR == null) {return/throw ...} return 1+VAR.xyz
не работает. Можно слева добавлять константу? А переменную? А другое поле? А волатильное поле? Напишите спецификацию и вы поймёте, что лучше запретить вообще все повторные чтения, чем описывать, когда они безопасны.
Можете привести определение безопасности, которому соответствует эта ошибка и пример кода, в котором она является небезопасной?
Приведенный у вас код не имеет отношения к обсуждаемому.
Второй вопрос: какой процент "ложных срабатываний" ошибки приемлем для комфортной работы?
Т.е. в каком проценте случаев возникновения этой ошибки код, на котором она возникла, не будет иметь никакого отношения от той "опасности" от которой она защищает.
Истерично-бессмысленная война с null-абле
Проблемы на границах с другим кодом это не причина отказываться не от модной, а необходимой вещи как null safety. В примере кода явно проблемы с диагностическим сообщением, не более — если компилятор умный, то ему должно быть понятно, что если условие не выполнено, то value всегда null и нужно выдавать ошибку. Изменить условие на value == null и ошибка должна исчезнуть. Если конечно котлин достаточно умен.
Почему присваивание — это не выражение?
Потому что это одна из тех вещей, которая имеет минимальную пользу, но вносит незаметные баги на ровном месте. А живет лишь как дань С. Поэтому многие языки отказались от этого. Оно того не стоит.
Чем не угодил оператор "?:"?
Тоже самое — оно того не стоит.
За что убили автоматическое приведение типов?
Здесь еще более явна причина — оно того не стоит. Помнится, Страуструп сам недоволен тем, насколько простителен С++ в этом плане, хотя он даже жестче С. Совсем неудивительно, что современные языки требуют только явных приведений.
Недо-typedef
В данном случае речь как раз о другом названии для типа и нужно это для рефакторинга, чтобы потом эти псевдонимы типов экспортировать, а не как привыкли в С, чтобы прятать реальные типы и давать им красивые имена без всякой реальной пользы. Недоделали может быть, но фича востребованная и есть во многих современных языках в том или ином виде.
> Тоже самое — оно того не стоит.
А он то какие баги вносит?
if else может делать тоже самое в kotlin, большинство мест где тернарный оператор использовался в джаве это проверка на null и значения по умолчанию прекрасно заменяются .? и элвис оператором.
Тернарный оператор вносит нечитабельный код. В больших проектах на C или Java с завидной переодичностью встречаются ... ? .... : ....
выражения на 3 строки, а то и больше. И сделать с этим ничего нельзя — в отличие от if-else, ?-: даже с подсветкой синтаксиса плохо читается.
Как выше заметили, if-else в Kotlin — это такое же выражение, т.е., в отличии от C и Java, тут тоже можно написать выражение на 3 строчки, которое будет плохо читаться. Да и тот же ReSharper тернарный оператор в if-else рефакторит в один клик. И из 3-х строчного выражения можно мгновенно получить простыню на 2 экрана, но читабельность это, честно говоря, не сильно улучшает.
А вот выделить пяток простых выражений в переменные — улучшает, не смотря на наличие там тернарных операторов (и даже благодаря им).
Впрочем, с программистами, не заботящимися о читабельности, это не самая большая проблема (вы видели что делают фанаты дженериков, например?).
Программисты программистами, а некоторые синтаксические конструкции провоцируют писать плохой код.
В этом смысле if-else
имеет преимущество. Во первых, выражение if-else чаще разносят на отдельные строки, почему то так принято. Во вторых, if-else
легче подсветить ярким синим цветом который визуально выделяется в остальном коде, в то время как подсветка ? :
такого эффекта не дает.
Но это все довольно субъективно, и как уже отмечалось, в Kotlin рассматривают возможность добавления тернарного оператора.
Именно, я предложил разделять. Перегнуть палку и написать плохо можно далеко не только тернарным, и я в нём не вижу ничего ни уникального, ни плохого.
Звучит, как будто проблема вообще не в тернарном операторе, а в длинных выражениях. Запретите компилировать выражения, длиннее 40 символов (и применение магических чисел заодно), это решит проблему?
> Во первых, выражение if-else чаще разносят на отдельные строки, почему то так принято.
Ну «примите» не писать выражения в 3 строчки, будет не чаще.
https://discuss.kotlinlang.org/t/ternary-operator/2116/12
Да ну, то что там приведено в пример (двоеточие отдельно используется, ну надо же) — ересь какая-то. Вполне можно в разных правилах грамматики использовать одинаковые терминалы — главное аккуратно. Нет времени досконально разбирать грамматику Котлина, но думается, решить эту "мегапроблему" можно.
Кратенько, по существу т.к. придирки и упражнения с схоластике не особо интересны.
У for-in своя семантика, это не for из C.
То что сейчас в Kotlin — это foreach, который именно в таком виде завели в каком-нить С# именно для того, чтобы обозначить его нишу.
Мне, по сути, без разницы как оно называется.
У меня претензия к отсутствию альтернативы нормального for. Считайте его хоть из С хоть из Java.
Нет, чистый Kotlin null-safe, проверки будут только на стыке Java и Kotlin.
Это неправда.
Посмотрите генерируемый код.
А на стыке Java\Kotlin как раз проверок не будет вообще, если на стороне Kotlin явно не указан "?"-й тип.
Потому что в Java все потоки работают с одной кучей и в общем случае объект не привязан к какому-то отдельному потоку.
В Java не нужно обеспечивать синхронизацию? Все операции\функции\блоки… атомарны? Компилятор как-то автоматически за меня определит границы блока синхронизации и создаст для них код?
Если нет, то аргумент не принимается.
ПС: Я не знаю языков где "объект привязан к какому-то потоку". Такие существуют?
Но ведь smart cast и так работает для локальных переменных, в том числе мутабельных:
http://try.kotlinlang.org/#/UserProjects/vg26o1hplih1u2c89031fvdbl/svm0e1lu4fplliqtj8c5ejague
И надо не забывать, что в Java проверка на null с выкидыванием NPE так чудно заоптимизирована, что и не стоит по сути ничего. Если смотреть на сгенерированный машинный код из JIT то там этих проверок и нету вовсе. Всё срабатывает через Uncommon Trap и прерывание процессора.
То что сейчас в Kotlin — это foreach
for-loop
в Kotlin это не совсем foreach. Вы уже наверное посмотрели byte-code и знаете, что в ряде случаев итератор в for-loop заменяется на стандартный цикл с индексами. Это ставит производительность for-loop на совсем другой уровень по сравнению в forEach.
Вообще я насчитал 5 случая применения С-подобного for-а, и считаю что Kotlin ни в чем не проигрывает:
1) Перебор значений:
for(x in 101..200) {...}
2) Перебор индексов в коллекции:
for(i in list.indices) {...}
3) Перебор элементов коллекции:
for(value in values) {...}
4) Повторение операции N раз:
repeat(N) {...} // все оптимизируется в for(int i = 0; i < N; i++) {...}
5) Повторение операции пока верно условие
while(condition) {...}
// да, для такого и в C лучше использовать while если хотите чтобы в коде было легко разобраться
Вообще я насчитал 5 случая применения С-подобного for-а, и считаю что Kotlin ни в чем не проигрывает:
1) Перебор значений:
Ну, у вас же тут только тривиальный случай с простейшим условием остановки и единичным шагом, и с единственной переменной. В этом случае правда лучше перебор по диапазону. Но for можно куда более хитрый написать. Да, понятно, можно всё это while'ом сделать, но громоздко и некрасиво же будет. Тем более, реализация в компиляторе сишного (явского) фора — задача банальнее не придумаешь.
Нет, чистый Kotlin null-safe, проверки будут только на стыке Java и Kotlin.
Не совсем, Kotlin вынужден ставить проверки во всех публичных метода на случай если их позовут из Java.
Другое дело что на JVM null-проверки не только ничего не стоят, а более того, в ряде случаев и ускоряют код, так как позволяют JIT лучше оптимизировать.
Чем не угодил оператор "?:"?
где такое еще есть?
В CoffeeScript. Там ?
— оператор проверки на неопределённое или null-значение.
Тернарник:
// js
let a = some ? 23 : 42;
// coffee
a = if some then 23 else 42
Проверка на неопределённое значение:
// js
if (a !== null && typeof a !== 'undefined') ...
// coffee
if a? ...
Откуда взялась эта абсолютно бредовая уверенность компилятора в том, что каждая буква моей программы — это элемент многопоточной конкуренции?
Дело не в многопоточности, а в том, что value — это не поле, а property. Когда вы вызываете if ( value != null ) или return value, вы обращаетесь не к полю value, а к геттеру, которого в вашем случае не видно, но он может появиться в наследнике класса. А так как геттер — обычная функция, он может вернуть как null так и не null, даже будучи вызваным 2 раза подряд.
то с картами все значительно печальнее:
Можно вот так:
mapOf(
"1" to 1,
"2" to mapOf(
"2.1" to 21,
"2.2" to 22
),
"3" to mapOf(
"3.1" to 31
)
)
Это объяснение уже начинает иметь какой-то смысл кроме абстракций, спасибо.
Есть какие-то подтверждения этого?
fun F() : Int {
val result = value
if ( result == null ) return 0
return result
}
Или проще:
fun F() : Int {
return value ?: 0
}
В первом примере result уже не property, а локальная переменная(константа), значение которой мы проверяем напрямую, без геттера.
Я знаю несколько способов обойти описанную проблему, к описанному выше еще можно добавить "?.let", но вопрос был не об этом.
Есть ли какие-то подтверждения того, что запрет на smart cast растет именно из "потенциальной неодинаковости getter-а", а не из-за многопоточности?
Этот пример говорит об обратном:
class A {
private var value : Int? = 0
fun F() : Int {
if ( value == null ) return 0
return value
}
}
Ошибка точно та же, гетеров нет. То же и с "@JvmField".
В ссылках на то, что не имеет отношения к тексту есть какой-то смысл?
Какой?
Это только слова, к тому же без уточнения "non-private" еще и неправильные (и это без учета JvmField).
Во всех случаях кроме наличия обоих get\set в классе будет поле.
Я не помню, но вроде и с protected-ом будет protected поле, которое будет использоваться напрямую в родном классе и наследниках.
Еще раз: зачем приводить бесполезные ссылки?
Поверьте, я читал 99% документации, благо ее не вагон.
Фактическое поведение компилятора сложнее 3х строчек описания
А может стоит всё-таки сначала поизучать язык? Ах, хотя нет, зачем.
1) Банально запоролись уже на втором аргументе про null-safety. Ну и то, что кто-то привык к си — вообще не аргумент и кричать про бесполезность null-safety немного стрёмно. Как минимум это дополнительный инструмент построения более стабильной и прозрачной архитектуры.
И вышеприведенный код отлично компилируется и работает.
Другой вопрос в приведении типа к non-null — да, согласен насчет синтаксиса, код получается малость «экспрессивный».
2) Чем вам for-то так не угодил? Синтаксисом? Объёмом генерируемого кода? Посмотрите в сторону того как современные компиляторы оптимизируют код — в большинстве случаев ваш цикл превратится в обычный ваш «сишный» for.
3) Аннотации же нужны, декларирующие о null-ability типа! И по дефолту котлин вам разрешает использовать код из java без каста к non-null, но предупреждает, что вы можете выстрелить себе в ногу. Может всё-таки вы будете сначала искать хоть какую-то информацию перед написанием статей?
Это замечательно компилируется без каких-либо ошибок или предупреждений, запускается и с грохотом схлопытвается cо стандартным NullPointerException т.к. тут Kotlin не проверяет ничего и нигде. И где обеща..., тьфу, декларируемая безопасность?
4) Ну, наверное, за введение оператора "?:", чтобы избежать конфузов. Поясняю: в котлине есть оператор, который оценивает выражение слева, и если оно возвращает null, то отдаёт выражение справа. То, чего вам так не хватало в пункте 2 про null-safety.
Чем не угодил оператор "?:"?
За что ампутировали оператор "?:"?
Что усмотрели вредного в таких конструкциях?
5) Чтобы не было конфузов с boxing-ом типов? Скажу, что меня тоже бесит постоянно конвертить даблы во флоаты и обратно, но это плата за код, который работает именно так как мне надо и не выплёвывает сюрпризов.
За что убили автоматическое приведение типов?
6) Вам ничего не говорит пунктик про то, что котлин полностью обратносовместим с Java? Kotlin нигде не позиционируется как «офигенно крутой язык, меняющий jvm и все ещё компилирующийся в Java-совместимый байткод». Противоречие видите? Не под силу Kotlin изменить работу JVM и добавить туда ваши пресловутые шаблоны.
Правда, есть одна клёвая фишка — reified. Сродни тому, о чём вы так молите (о да, вы сможете скастить объект в ваш generic-type в рантайме!), работает правда только с inline методами (угадайте почему?).
В котлин-generics меня раздражает разве что in\out, особенно когда начинал осваивать язык, ловил разрыв шаблона.
Бог с ней с Java т.к. меня лично ее проблемы не волнуют. Меня волнует ущербность шаблонов конкретно в Kotlin, который позиционируется как другой язык, а не препроцессор для Java и, в результате, кивать на ее недостатки по меньшей мере бессмысленно.
7) А если вот так?
то с картами все значительно печальнее:
val tree = mapOf( Pair("dir1", mapOf(Pair("file1", 0), Pair("file2", 1))), Pair("dir2", mapOf( Pair("dir21", mapOf(Pair("file1", 0), Pair("file2", 1))), Pair("dir22", mapOf(Pair("file1", 0), Pair("file2", 1))))) )
P.S. Вы бы ещё на отсутствие checked exceptions пожаловались.
Меня просто поржает: с чего такая агрессивность-то?
Наследие "кг\ам" или просто день не задался?
Если я чем умудрился обидеть, то прошу прощения.
В комменте, практически в каждом пункте текст, который вызван простым непониманием вопроса в статье.
Не проблема, я не заставляю этот текст читать, тем более что, видимо, это противно.
Это взаимно, так что на этом прощаюсь
Признаюсь — конкретно подгорело со стиля написания статьи, слишком много утвердительного тона и громких фраз, которые не очень коррелируют с реальностью.
Если бы текст статьи был более «вопросительный» и мягкий, я бы и
Я понимаю, что статья написана после первых часов опыта работы с Kotlin и тут в основном задаются вопросы к сообществу, но эта форма подачи…
Вот вроде он на свифт похож очень, но что-то с ним не так, мне лично синтаксис и некоторые особенности (вроде синтаксических костылей вместо static методов и полей) совсем не понравились.
А еще я не понимаю, почему каждый разработчик языка старается придумать что-то уникальное в синтаксисе, никак не влияющее ни на что, кроме создания доп. путаницы. Как я понимаю, Kotlin развивается с оглядкой на Swift, очень уж они похожи, но в одном языке fun, а в другом func. И куча таких мелких различий, которые при работе над двумя проектами на разных, но очень похожих языках неплохо ломают мозг.
За пару часов язык в принципе освоить невозможно и, соответственно, делать о нем какие-то выводы сложно.
Свифт, наверное, хорош (когда возникла необходимость писать под иос я, взглянув на синтаксис objective-C сказал что буду это делать только за очень отдельные деньги, и вопрос с тех пор не поднимался, жаль тогда не было свифта) но, к сожалению, за пределами одной платформы его не существует.
Если же возникает задача для андроида, то, на мой взгляд, Kotlin — это наилучший выбор.
Уж в сравнении с явой-то точно.
За пару часов язык в принципе освоить невозможно и, соответственно, делать о нем какие-то выводы сложно.
Само собой, освоить не получится. Но от свифта у меня были совсем другие впечатления за первые пару часов. Однако, это лишь мое субъективное мнение.
когда возникла необходимость писать под иос я, взглянув на синтаксис objective-C сказал что буду это делать только за очень отдельные деньги, и вопрос с тех пор не поднимался, жаль тогда не было свифта
+1, я года 4 собирался освоить iOS, каждый раз офигевал от синтаксиса Obj-C и бросал это дело.
Если же возникает задача для андроида, то, на мой взгляд, Kotlin — это наилучший выбор.
Вот не уверен — язык очень молодой, меняться же наверное будет. Тот же свифт уже вовсю в третьей версии, и каждый раз переход между мажорными версиями — это боль и жжение в области таза из-за того, что половину языка меняют :(
Да, скорее всего, будут что-то менять.
Я лично на это даже надеюсь :)
Пока я каких-то проблем не испытывал.
Вот сегодня (вчера) прилетела обнова языка и в ней оказались ампутированными битовые операции для типов Short и Byte. Пришлось править кусок либы и я уже хотел по этому поводу расстроиться, но потом посмотрел на реализацию и на Java и, в общем-то, причина этого действия стала ясна. Меня "убедили" в адекватности этого действия.
Пока это было максимальное, на что я нарывался.
Да и, на мой взгляд, движуха в языке не является особой проблемой.
В любой развивающейся системе будут изменения — этого не избежать.
Я лично пришел к следующему рецепту: при обновлении смотрю на ньюсы и, если количество решенных проблем или интересных фич перевешивает трудозатраты на адаптацию, то перехожу; если нет — сижу на старой версии.
Это я по опыту изменений, которые приходилось вносить с принципиальным перелопачиванием многих сотен кб сорсов. В Kotlin-же я пока не видел ничего даже отданно схожего по трудозатратам.
Я лично выбрал этот язык с прицелом именно на андроид т.к. остальные альтернативы мне показались значительно хуже либо синтаксически и стилистически (например Scala), или принципиально (например, нетипизированный Groovy).
На мой взгляд, именно Kotlin максимально близок к Swift, но, возможно Вам лучше подойдет что-то другое т.к. иногда лучше перейти на что-то совсем другое, чем "очень похожее, но совсем не такое в мелочах" — будет меньше ошибок и расстройств.
Java сейчас официальный язык.
Остальное — уже как-то совсем не тру. Понятно, что все эти языки компилятся в байт-код джавы и на выходе получаем примерно одно и то же, но как-то неправильно это, прямо как кроссплатформенные аппы на вебвью делать :) К тому же, у скалы и груви, насколько я помню, предназначение другое.
Меня-то в принципе и джава почти устраивает. По сравнению с Xcode + Swift, Android Studio + Java хотя бы адски не лагает на топовом железе. Я все на старенький макбук грешил, а оно точно так же умудряется лагать на i7-7700K с 32 гигами DDR4-2400… Причем, говорят, Obj-C не лагает, лаги именно в автокомплите свифта :)
В любом случае, я бы не стал крупные проекты делать на молодых языках. Это все очень круто и ощущения отличные, но потом язык кардинально меняется со следующим релизом, и становится очень больно :)
Для андроида нет никакой разницы в используемом языке, лишь бы он мог вызывать Java, так что писать можно абсолютно на чем угодно, а уж на любых JVM языках и подавно.
Если говорить о языках JVM, то разница будет только в настройке процесса компилятции и рюшках, доступных в среде.
Kotlin в обоих случаях не доставляет абсолютно никаких проблем, но как дела обстоят у других я не интересовался, возможно тоже без проблем.
Насчет смены жавы — я просто не верю.
Причина проста: никто не будет переписывать существующий код. Во-первых, потому что в принципе не будут (текущее лоскутное одеяло АПИ тому подтверждение) и, во-вторых, потому что без баквард-компатибилити гугл потеряет огромное количество "лайков". Поддержать же два языка бессмысленно. Поэтому я думаю, что Java в андроиде навечно.
Насчет крупных проектов — это философский вопрос, на самом деле.
К примеру я, не так уж и давно, перестал писать вполне себе крупный проект на языке С, который собирался таким зверем как Watcom 8.0, про который многие уже и не знают, наверное.
На работе народ вполне себе непрерывно пишет на Borland C 3.0.
Собственно, в самой старости языка нет никакой проблемы с точки зрения поддержки проекта, если функционал этого проекта состоялся.
Насчет смены жавы — я просто не верю.
Причина проста: никто не будет переписывать существующий код
Сложно сказать, что там у гугла на уме — но Kotlin ведь полностью совместим с джавой. Достаточно просто рантайм принести на девайс, дабы не тащить его в каждой апк-шке.
Apple ведь точно этим сейчас и занимается — меняет Obj-C на Swift. И даже частично компоненты переписывает — говорят, в нынешней макоси как минимум Dock уже на свифте написан :)
Любой JVM язык полностью совместим с жавой. По определению :)
Вообще, ассемблер JVM крайне примитивен и, если бы в него, в свое время, конструктивно не были бы воткнуты всякие "хаки" для ускорения, то для него можно было бы собрать вобще любой язык, с любыми произвольными возможностями. Т.к. основной задачей любого JVM языка является совсем не поддержка этого ассемблера, а обеспечение точного и однозначного вызова java-языкХ-java, то все они абсолютно одинаково с ней "совместимы": генерируют точно такие же классы, неотличимые в среде выполнения от сгенерированных жавой.
С "доложить рантайм" понятно, но стимула делать это у гугла я пока не вижу. С апле ситуация другая совсем. У них крошечный процент рынка мобильной техники, несравнимо более высокий порог входа для разработчика, стоимость самой разработки и нулевой интерес аппартных производителей к их платформе. Swift — это вынужденный шаг по популяризации свой платформы при сохранении закнутого статус-кво. Насколько удачный сказать сложно, но без него апле бы попросту захирел и в мобильной области, как уже случилось с настольной.
С «доложить рантайм» понятно, но стимула делать это у гугла я пока не вижу.
В принципе, да, тут тяжеловато с точки зрения обратной совместимости — Apple-то свои девайсы очень долго обновляет, и им не проблема принести новые либы. А с Android-вендорами все совсем не так однозначно. Разве что нести рантайм в апк для старых версий ОС, а для новых не нести… Но это куча геморроя.
Насколько удачный сказать сложно, но без него апле бы попросту захирел и в мобильной области, как уже случилось с настольной.
Да вроде как за последние 4 года у macOS доля аж на 2.5 процента выросла (до 9.6%), что очень немало для их ценовой категории. Так что пока живее всех живых :)
Это не правда. Java != JVM.
К тому же не забывайте что совместимость работает (или не работает) в обе стороны, из Java вы можете использовать любой Kotlin код, что не правда для многих других JVM языков, порой очень сложно или невозможно вызвать код написаный на другом JVM языке.
Поддерживать полную двухстороннюю совместимость с Java это совсем не легкая задача
Т.е. существуют языки, в которых невозможно устаномить listener для класса на Java?
Если это так, то спасибо, не догадывался.
А какая ниша применения у такого рода языков?
Обычно требуется ограничить себя в некоторых фичах, чтобы сделать Java-совместимое API или callback. Например, многие Scala-библиотеки имеют нативное API и дополнительно Java API, частично пересекающееся с нативным.
Получается, что то что я написал выше: java-языкХ-java — это вполне правильно.
Языков JVM, которые бы не позволяли вызвать ява-код и быть вызванными из него не существует.
А то что типы могут потеряться и и тому подобное — это уже не принципиальные особенности т.к. они логически обоснованы разными языками.
Нет, listener установить можно всегда если можно унаследоваться от нужного типа. Речь шла о ситуации, когда код на другом языке уже написан — но из Java нельзя его вызвать, потому что компилятор Java не понимает что от него хотят.
Языков не скажу, но как так можно сделать могу предположить. К примеру, JVM, в отличии от Java, поддерживает перегрузку методов по возвращаемому значению. Если сделать на другом языке такие методы — то из Java нельзя будет их вызвать.
Или, например, можно использовать ключевые слова в качестве идентификаторов.
Или вот пример из соседней оперы. На языке C++/CLI можно создать публичный шаблонный класс:
template <typename T> public ref struct Holder {
T value;
};
public ref struct Example {
static Holder<int>^ Foo() {
Holder<int>^ result = gcnew Holder<int>();
result->value = 42;
return result;
}
};
Теперь если добавить такую сборку в проект на C#, то результат вызова Example.Foo()
нельзя записать в поле класса или в явно типизированную переменную! Кажется, в D похожий эффект назвали "Волдеморт-типом", типом-который-нельзя-назвать. Почему? Да потому что класс называется Holder<int>
. Прямо так, с угловыми скобками. И это не generic, это обычный класс :)
Тот же ILSpy декомпилирует этот класс в C# вот так:
public class Holder<int>
{
public int value;
}
Подозреваю, что в том же упоминавшемся выше Ceylon сделали что-то подобное...
К примеру, JVM, в отличии от Java, поддерживает перегрузку методов по возвращаемому значению.
Приятно узнать что-то интересное, спасибо. Погляжу в jvms.
Из эпичных вещей с неработающим interop: из Scala нельзя было вызвать валидный java-код. Проблема осталась и в 2.12.1.
Код логгера со стороны java (slf4j) имеет несколько перегруженных методов (для пример достаточно двух: Logger#info(String, Object...)
и Logger#info(String, Object, Object)
.
Из scala вызов такого метода с тремя аргументами аргументами типа String
становится ошибкой компиляции, а из java вполне допустим, т. к. у них разные правила обработки перегруженных методов. Под спойлером воспроизводимый пример.
Возьмем для примера такую имплементацию "логгера":
class Logger {
void info(String s, Object... args) {
System.out.println(s);
System.out.println(args.getClass().getName());
}
void info(String s, Object a1, Object a2) {
System.out.println(s);
System.out.println(a1.getClass().getName());
System.out.println(a2.getClass().getName());
}
public static void main(String[] args) {
new Logger().info("one", "two", "three");
}
}
Она компилируется и работает (javac Logger.java
, java Logger
, если хочется проверить).
Теперь попробуем её вызывать из scala:
object Main {
def main(args: Array[String) = {
val l = new Logger
l.info("one", "two", "three")
}
}
И при вызове scalac Main.scala
получаем
Main.scala:4: error: ambiguous reference to overloaded definition,
both method info in class Logger of type (x$1: String, x$2: Any, x$3: Any)Unit
and method info in class Logger of type (x$1: String, x$2: Object*)Unit
match argument types (String,String,String)
l.info("one", "two", "three")
^
one error found
Из scala вызов такого метода с тремя аргументами аргументами типа String становится ошибкой компиляции
Во!
В Kotlin точно так же.
Я пытался написать плагин к JavaDOC и вызвать явовский парсер из Kotlin не смог вообще. Именно из-за обилия перегруженных функций с одинаковыми типами параметров (куча форм вызова main с передачей ей строковых параметров).
Там огромное количество эдж кейсов, вроде листенер то установить вы сможете, а вот результирующее значение будет с non-reified generic или еще какими то ограничеями из-за того, что это Java объект, а не объект вашего JVM языка.
Если интересно можете посмотреть какие ограничения на фичи накладывает Scala или Closure при вызове из Java и наоборот.
У Kotlin совершенно другой подход по отношению к обратное совместимости https://blog.jetbrains.com/kotlin/2015/12/kotlin-1-0-beta-4-is-out/#comment-41068
Так же под рукой нет, но есть посты об этом в официальном блоге Kotlin
Они не в том положении как Apple, которые могут нагнуть все комьюнити и сломать обратную совместимость очередной версий и даже существующий код заставить переписать, иначе в AppStore не пустят (привет Swift 3)
И Kotlin не намного моложе Swift: Разработка Swift внутри Apple начата в Июле 2010-го, первый коммит в репозиторий Kotlin был сделан в Октябре 2011 (возможно разарботка была и раньше). В том же 2011 впервые анонсирован, Switft показали публике в Июне 2014, тут еще большой вопрос кто у кого что подсмотрел
Описание в обратной нотации, т.е. "имя-тип" диктуется не огладкой на Pascal или тенденциями, а простым требованием синтаксиса там, где описние типа опционально. В противном случае текст программы читается (и возможно компилируется) неоднозначно.
Фигурные же скобки — это просто всего один из трех возможных на клавиатуре вариантов самой краткой записи начала и конца. Квадратные как-то "сроднились" с индексацией, вот и получается что выбрать производителю просто не из чего. Кому хочется больше альтернативщины, те используют круглые, кому что попроще — фигурные :)
использовать ключевые слова для объявления функций и переменных, как в Pascal
Почему не сказать сразу "как в ML" (истоки и того, и другого лежат в 70х)? То же ключевое слово fun
было в Standard ML, как минимум.
Я понимаю, что писать возмущенные тексты приятно :) Не могу сказать того же о чтении таких текстов. Постараюсь по существу ответить на ту часть, которая не покрывается документацией.
Про тернарный оператор (?:): многим не нравится, мы посмотрим, что можно сделать.
Про литералы для коллекций: тоже сделаем.
Объявления новых типов, а не алиасов тоже сделаем.
Неявные преобразований числовых типов у нас действительно нет. Есть перегрузки для арифметических операций. Мы думаем над тем, как сделать поменьше явных приведений типов, но с этим пока есть некоторые сложности.
C++ для JVM не сделаем. У нас другие задачи :)
Пожалуйста учитывайте, что многим нравится if-else в текущем виде
Так его убирать и не предлагают, вроде бы.
и не нравится тернарный оператор
(удивлённо) — так и не используйте, раз не нравится.
То же про отсутствие C-шного for-цикла.
То же про отсутствие C-шного for-цикла. :) ну, правда, ни кто же не заставляет.
не насовали всего, чтобы всем угодить,
Это имело бы смысл, если бы речь шла о какой-то языковой экзотике. Но тут-то вещи настолько традиционные, что их отсутствие ничего, кроме недоумения не вызывает. Особенно, отсутствие тернарного оператора при наличии "элвиса".
Огромное спасибо за отклик.
Рад что одиозное название не отпугнуло от чтения
В данном случае, как в аббревиатуре RTFM, ругательная часть просто является обозначением, а не моим отношением :)
К сожалению, мой английский заточен на общение и кино, а написание довольно абстракных технических вопросов и пояснений мало того что отнимает огромное количество времени, так еще и, скорее всего, вызывает недоумение, поэтому многие вещи, которые хотелось бы иметь или о которых хотелось бы спросить я уточнить на форумах Kotlin не могу.
Пользуясь случаем хотелось бы обратить внимание на такие возможности:
- Добавить компилятору ключи командной строки, которые бы могли влиять на его поведение. В настоящее время ключей практически нет, но их использовани могло бы решить некоторые частные вопросы. Например ключ для отключения механизма null-safety был бы часто полезен.
- Во многих языках есть удобная фича: возможность указать ключи (или настройки компиятора) прямо в исходных текстах. Например в С для этого существуют pragma. Использование таких конструкций позволяет локально менять настройки и действует на весь модуль что в яве аннотациями не сделать, насколько я понимаю.
В результате этих двух пунктов можно было бы вынести какой-то код в отдельное место и указать для него другие правила (например отключить тот же null-safety, который при работе с каким-нить свингом просто мозг выносит т.к. превращает исходник в забор из "!").
ключ для отключения механизма null-safety был бы часто полезен
Может я не правильно вас понял, но ведь есть ключ для отключения генерации not-null проверок
$ kotlinc -X
Usage: kotlinc-jvm <options> <source files>
where advanced options include:
-Xno-call-assertions Don't generate not-null assertion after each invocation of method returning not-null
-Xno-param-assertions Don't generate not-null assertions on parameters of methods accessible from Java
Поняли правильно, но я там дальше написал использование.
Это не то.
Это глобальный ключ.
Я не хочу отключать проверки во всем проекте, без возможности указать его действие для модуля он для меня бесполезен.
Можно указать раздельные ключи индивидуально для каждого файла в IntelliJ?
- Насколько я понял этот ключ делает не то что я хочу.
Он убирает оверхед проверок, а мне нужно другое: а) отключить исключение из smart-cast для var и б) временно выключить ошибки преобразования, т.е. временно интерпретировать типы не как "?", а как "!", что позволит мне оперировать nullable без огорода "!!" в коде.
Пользуясь случаем хотелось бы обратить внимание на такие возможности:
Вводить такие ключи и прагмы — это крайняя мера, мы пока попробуем без них. (Аннотации на файл в Котлине есть, но их для таких целей мы использовать пока не хотим.)
В данном случае, как в аббревиатуре RTFM, ругательная часть просто является обозначением, а не моим отношением :)
Неприятнее всего было, конечно, не название, а то отношение к нам, которое выражено в тексте.
Зато научитесь понимать отличатьArray<Array<Array<Array<Double>>>>
иArray<Array<Array<Double>>>
с первого взгляда и с любого расстояния
Кстати прекрасный кейс где typealias отлично работают:
typealias Matrix4<T> = Array<Array<Array<Array<T>>>>
typealias Matrix3<T> = Array<Array<Array<T>>>
Э… вообще-то у матрицы ранг всегда 2 и она обязана быть выровненной. А то, что вы привели, даже как назвать-то и не знаю...
Окей-окей, согласен название не удачное и тем более применения, я просто сослался на пример который привел автор статьи, что если у вас действительно такая проблема, то typealias на которые так же были в статьи критикуемы, прекрасно могут с этим помочь
Matrix3d, Matrix4d
Увы, но название "Matrix3d" уже зарезервировано под квадратную матрицу 4x4… :)
The matrix3d() CSS function describes a 3D transform as a 4x4 homogeneous matrix. The 16 parameters are described in the column-major order.
Своя логика тут есть, потому что матрица 4х4 описывает проективное преобразование трехмерного объекта.
Потому что слово "трансформация" уже используется в имени свойства. Зачем дублировать?
А вот отличать проективное преобразование в пространстве от преобразования на плоскости — надо!
Вообще-то, ранг матрицы не обязан равняться двум. То есть может, конечно, если в матрице два линейно-независимых столбца (строки), но это совершенно не обязательное условие существования матрицы.
null-абле
Это слово раздвинуло мое сознание. Почему не "nullable"?
тред, трид
Поток
while-ом
Просто "while", слово на латинице в русском тексте не склоняется.
var value : Int? = null
fun F() : Int {
if (value == null) return 0
return when (Random().nextInt()) {
3 -> value!! + 2
12 -> value!! + 1
5 -> value!! * 4
else -> 0
}
}
лучше таки использовать встроенные иснтрументы обеспечения null-safety:
var value: Int? = null
fun F() = value?.let {
when (Random().nextInt()) {
3 -> it + 2
12 -> it + 1
5 -> it * 4
else -> 0
}
} ?: 0
Это всего лишь кратенький пример иллюстрация.
Обойти в нем использование "!!" можно несколькими способами.
Но в реальной жизни, к сожалению, не все они удобны или применимы.
К примеру, при работе с интерфейсом swing в одной функции может происходить общение с кучей элементов интерфейса, каждый из которых nullable и с ним производится всего 1-2 действия.
Ни оборачивать каждый элемент в лямбду, ни заводить локальную переменную для них не имеет никакого смысла — это просто хуже читаетя и занимает больше места.
Приходится городить кучи "!!" в местах, которые гарантированно не null при использовании.
Это достает, вплоть до написание глобальной аннотации к классу.
Не уверен про ваш случай с Swing, но как я понял это близко к ситуации у нас в Android при работе с UI и вьюхами. Мы убираем nullability через lazy проперти, так же вы можете использовать lateinit
Да, аналогично, но в андроиде ее практически нет.
Во-первых, у котлина есть автоматическое создание доступа к элементам описанным в лайауте (т.е. создавать переменные в классе вообще не нужно).
Во-вторых, в андроиде есть дизайнер.
В свифте дизайнер(ы) напоминают "жалкое подобие левой руки" и, как правило, весь инерфейс создается в рантайме полностью вручную.
В-третих, из-за наличия дизайнера, в коде под андроид вы оперируете УЖЕ созданным интерфейсом и, соответственно, в активити\фрейме… можно использовать любой способ доступа к элементам (в том числе вручную lazy, если вас устраивает безумный оверхед на классах для каждой анонимной функции). При ручном создании интерфейса этим "лейзам" просто неоткуда брать компонент, поэтому используются всякие аналоги вида "firstAssign", но их не всегда удобно использовать.
Не претендую даже на звание профана… но, на мой дилетантский взглядпрофан, насколько можно верить словарю ушакова — это значит «Человек, совершенно несведущий в чем-нибудь», однако дилетант — это " Занимающийся [чем-либо] как любитель (не профессионально)". Получается, что дилетант несколько более сведущ в предмете, чем профан. Сори за занудство, просто мне самому бывает очень неприятно, когда я понимал какое-то слово неправильно и употреблял, а мне про это даже никто не сказал.
ПС: В виде отдельного гвоздя в голову я пожелаю кому-нибудь написать библиотеку для работы с матрицами. Зато научитесь понимать отличать Array<Array<Array<Array>>> и Array<Array<Array>> с первого взгляда и с любого расстояния.
typealias Matrix4D<Double> = Array<Array<Array<Array<Double>>>> typealias Matrix3D<Double> = Array<Array<Array<Double>>>
В разработке ЯП важен баланс инженерного (частного) и академического (системного) подходов. Kotlin повело в инженерную крайность.
А если еще и обратная совместимость <...> будет постоянно ломаться
Надеюсь тема про совместимость взялась не из довольно тухлого срача в VK несколько-дневной давности, потому что Kotlin еще ни разу (тьфу-тьфу) не нарушил совместимость, которую команда обещала.
Процитирую более развернутый комментарий @gilgor: https://habrahabr.ru/post/322256/#comment_10081594
У Kotlin совершенно другой подход по отношению к обратное совместимости https://blog.jetbrains.com/kotlin/2015/12/kotlin-1-0-beta-4-is-out/#comment-41068
Почему Kotlin отстой