Pull to refresh

Comments 37

А в чем прикол с постинкрементом в самом первом примере?

var i = 5
i = i++ + i++
println(i)

Если он "пост" инкремент, то должен выполняться ПОСЛЕ вычисления всего выражения выражения, после "точки с запятой" которой в этом языке нет. Сначала i = 5+5, т.е.10, затем еще два раза ++, то есть 12. Почему 11?
Так нагляднее:

fun sum(a: Int, b: Int) = a + b

var i = 5
i = sum(i++, i++)
println(i)
// 11

"После" — это после вычисления аргумента функции sum. Вычислили первый аргумент, добавили 1 к i, вычислили второй, добавили 1 к i, вычислили sum, присвоили значение в i.
С оператором точно то же самое.
Вот байт-код

`
  L1
    LINENUMBER 2 L1
    ICONST_5
    ISTORE 1
   L2
    LINENUMBER 3 L2
    ILOAD 1
    IINC 1 1
    ILOAD 1
    IINC 1 1
    IADD
    ISTORE 1
   L3
    LINENUMBER 4 L3
    NOP
`

Прочитали 5, увеличили на 1, прочитали 6, увеличили его на 1, сложили 5 и 6, записали туда, где было 7. Упс, не успел.
Меня, когда я впервые пробовал пописать на котлине (на дне открытых дверей в Jetbrains), очень сильно удивил приоритет elvis operator:

fun main(args: Array<String>) {
    val i: Int? = 0

    println(2*1 + 1)
    println(i ?: 0 + 1)
}

Попытайтесь угадать результат
3
0


Чё там в Scala?
getOrElse — метод

val i = Option(0)

scala> i.getOrElse(0) + 1
res0: Int = 1

При желании
Операторов в scala нет вообще, а методы, начинающиеся с букв, имеют наименьший приоритет при операторной записи, так что точно такой же выстрел в ногу возможен, просто так не пишут:

scala> i getOrElse 0 + 1
res1: Int = 0


ну в варианте скалы вы обозначили сами приоритет
это как в котлине написать println((i ?: 0) + 1)
Блеск! Отличный выстрел
Вполне возможно. Но когда я после a?.method() по аналогии написал a?:0 + 1, я несколько удивился результату.
Да, понятен сценарий применения (например throw MyException...).
И привыкнуть можно, но для меня это было способом выстрелить себе в ногу.
Самое неприятное, что нет возможности этого избежать без дополнительного уровня вложенности скобочек вокруг потенциально длинного выражения с nullable результатом.
Мне кажется, тут очевидный приоритет элвис оператора, если понимать как он работает с nullable.
По другому просто нельзя, верней можно, но это будет ну очень странно.
Final by default действительно очень спорный вопрос. Как по мне, было бы гораздо лучше если бы везде было open by default. По этому поводу на форуме kotlin, как раз идет большой холивар.
Рассуждение авторов языка на тему выбора defaults: статья
Эта тема вообще достойна отдельной статьи. Они ссылаются на спорный пункт Effective Java, который, в свою очередь, ссылается на другой спорный пункт Effective Java. При этом в других частях языка Effective Java может не выполняться.
В общем по пунктам. Согласен только с тремя: 5, 8 и 9.
Остальное работает так, как и должно. Не увидел ничего странного.
Что касается первого пункта, просто не надо так писать. Это код с неочевидным результатом на большинстве языков. От такого всегда стоит избавляться.

Для сравнения приведу паззлеры на груви. Вот там действительно не очевидно.
Повторюсь: если инкремент нельзя так использовать, зачем тогда оставлять его как expression? В питоне, например, его убрали как раз из тех соображений, что на нем можно написать что-то неочевидное.
Совершенно непонятна претензия к инкременту, он абсолютно логично работает, причем идентично Java и Groovy.
Так же странным выглядит упоминание оператора !!, совершенно непонятно как можно выстрелить себе в ногу. Максимум что тут можно ожидать, это статическая проверка на уровне IDE.
Ну например, вы проверили var i на null, а потом везде пишете i!!. В это время другой тред изменил значение i на null, и вы ловите NPE.
Если вы именно таким способом используете переменную в разных потоках, то боюсь что вы выстрелили в ногу себе давно и!!! ваша наименьшая проблема
Использование этого оператора в любом виде подразумевает, что вы будете стрелять себе в ногу (получением NPE как в примерах или просто плохим кодом, где можно было использовать smart-cast или дополнительную переменную). Опишите хороший кейс для этого оператора, где выстрелить невозможно, если вы считаете, что упоминание этого оператора излишне.
Пункт 5 (про инициализацию полей, названный почем-то "Конструктором") работает так же как в яве (где это описано в JLS).
Прошу Вас, называйте язык так, как хотели разработчики — джава, а не ява. Умоляю. Всем сердцем.
Да хоть oak, какая разница? Название происходит от Java coffee, а остров Java у нас зовётся Ява, а не Джава.
И это правда. Поэтому я и написал "как хотели разработчики", а они его называют так.
Не совсем — примитивы инициализируются сразу. А вот ссылочные поля да, по порядку, и тоже можно выстрелить.
Если это все выстрелы, то все очень даже неплохо. В scala есть один хороший способ выстрелить себе одновременно в ногу, затылок и задницу, и задеть всех, кто стоит рядом. Это implicit. И если его добавить в пункт 5, то будет просто прекрасно.
Вообще во многих случаях scala ведет себя так, что то, что написано — это совсем не то, что имеется ввиду. В лучшем случае, вы смотрите на кусок кода и не понимаете что к чему без знания всего контекста. Так уж закономерность — чем мощнее инструмент, тем легче им отрезать палец.

P.S. в 9-м пункте ключи разные:
cache.put(«IAmNull», null)
getter(«IamNull»)
Жалко кармы не хватает плюсануть… полностью согласен с автором!

п.с. вообще возникает ощущение что "последователи scala" не спят, и только и делают что ищут отрицательные комментарии в сторону своего детища, чтобы потом их безжалостно топить…
Может дело просто в том, что многие из этих комментариев просто не по существу?
Аналог implicit здесь — extension method, если вы про implicit class. Если вы про что-то другое — можно пример?
Насчет закономерности мощность/возможность выстрелить я в целом согласен, хотя приведу два контр примера — C и Haskell.
9 пункт поправил, спасибо.
В Scala есть еще implicit convertions и implicit parameters. При помощи них можно придумать много чего самострельного. Хуже то, что для этого не нужно что-то изобретать — наступить эти на грабли можно даже не специально. Если Вам нужны примеры, зайдите на StackOverflow и поищите по «scala implicit» и среди 5800 результатов посмотрите какие у людей с ним проблемы. Кроме всего прочего, такой инструмент при неумелом использовании пораждает различные bad practice. А для умелого использования не хватает четко обозначенных use cases, где использовать это необходимо или разумно.

Не понял на счет контрпримеров. C — это мощный, сложный, но слаботипизированный язык (как говорил мой препод — язык среднего уровня), утыканный граблями. Haskell — наоборот, простой язык с четкой идеей.
Насчет implicit conversions — согласен (для тех, кому лень гуглить — вот пример). Насчет разумного — более-менее адекватно выглядит implicit conversion арифметики, например Int в Long, Double в MySuperDuperComplex и т.п. Кстати, в котлине придется явно указать преобразование.

Насчет мощности — мы, наверно, по-разному понимаем ее. В моем понимании мощность близка к выразительности и набору возможностей. И при таком раскладе — С не мощный, выстрелить элементарно, Haskel — мощный, выстрелить трудно.
>> lateinit
>> lateinit var без аннотации
Вы просто не поняли, зачем нужен lateinit.
Для того, что у вас написано, действительно нужны Delegates.nonNull или nullable var.

>> Почему не стоит думать о Nullable как об Option
А почему вы решили, что стоит?..

>> внутри run… что надо использовать?
Из сигнатуры же очевидно, что this.
Хорошо, а для чего его тогда, по-вашему, надо использовать? Цитирую документацию:

Normally, properties declared as having a non-null type must be initialized in the constructor. However, fairly often this is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.

To handle this case, you can mark the property with the lateinit modifier

Почему многие подумают, что Nullable — это замена Option, я написал в начале статьи.
Сигнатуру я могу прочитать. Вот только если бы этот момент был бы очевидным, читать бы сигнатуру не пришлось.
Исключительно для того, что сказано в документации, IMHO, в первую очередь для injected-объектов. С другими кейсами я лично не сталкивался, может быть, тесты — но их я еще не писал.

IMHO, lateinit это практически костыль для ограниченного набора случаев, и не стоит его использовать, когда можно обойтись чем-то другим.
В документации прямо говорится, что это костыль, чтобы избежать лишних проверок. Вы можете некорректно сделать инъекцию, забыть вызвать setup метод — на 100% быть уверенным в том, что поле будет проинициализировано, нельзя. В статье я написал, что его вообще использовать не стоит.
>> некорректно сделать инъекцию
>> забыть вызвать setup метод
И что, это штатная ситуация, которая должна быть обработана на уровне отдельного объекта? Конечно нет, это ваша ошибка, которую вам нужно как можно скорее исправить. Значит, использование nullable, которое вы предлагаете в посте совершенно некорректно. Зачем лепить проверки на null на поле, которое в нормальном состоянии всегда проинициализировано?

Для таких случаев нужно использовать Delegates.notNull или lateinit — в зависимости от прочих условий. Они следят за выполнением контракта «доступ к полю до инициализации — исключительная ситуация». Дальше вы это исключение обрабатываете или нет, это отдельный вопрос.

Почему же тогда я назвал lateinit «практически костылем»? Потому что он дублирует на уровне языка функциональность, которая реализована в стандартной библиотеке как delegated property Delegates.notNull.
Зачем он введен? Затем, что у delegated property в общем случае нет backing field, на который может потребоваться применить аннотацию, скажем, инжектора.
То есть, lateinit это элемент совместимости совместимости с деталями платформы, которые отсутствуют в языке в явном виде. То есть, практически костыль.
От перестановки слагаемых…
Разумеется: 25 28
Удачной рыбалки.
От рид-онли nicky1038:

Что мне ещё не понравилось (это не к выстрелам в ногу относится) — что нет clone/copy методов у коллекций
А то, вроде, несколько строк только написал, а уже забомбило.
youtrack.jetbrains.com/issue/KT-11221
Sign up to leave a comment.