Как стать автором
Обновить

Комментарии 11

Странный пост. Все правильно ваш интервьюер сказал. Нал-сэйфти - зе бест.

А про Optional в джава смешно конечно сравнивать с этим.

И в пример приводить грязный хак с рефлексией - так себе пруф.

В общем руки прочь от котлина! 🤚🏻

И что такого дала вам null safety в котлин? Не, ну она бьет по рукам людей которые не пользовались другими контейнерами или аннотациями. И синтаксис чуть более лаконичен. (Соглашусь что бить по рукам это в целом неплохо, и сахарок приятный)

Как вы используете null-safety что это прям лучшая фича в типах в Котлине?

Кстати, где тут грязный хак? Я просто хотел 2 обьекта с кроссреференсом, а получил NPE)

Контейнеры это полумера. В них также можно передать null. На моей практике бывали и null переданные в Optional, и даже Some в котором лежал null (правда это было в Scala, но все же).
Nullable типы не про то, что вы никогда не поймаете NPE, это лишь более естественный механизм описания типов.
И он практично реализован в Kotlin. В рамках Kotlin кода без рефлексии тяжело поймать NPE, а все проверяется на уровне компиляции. Для всего того что приходит из Java кода полагаемся на аннотации (часть которых также понимает компилятор), а в крайнем случае возвращается платформенный тип.

Хм.

Так в чем все же принципиальная разница?

Я не могу так сходу придумать пример кода, который сильно выигрывает от наличия в системе типов Kotlin nullable типа. Чтобы при взгляде на него приходила мысль вроде: «на java даже с контейнерами и аннотациями я не добьюсь схожего поведения компилятора и безопасности своего кода».

Будет здорово посмотреть пример и его обсудить.

Самое очевидное, это "согласование инвариантов" в чем-нибудь типа:

fun <T> doSome(value: T, transform: (T) -> T) = transform(value)

Смысл в том, что - какой-нибудь - doSome<String> уже закрывает кучу вариантов. Если есть таки варианты сделать такое аннотациями -- будет круто узнать про них.

Или "чуть-более темное":

fun <T, R> doSome(value: T, transform: (T) -> R) = transform(value)

Что тут можно выправить "контейнерами и аннотациями" я вообще слабо себе представляю. Но если таки можно - буду рад узнать.

Вот буквально сегодня по производственной необходимости пересел с kotlin на java. Реально две самые "бесячие" вещи в "java после kotlin": а - отсутствие nullable/non-null типов, и б - наличие checked exceptions.

И что такого дала вам null safety в котлин? Не, ну она бьет по рукам людей которые не пользовались другими контейнерами или аннотациями. И синтаксис чуть более лаконичен. (Соглашусь что бить по рукам это в целом неплохо, и сахарок приятный)

Наличие nullable/non-null типов дает - внезапно - одну очень важную вещь: T и T? - это разные типы... со всеми вытекающими :-) Собственно, null safety - как таковая - это органичное следствие из.

А то, что на каком-то конкретном бэке она - пока ещё - в каких-то случаях каким-то образом "пробивается" - дело десятое, на самом деле. "Полечат" и это... не в первый раз. В конце концов, и из вашего примера видно, что для этого - мягко говоря - надо "специально стараться" :-)

Важно другое... вы можете выразить nullable/non-null инвариант на уровне типов. В java - пока, по крайней мере - что-то похожее возможно только для "примитивов". Отсутствие необходимости писать какие-нибудь "бесконечные" Objects.requireNonNull - это хоть и приятное, но таки лишь следствие.

"Контейнеры" в java - это вообще мимо темы, как говорится. А аннотации - это паллиатив... т. е. какие-то конкретные аспекты они могут "закрыть", но целиком "область применения" они, к сожалению, не накрывают.

Как вы используете null-safety что это прям лучшая фича в типах в Котлине?

Я вообще не совсем понимаю, как можно использовать null-safety? Использовать можно non-null типы. И с этим - вроде как - проблем быть не должно. Нет?

Кстати, где тут грязный хак? Я просто хотел 2 обьекта с кроссреференсом, а получил NPE)

"Грязный хак" - это возможность через статическую инициализацию выразить cross-referense на non-null типах :-)

> Как вы используете null-safety что это прям лучшая фича в типах в Котлине?

Я вообще не совсем понимаю, как можно использовать null-safety? Использовать можно non-null типы. И с этим - вроде как - проблем быть не должно. Нет?

Ну да. Для non-null типов Котлин дает гарантию отсутствие NPE при работе с ними (по крайней мере для котлиновского кода). Но все остальные типы Котлин обязует жестко декларировать как nullable и проверять при каждом обращении. Отсюда лезут проблемы с отложенной инициализацией, которая чуть менее, чем всегда используется в большинстве фреймворков. То есть вроде как бы исходя из контекста нула быть не должно, но поле все-равно требуется объявить nullable (либо присвоить значение) и каждый раз бестолково проверять. А внедрение lateinit было лишь неудачным костылем.

А проблема в том, что nullability во большинстве случаяев невычислима в статике и является контекстно-зависимой. Поэтому лучшим решением было бы внедрить в Котлин послабление вроде Any!!, где null-safety полностью ложится на разработчика, к тому же такие типы существуют внутренне в компиляторе. Но разработчики посчитали не нужным "портить киллер-фичу".

Но все остальные типы Котлин обязует жестко декларировать как nullable и проверять при каждом обращении.

А какие варианты?! То, что может быть null обязательно нужно проверять.

Отсюда лезут проблемы с отложенной инициализацией, которая чуть менее, чем всегда используется в большинстве фреймворков. То есть вроде как бы исходя из контекста нула быть не должно, но поле все-равно требуется объявить nullable (либо присвоить значение) и каждый раз бестолково проверять.

"Проблема" отложенной инициализации она больше про мутабельность, чем про nullable/non-null типы. Т.е. проблема в том не в том, что "исходя из контекста нула быть не должно", но в том, что "исходя из контекста" должен бы быть val, а приходиться объявлять как var.

Ну а про то, что nullable - безотносительно какой-либо инициализации - нужно проверять уже говорилось.

А внедрение lateinit было лишь неудачным костылем.

Ну он же "неудачный" не из-за обязательности non-null, а из-за var. Non-null тут никак не мешает. А вот отсутствие гарантии неизменяемости после инициализации - мешает.

Но "вот это вот все" уже потихоньку вытанцовывается в полноценные контакты.

А проблема в том, что nullability во большинстве случаяев невычислима в статике и является контекстно-зависимой.

"Замнем для ясности" пока про "невычислима в статике"... но зависимость от контекста в чем выражается?!

Поэтому лучшим решением было бы внедрить в Котлин послабление вроде Any!!, где null-safety полностью ложится на разработчика, к тому же такие типы существуют внутренне в компиляторе.

?! В смысле, позволить выносить в run-time то, что только вот что перенесли в compile-time? Так это и сейчас можно... "забить" на type inferred from a platform call и перестать "бестолково проверять". Но типы свои придется таки описывать вне kotlin - это да... "проблема" :-)

А какие варианты?! То, что может быть null обязательно нужно проверять.

Не нужно, если вы, исходя из контекста выполнения (который в большинстве случаев не может быть вычислен компилятором статически), гарантируете, что здесь не null. И если оно все-таки по каким-то обстоятельствам null, то вы получите вполне нормальный NPE -- это ничем не хуже, нежели каждый раз делать дополнительную проверку и кидать исключение вручную.

"Замнем для ясности" пока про "невычислима в статике"... но зависимость от контекста в чем выражается?!

Форма пользовательского ввода. Nullability полей объекта формы уже может быть проверена выше по треду косвенно или сторонними средствами (валидаторы всякие), а компилятор об этом ничего не знает.

data class MyForm {
  ...
  @NotNull @Valid // JSR-303 annotations
  // наплевать, что NotNull, все-равно ВЕЗДЕ будем вставлять ненужные проверки
  var address : Address?
  ...
}
...
// Типа Address уже проверен, что не null.
// Зачем тут нужен "!!"? Чем он будет лучше простого NPE?
System.out.println(myForm.address!!.city)

?! В смысле, позволить выносить в run-time то, что только вот что перенесли в compile-time?

Именно. Не быть NPE-nazi и дать возможность пользователю выбирать между compile-time check и runtime check.

Не нужно, если вы, исходя из контекста выполнения (который в большинстве случаев не может быть вычислен компилятором статически), гарантируете, что здесь не null.

"Эти грабли, эти золотые грабли..." (с) :-) Проходили же это все. Единственный способ что-то такое гарантировать - использовать non-null типы.

Форма пользовательского ввода. Nullability полей объекта формы уже может быть проверена выше по треду косвенно или сторонними средствами (валидаторы всякие), а компилятор об этом ничего не знает.

Самый очевидный вопрос - если все так хорошо, зачем использовать nullable тип?! Может быть таки не "все так хорошо"? :-)

Возвращаясь таки к теме - у нас тут речь о системе типов. Если мы не можем записать код таким образом, что наш "контекст выполнения" не оперирует nullable типами - значит таки не все так хорошо, и проверки таки нужны. Все просто... и никакой "контекстно-зависимой вычислимости" тут нет.

Именно. Не быть NPE-nazi и дать возможность пользователю выбирать между compile-time check и runtime check.

А в чем смысл ослабления compile-time проверок? Проблема же не в них, а только в том (по вашим же словам), что - на данный момент - по каким-то причинам (не суть) невозможно использовать non-null типы, в ситуациях когда из "контекста выполнения" следуют именно они. Ведь так же?

Я к тому, что если таки ввести в язык notable platform types это же только замаскирует проблему, а не решит её. Разве нет?

null-safety ломается гораздо проще через порядок инициализации (вызов из init блока функции, которая обращается к переменной, которая инициализируется позже). Это плата за интероп с джавой. Если бы можно было сохранить интероп с джавой и при этом иметь foolproof null-safety, то это бы, разумеется сделали. Аналогичная история с lateinit. Его сделали только для работы с API андроида.

Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории