Эта статья родилась по мотивам вот этой статьи в виде полу-шутки. В той статье большая часть "проблем" является либо синтетическими и крайне редко используемыми, либо притянутыми за уши из-за ожидания соответствия языка теоретической парадигме которой, по мнению автора, язык должен соответствовать. С другой стороны не упомянуты вещи, которые мне лично действительно усложняют жизнь.


Я ни в коем случае не претендую на абсолютные знания Kotlin, поэтому в статье могут быть ошибки. Я буду благодарен, если Вы в комментариях мне укажете на способы решения тех проблем, которые у меня возникают.


Убогий for


Самую могучую конструкция для реализации любых циклических действий for в Kotlin превратили в самую бесполезную вещь, реализуемую самим же Kotlin всего одной строкой.


inline fun <T> For(it : Iterator<T>, cb : (T) -> Unit) {
  while (it.hasNext()) cb(it.next())
}

fun main(a : Array<String>) {
  val list = listOf(1, 3, 4, 12)
  println("for");   for (it in list) println(it)
  println("FOR");   For(list.iterator()) { println(it) }

  val arr = arrayOf(1, 3, 4, 12)
  println("a-for"); for (it in arr) println(it)
  println("a-FOR"); For(arr.iterator()) { println(it) }

  println("r-for"); for (it in 0..10) println(it)
  println("r-FOR"); For((0..10).iterator()) { println(it) }
}

Как видно по примеру выше даже такая примитивная реализация For не просто работает абсолютно одинаково с for, но и во всех случаях кроме работы с массивом еще и абсолютна идентична ему по генерируемому коду. Дописав еще несколько строк можно даже добиться того, что писанины самодельный аналог будет требовать меньше штатного.


Вопрос: зачем было вообще вводить это ключевое слово в язык и реализовывать жалкую породию частного случая цикла? Убогий цикл и без того уже есть.


Собственно, бог бы с ним с этим недоделанным for-ом, если бы была альтернатива. Но ее нет. К сожалению, жизнь на итераторах не заканчивается, а когда приходится писать какие-то сложные циклы, то приходится жестоко страдать с убогим while-ом.


Истерично-бессмысленная война с null-абле


Может быть из-за того, что я стар, а может быть из-за того, что уже лет 25 успешно пишу на С, где (sic!) есть такая вещь как void*, я не испытываю никакого экстаза от повторения вслух шаблонных: "стрельба в ногу" и "ошибка на миллион". В результате, я просто не понимаю с чем воюют. Какая разница, когда хлопнется программа, на проверке аргументов или на их использовании?


В чем соль декларирования null-safety Kotlin-ом, если он ее даже теоретически обеспечить не может? Значение null есть в самом языке, оно есть в Java, без инфраструктуры которой Kotlin, скажем прямо, не представляет никакого интереса. Как можно защититься от того, что используется за пределами языка и никак им не контролируется? Да никак. Это не более чем модная тенденция, уродование исходных текстов и регулярный геморой.


Впрочем, я далек от того чтобы учить других как именно им жить. Если кому-то хочется какого-то самоуспокоения, то кто я такой чтобы им в этом мешать? И я бы абсолютно спокойно игнорировал ажиотаж вокруг null, если бы не регулярный геморой с ним. Вернее с той абстракцией, которой Kotlin старательно усложняем мне жизнь.


var value : Int? = null

fun F() : Int {
  if ( value != null ) return 0
  return value // Ошибка
}

Ошибка Smart cast to 'Int' is impossible, because 'value' is a mutable property that could have been changed by this time просто задалбывает. Хочется кого-то убить или что-то сломать.


Где, как и кем эта проперть может быть модифицирована между двумя строчками??!!! Соседним тредом? Откуда взялась эта абсолютно бредовая уверенность компилятора в том, что каждая буква моей программы — это элемент многопоточной конкуренции? Даже в случае написания жестокого многопоточного кода пересечения тридов случаются на очень малом объеме текста программы, но из-за репрессивной заботы компилятора о такой возможности я имею гиморрой с клинописью постоянно.


Кто придумал два восклицательных знака? Неуд! Два еще недостаточно взрывают мозг. Надо было пять. Или десять. И с обоих сторон. Так уж точно было бы понятно, где тут самый не кошерный и "небезопасный" код.


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 — это нормальное "неизвестное" состояние множества объектов, но при работе с ними постоянно приходится писать абсолютно ненужную клинопись.


Смешно же во всей этой чепухе вокруг null то, что это не работает. Я уже почти смирился с писаниной бездарной клинописи в своем коде с надеждой, что "зато когда-нибудь это спасет".


Ага, щаз.


Java


public class jHelper {
  public static jHelper jF() { return null; }
  public void M() {}
}

Kotlin


fun F() {
  val a = jHelper.jF()
  a.M()  //Упс!
}

Это замечательно компилируется без каких-либо ошибок или предупреждений, запускается и с грохотом схлопытвается cо стандартным NullPointerException т.к. тут Kotlin не проверяет ничего и нигде. И где обеща..., тьфу, декларируемая безопасность?


В общем, в сухом остатке я имею следующее:


  • регулярный гиморой с преодолением надуманных проблем в моем коде;
  • постоянные приседания с !! при работе с nullable типами в моем коде;
  • постоянный оверхед, генерируемый компилятором на проверках всех параметров функций и при установке любых значений в моем коде;
  • нулевую безопасность для любых данных пришедших извне;

Т.е. весь гиморрой только в той части, которую я знаю и контролирую, а все наружнее молча посыпется при первой же возможности. Зашибись, зачет!


Зато все красиво и по феншую.


Почему присваивание — это не выражение?


Даже if это убогое, но выражение, а присваиванию эту возможность отрезали. Почему я не могу написать так?


var value = 10

fun F() : Int {
  return value = 0 // Ошибка
}

или так:


var v1 = 1
var v2 = 1
var v3 = 1

fun F() {
  v1 = v2 = v3 = 0 // Ошибка
}

Что в этом коде криминального? Хотя, я наверное, догадаюсь. Защищаем пользователя от if (v=20)?.. Но, врядли, т.к. это просто не соберется без автоматического приведения типов, которого у Kotlin, опять-же, нет. Сдаюсь. Кто знает ответ?


Чем не угодил оператор "?:"?


За что ампутировали оператор "?:"?


Что усмотрели вредного в таких конструкциях?


value != 0 ? "Y" : "N"

С if все замечательно:


if (value != 0) "Y" else "N"

кроме полной альтернативности (где такое еще есть?) и того, что часто побочная писанина if () else места занимает больше, чем само выражение.


За что убили автоматическое приведение типов?


Да, тотальное приведение типов друг к другу — это чистое и незамутненное зло. Я обоими руками против того, чтобы плодить ребусы, к которым приводит взаимное преобразование чисел и строк. В принципе, я даже за то, чтобы различать целочисленное и плавучку. Но зачем было совсем все-то вырезать?! Почему нельзя использовать стандартные и общепринятые правила приведения типов, которые существуют в подавляющем большинстве языков?


Ну ладно, пусть даже отрезали. Привет Pascal. Но зачем в документации-то врать про «there are no implicit widening conversions for numbers»? Где оно "are no", если такое замечательно собирается?


val i = 10
val l = 12L
val f = 12.1

val l1 = i+100/l-f

Где ожидаемый хардкор?!


val l1 = i.toDouble() + 100.toDouble() / l.toDouble() - f

Т.е. авто-приведения типов нет… хотя… оно как-бы есть… но только для выражений… и еще для констант. А вот если передать в качестве параметра надо переменную или там присвоить в переменную без вычислений — тут уже ручная гребля в санях. Ведь это так принципиально, и нужно акцентировать все внимание на том, что вот из этого Int получается именно Long, а из этого Float именно Double.


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


Хотел бы я еще заикнуться про крайне желательное:


val c : SomeClass? = null

if ( c ) "not-null"
if ( !c ) "is-null"

но не буду т.к. опасаюсь за свою жизнь.


Недо-typedef


Давно просили прикрутить к Kotlin псевдонимы. Прикрутили. Я не знаю в каких случаях люди это планируют использовать но, на мой взгляд, толку от такой реализации примерно ноль. Назвали бы эту конструкцию макросом — у меня притензий бы не было, а так… обман какой-то.


Давайте разберемся в каких ситуациях вообще нужны псевдонимы в каком-нибудь языке. Я могу предположить следующее их применение:


  1. Создание альтернативного имени для существующего класса. Задача довольно бестолковая но, возможно, кому-то пригодится. С этим существующие псевдонимы справляются полностью.


  2. Создание нового типа без создания нового класса. Эту задачу существующие псевдонимы решить не способны вообще т.к. они не являются самостоятельным типом. Различить два псевдонима, отличающихся только именем невозможно.


  3. Уменьшение писанины при использовании шаблонных типов. Эта задача самая полезная и часто используемая. Существующие псевдонимы могут решить только описательную ее часть (см п.1), т.е. их можно использовать для описания типа переменных, параметров, возвращаемого значения и создать объект такого (базового) типа. Певдоним для шаблонного типа нельзя использовать для приведения или проверки типа объекта.

На практике мы имеем следующее:


typealias aI = SuperPuperClassA
typealias pSI = Pair<String,Int>
typealias pIS = Pair<Int,String>
typealias pOTHER = Pair<String,Int>
typealias aS = List<String>

class SuperPuperClassA {
  fun F() = pSI("",10)
}

fun main(a : Array<String>) {
  val a = aI()
  val i1 = a.F()
  val i2 : Pair<*,*> = a.F()
  val i3 : Any = a.F()

  //Этот код собирается и условие выполняется
  if ( i1 is pSI ) println("ok")
  if ( i1 is pOTHER ) println("ok")

  //Этот код НЕ собирается
  if ( i1 is pIS ) println("not compile")
  if ( i2 is pSI ) println("not compile")
  if ( i2 is pIS ) println("not compile")
  if ( i3 is pSI ) println("not compile")
  if ( i3 is pIS ) println("not compile")
}

Обратите внимание на то, что в обоих строках где код собирается условие выполнится. Т.к. псевдоним не является полноценным типом, то различить их невозможно. Собственно, Kotlin мог бы их различать хотя бы в случаях, как в этом примере (весь код с явными и известными типами), но, видимо, нет желания.


Код, которые не собирается, имеет одну и ту же проблему: "Cannot check for instance of erased type". Проблема в недоразвитости (попросту отсутствии) шаблонов в рантайме JVM.


Итого.


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


А, да, я говорил что псевдонимы можно описывать только глобальные, вне любого класса?


Nested and local type aliases are not supported


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


Убогие шаблоны


Шаблоны (generics) в Java вообще и в Kotlin в частности убоги и причина абсолютно одна и та же: JVM ничего не знает о шаблонах и все эти треугольные скобки в языке не более чем навесное синтаксическое украшательство.


Бог с ней с Java т.к. меня лично ее проблемы не волнуют. Меня волнует ущербность шаблонов конкретно в Kotlin, который позиционируется как другой язык, а не препроцессор для Java и, в результате, кивать на ее недостатки по меньшей мере бессмысленно.


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


Как Вам такой ребус:


/*00*/ class C<T>(val value : Any) {
/*01*/   fun F() : T {
/*02*/     try {
/*03*/       val v = value as T //Предупреждение компилятора "Unchecked cast: Any to T"
/*04*/       return v
/*05*/     } catch(ex : RuntimeException) {
/*06*/       println("Incompatible")
/*07*/       // Хак для иллюстрации того, что эксепшин будет съеден и не пойдет дальше
/*08*/       return 0 as T
/*09*/     }
/*10*/   }
/*11*/ }
/*12*/ 
/*13*/ fun fTest() {
/*14*/   val a = C<Int>( 12.789 )
/*15*/   println( "rc: ${a.F()}" )
/*16*/ 
/*17*/   val b = C<Int>( "12.123" )
/*18*/   println( "rc: ${b.F()}" )
/*19*/ }

В этом коде, в классе "С" делается попытка проверить совместим ли тип объекта с типом шаблона.


Внимание, вопрос: как отработает этот код?


Варианты ответов:


  1. Не соберется вообще
  2. Соберется, выполнится и напечатает "12", "12"
  3. Соберется, выполнится и напечатает "12", "Incompatible"
  4. Соберется, выполнится и напечатает "12.789", "12.123"
  5. Хлопнется при запуске внутри функции "C::F" (на какой строке?)
  6. Хлопнется при запуске внутри функции "fTest" (на какой строке?)

Правильный ответ

Правильный ответ: хлопнется при запуске внутри функции "fTest" на строке 18


rc: 12

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
    at jm.test.ktest.KMainKt.fT(kMain.kt:18)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

Следующий конкурс: кто может объяснить почему это происходит?


  1. Почему не упало на первом вызове, где передается Double вместо Int?
  2. Почему не отработал блок try/catch?
  3. Как ошибка кастинга с ПРАВИЛЬНЫМИ типами смогла вообще доехать до кода используещего функцию "C::F"?

Под капотом

Кратеньно, выводы делайте сами.


Вот код, который генерирует Kotlin для проверки типа внутри "C::F":


// val v = value as T

GETFIELD jm/test/ktest/C.value : Ljava/lang/Object;
CHECKCAST java/lang/Object
ASTORE 1

Если очень сильно подумать (или заранее знать что оно неработоспособно), объяснить почему именно CHECKCAST Object можно. Сложнее объяснить зачем вообще этот код генерировать т.к. он абсолютная пустышка всегда, но это вопрос уже совсем к другой части компилятора.


А вот код, который генерируется при вызове функции "C::F":


LINENUMBER 18 L6
ALOAD 1
INVOKEVIRTUAL jm/test/ktest/C.F ()Ljava/lang/Object;
CHECKCAST java/lang/Number

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


В общем, несмотря на множество синтаксичесих прелестей шаблонов Kotlin, они могут подложить очень неожиданную и толстую свинью.


Я все понимаю: шаблонов в Java нет и все подобное. Этот пункт, скорее всего, не появился бы вообще, если бы нормальную работу с шаблонами нельзя было бы реализовать в принципе никогда и нигде… Но вот у меня перед глазами яркий пример — VCL. Фирма Borland в богом забытом году умудрилась прикрутить не к чему-нибудь, а к С и Pascal настолько мощное RTTI, что альтернатив ему не существует до сих пор. А тут не машинный код, тут Java и обеспечить в ней полнофункциональное использование шаблонов в своем, Kotlin-овском коде можно. Но его нет. В результате, язык вроде бы и другой, а ситуация, из-за синтаксического разнообразия, еще хуже чем в самой Java.


Напишем аналог шаблона из ребуса на Java.


public class jTest<T> {
  Object value;

  jTest( Object v ) { value = v; }

  public T F() { return (T)value; } //Предупреждение компилятора "Unchecked cast"

  public static void Test() {
    jTest<Integer> a = new jTest<Integer>( 12.123 );
    System.out.print( "rcA: " );
    System.out.print( a.F() );

    jTest<Integer> b = new jTest<Integer>( "12.789" );
    System.out.print( "\nrcB: " );
    System.out.print( b.F() );

    System.out.print( "\n" );
  }
}

И попробуем его вызвать из Kotlin и Java.


fun fTJ_1() {
  val a = jTest<Int>( 12.123 )
  println( "rc: ${a.F()}" )

  val b = jTest<Int>( "12.789" )
  println( "rc: ${b.F()}" )
}

fun fTJ_2() {
  jTest.Test()
}

Я не буду утомлять разнообразием ребусов для всех возможных вариантов и сведу его к простейшему: как поведет себя программа, в которой:


  1. и шаблон и его использование реализовано на Kotlin;
  2. шаблон на Java, а его использование на Kotlin;
  3. и шаблон и реализацая на Java;

и какие будут результаты выполнения программы в каждом случае?


Варианты:


  • Все примеры отработают одинаково.
  • Все примеры отработают по разному.
  • Все примеры, где реализация написана на Kotlin отработают одинаково, а с Java по другому.

Правильный ответ

Правильный ответ: все три варианта поведут себя по разному


  1. Kotlin:


    rc: 12
    Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number

  2. Kotlin->Java:


    Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer

  3. Java:
    rcA: 12.123
    rcB: 12.789

Почему так а не иначе? А это будет домашнее задание.


Нет синтаксиса для описания структур


Если взять абсолютно любой сравнымый язык (хоть саму Java, хоть Scala, Groovy и множество прочих от Lua до, даже, С++) то в них во всех сделано так, чтобы было удобно описывать структуры данных в коде программы.


Kotlin — это единственный известный мне язык, где синтаксиса для описания структур данных нет вообще. Есть (грубо говоря) всего три функции: listOf, mapOf и arrayOf.


Если с массивами и спискамми синтаксис громоздок, но как-то структурируется зрительно:


  val iArr1 = arrayOf(1, 2, 3)
  val iArr2 = arrayOf( arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3) )
  val iArr3 = arrayOf(
    arrayOf(arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3)),
    arrayOf(arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3)),
    arrayOf(arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3))
    )

то с картами все значительно печальнее:


  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))))) )

Я не знаю как именно другие пользователи описывают константные данные, но лично я испытываю жестокий дискомфорт при попытке использовать что-то сложнее одномерного списка.


Понятно, что можно засунуть данные в любой подходящий формат (хотя бы тот же JSON и оттуда ее прочитать), однако...


  1. Засовывать каждый десяток строк в отдельный файл только для того, чтобы иметь возможность ими наглядно манипулировать — это как-то избыточно (хотя именно так и приходится делать).
  2. Усилия по написанию структуры кода прямо в программе и во внешнем файле, с последующим их чтением, просто несопоставимы.
  3. В случае изменения структуры данных приходится, помимо кода, править гору совершенно лишнего текста по обслуживанию его загрузки.

В общем, концепция минимализма — это круто, но аццки неудобно.


ПС: В виде отдельного гвоздя в голову я пожелаю кому-нибудь написать библиотеку для работы с матрицами. Зато научитесь понимать отличать Array<Array<Array<Array<Double>>>> и Array<Array<Array<Double>>> с первого взгляда и с любого расстояния.

Поделиться с друзьями
-->

Комментарии (145)


  1. voddan
    20.02.2017 23:57
    +4

    вещи, которые мне лично действительно усложняют жизнь.

    Правильно ли я понял что наличие на ваш взгляд бесполезного for-цикла усложняет вам жизнь? Или все это сарказм с начала и до конца?


    1. JouriM
      21.02.2017 00:10
      +6

      Усложняет, разумеется, не наличие бесполезного цикла, а отсутствие полезного.


    1. JouriM
      21.02.2017 00:15
      +3

      Усложняет, разумеется, не наличие бесполезного цикла, а отсутствие полезного.
      Фор — это очень удобная и краткая конструкция с двумя выражениями и одним условием.
      Она короткая и очень наглядная.
      Для меня лично, разворачивание его в while с отдельным описанием переменных, значительно снижает читаемость кода.


  1. AnarchyMob
    21.02.2017 00:11
    +1

    Ceylon и тот поддерживает "нормальные" generics, а Kotlin увы...


    1. JouriM
      21.02.2017 00:37
      +1

      Действительно, в нем декларируется "Ceylon's type system is fully reified at runtime" и в качестве примера приводят именно "if (is Map<String,Object> map)".
      Интересно, какой ценой они этого добились?


      1. senia
        21.02.2017 08:01
        +5

        Ценой потери совместимости с java.


        1. JouriM
          21.02.2017 08:27

          Я цейлон не изучал, но на их сайте указан таргет JVM и в описательной части приведены примеры для использвания с Java EE. Прямые их примеры использования жавы тут:
          https://github.com/ceylon/ceylon-examples-jdk
          Как это может работать без совместимости с Java?


          1. gildor
            21.02.2017 08:32

            Java != JVM
            Не уверен про случай цейлона, но думаю вы не можете без конвертации использовать типы цейлона с reifiend дженериками в Java


            1. guai
              21.02.2017 11:54

              Можно как явавские: как реализации интерфейсов, как бины. Дженерики в рантайме там где-то на аннотациях и служебных полях сидят, докопаться до них наверн всё-таки можно, но это вряд ли публичное АПИ.


    1. guai
      21.02.2017 19:26
      +1

      а почему цейлон «и тот»?
      вы так говорите, как-будто цейлон запилен школьниками после уроков, но даже там осилили нормальные дженерики
      цейлон на данный момент самый фичастый под jvm и команда там из охренительных инженеров. команда котлина пожиже будет
      а еще не стремятся порвать все шаблоны, как вон автору поста


      1. fogone
        21.02.2017 19:52
        +3

        используете цейлон в продакшене?


        1. guai
          21.02.2017 19:59

          пока нет, стремлюсь допинать коллег


  1. Beholder
    21.02.2017 00:15
    +6

    Из обычного for можно выйти по break или continue. С лямбда-блоком такого не выйдет.


    1. JouriM
      21.02.2017 01:24
      +1

      Согласен, break реализовать в коде сложно.


      1. senia
        21.02.2017 08:03
        -2

        Легко. Киньте в Break исключение без стектрейса и поймайте его в For.


        1. Prototik
          21.02.2017 10:31
          +4

          Исключение — штука тоже совсем не дешёвая, и лишний try-catch блок может убить производительность в критических местах. Ну и про то, что это дичайший костыль — я вообще молчу.


          1. Optik
            21.02.2017 10:37

            Она не дешевая только из-за сбора трейса.


          1. afanasiy_nikitin
            21.02.2017 17:29
            +2

            а можете подробнее рассказать про «недешевость» исключения? за что так минусуют предыдущего оратора?


            1. vedenin1980
              21.02.2017 17:55
              +3

              Даже try-catch блок, exception в котором никогда не бросается совсем не бесплатен, он мешает делать оптимизации во время выполнения, соответственно, он может стоит от ничего до сильного проседания производительности. Поэтому нет, исключения сильно сажают производительность не только из-за сбора трейса, но и из-за того что вместо заставляете JVM отключать многие оптимизации кода.
              В целом, использовать исключения для выхода из цикла это почти всегда плохо.


              1. alan008
                21.02.2017 19:14

                Да что вы там за низкоуровневый мега-производитеььный код пишете на Java? Задолбали уже с этой темой медленного Exception. Масса операций (запрос к БД, чтение с диска, обращение к сети) на несколько порядков медленнее генерации и ловли исключения, тем не менее все эти операции выполняются по тысяче раз в секунду. Никто не будет в здравом рассудке генерить сотни тысяч исключений в секунду, а жалкая пара сотен вообще никак не скажется на производительности.


                1. vedenin1980
                  22.02.2017 12:08

                  Да что вы там за низкоуровневый мега-производитеььный код пишете на Java

                  Приходилось писать для BigData (Hadoop, Apache Spark), HighLoad (веб сервис отвечающий на десятки и сотни тысяч запросов в секунду). Там экономия даже 10% позволяет покупать не 100 серверов, а 90. Сразу отвечаю, предлагать использовать C/С++ для этого не стоит, по многим причинам правильно настроенная Java предпочтительнее С++ и прочих относительно низкоуровневых языков (Java на самом деле достаточно производительная, если у программистов прямые руки, и к тому же очень надежна, если руки тоже не из одного места).


                  Никто не будет в здравом рассудке генерить сотни тысяч исключений в секунду, а жалкая пара сотен вообще никак не скажется на производительности.

                  Это только кажется, я видел в реальных приложениях алгоритмы вида "сделаем sql select из таблица с миллионными записей, потом в Java отфильтруем по имени и возьмем десяток записей". Причем писали их опытные программисты, проблема в том что сначала была таблица на сотню записей и так было вроде бы проще, потом записей стали миллионы, а память приложение стало есть со страшной силой и приложение отвечало медленно и печально.


                  Автор выше предлагает выходить из for-а через кидание exception'a, предположим автор реализовал это для for-а какой-нибудь функции, а потом другой программист использовал её в цикле сотню тысяч раз (не вникая в детали реализации функции) и вот у нас уже неожиданно в система появилось сотня тысяч исключений в секунду. Причем об этом мы узнаем только когда у нас система начнет "провисать".


                  Поэтому там где возможно стоит оставить исключений только для обработки реальных ошибок, не стоит забивать гвозди микроскопом. Поверьте, в Java много способов получить проблемы с производительностью даже в довольно простом приложение, а ловить потом утечки памяти и провисания производительности лишняя трата времени и сил.


                  1. alan008
                    26.02.2017 23:39
                    +2

                    предположим автор реализовал это для for-а какой-нибудь функции, а потом другой программист использовал её в цикле сотню тысяч раз

                    Т.е. все-таки проблема не в самом Exception'е как таковом (вызванном один раз), а в том, что не надо его кидать по 100 тыщ раз. Я именно об этом и говорил


    1. gildor
      21.02.2017 08:20
      -1

      Выйти можно, continue придется делать через условие или по другому построив цикл (например отфильтровав и т.п.)
      https://kotlinlang.org/docs/reference/returns.html#return-at-labels


    1. adev_one
      21.02.2017 12:00
      +3

      Вместо break можно написать вот такую конструкцию:

      (0..10).forEach one@ {
          (0..10).forEach two@ {
              if (0 == 0) {
                  return@one
              }
          }
      }
      


      1. adev_one
        21.02.2017 12:33

        Более развёрнутый вариант:

        fun foo() {
        
            fun loop() {
                (0..10).forEach one@ { i ->
                    (0..100).forEach two@ { j ->
                        if (j == 5) {
                            return@two  // continue для вложенного
                            return@one  // break для вложенного или continue для внешнего
                            return@loop // break для внешнего
                        }
                        print("$i, $j\n")
                    }
                }
            }
        
            loop()
        }
        


  1. Beholder
    21.02.2017 00:30
    +1

    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?
    }
    


    1. TargetSan
      21.02.2017 01:17
      +5

      ИМХО тут автор как раз прав — сакральный смысл таких "манипуляций" стремится к нулю. В тот же Spring таких аннотаций не понаставишь. Логичнее как раз считать всё "чужеродное" максимально "небезопасным", если не указано иное.
      Вообще странно. Год или полтора назад я читал, что в Kotlin специально решили считать все ссылки родом из Java — Nullable по умолчанию. И даже гоняли над JRE специальный верификатор, который составлял "карту аннотаций". Решили не заморачиваться, что ли?


      1. JouriM
        21.02.2017 01:34
        +2

        Наоборот.
        Специально для внешнего кода убрали проверки.
        Находил описание, когда наступил на эти грабли в каком-то парсере и начал искать почему в коде нет проверок.
        Полное содержимое кейса я не помню, но смысл был в том, что при наличии проверок ломался какой-то код.
        Сейчас нулабле-тип объявленный в Kotlin имеет тип "?", а из Java "!".


        //KOTLIN:
        fun sNULL() : String? = ""
        
        //JAVA:
          public static String F1() { return null; }
        
          val k = sNULL() // тип переменной String?
          val j = jTest.F1() // тип переменной String!

        Для первого проверки производятся, и выдаются все предупреждения, а для второго нет.


        1. TargetSan
          21.02.2017 01:46
          +4

          Тогда смысл "не-нуллабельности" вообще нулевой получается — с учётом что 95% нынешнего кода не на Kotlin. Я при работе с внешним кодом не получаю от компилятора абсолютно никакой помощи, и точно так же могу схлопотать NPE почти в любой момент. Смысл был бы как раз заставить проверять инварианты на внешнем коде или прогонять какой-то статический чекер. А так синтаксис покрасивше, не более.


          1. JouriM
            21.02.2017 01:54
            +4

            Смысла нет
            Об том и спич :)


          1. voddan
            21.02.2017 09:49
            +7

            У поддержки null типов в Котлин в том виде в котором она там есть два очень весомых "смысла":


            1) Устраняется "распространение" null-ов. Основная боль в Java в том что null может быть незаметно передан из функцию в функцию, из поля в поле, и NPE вылетит совсем не там где null возник. Для этого рекомендуется всюду ставить null-проверки, но не все это делают. Kotlin имеет null-assert-ы во всех публичных методах, и распространение null-ов предотвращается на самом раннем этапе.


            2) Null-типы позволяют декларировать nullability явно и удобно (в отличие от аннотаций). Это сильно упрощает использование кода на Kotlin.


            1. JouriM
              22.02.2017 04:29

              Чегож вы все читаете не то что написано, а то, на что знаете ответ?...


              Где написано что нулевые типы в котлин — это плохо?
              Где написано что я не понимаю откуда берется null?


              Претензии к текущей реализации 2:


              1. Ошибка компиляции, исключение из случаев safe-check, фактическая польза от которой, на практике, составляет мизерную часть тех мест в которых ее выбразывает компилятор, а во всех остальных она вызывает только раздражение.


              2. То, что все эти проверки НЕ РАБОТАЮТ с данными, пришедшими из жавы!
                А далее можете цитировать себя сами со всем обилием возможных проблем.
                Вот пример именно Ваших слов с последствиями.

                fun Dispatch( obj:GuiObject) {
                ... // тут будет проверка параметра на null
                }
              
                Dispatch( JavaCodeClass.getCurObject() )  // Тут проверки НЕ будет

              В текущей архитектуре код хлопнется на проверке парамеров внутри "Dispatch" в РАНТАЙМЕ, т.е. все танцы с "?" и "!!", которые я вынужден использовать в исходниках, оказываются совершенно бессмысленными.


      1. senia
        21.02.2017 08:12
        +3

        Решили не заморачиваться, что ли?
        Поняли, что задача в общем виде нерешаемая из-за generics. Андрей Бреслав — Kotlin: самое сложное — совместимость


    1. JouriM
      21.02.2017 01:18
      +2

      Я так понимаю, ты намекаешь на то, что никто не мешает исправить существующие классы Java и добавить в них недостающие аннотации?


      1. Beholder
        21.02.2017 10:51

        Готовые аннотации к классам JDK уже лежат в IDEA\lib\jdkAnnotations.jar. Для других библиотек в IDEA можно добавить вручную через механизм внешних аннотаций.


        1. JouriM
          22.02.2017 04:34

          Ясно.
          Ты серьезно считаешь, что работу с миллионом существующих библиотек Java я долен начинать с их реверс-инжиниринга в попытках понять как они работают и чем чревато их использование с последующей их модификацией наружними аннотациями?
          Ты сам-то так пробовл делать?
          Сколько времени остается на ту работу за которую платят зарплату?


          1. lany
            22.02.2017 05:05
            +3

            В IDEA внешнюю аннотацию можно добавить в пару кликов при вызове библиотечного метода. Не трогая те методы, которые не нужны. Если же вы не разобрались, может ли вернуть null используемый метод, то вместо работы, за которую платят зарплату, вам вскоре придётся дебажить NPE в продакшне.


            1. JouriM
              22.02.2017 05:21

              Эти "пара кликов" займут 0.0001% времени в задаче "понять куда, как и когда их надо воткнуть".
              Я подозреваю, что задачами по адаптации чужого кода, написано непойми как хз сколько лет назад, без коментариев и, возможно, даже без исходников Вы если и занимались, то не часто.
              Просто поверьте, что тупое втыкание аннотаций к чужому коду может привести к проблемам по крайней мере не менее сложным чем "дебажить нпе в продакшине"


              1. avost
                22.02.2017 11:42
                +1

                Эти "пара кликов" займут 0.0001% времени в задаче "понять куда, как и когда их надо воткнуть".

                Именно так. Пренебрежимо малые затраты. Но понять вам всё-равно придётся. Или нет? Неужели вы из тех, кто — фигак, фигак и в продакшн? Тогда, конечно, понимать не надо.


              1. am-amotion-city
                25.02.2017 10:16
                -5

                Я всегда в таких ветках хренею, как (мягко говоря) не очень умным людям с которыми вы общаетесь, все комментарии всегда переплюсовывают :)


                Собственно, зашел сказать, что вас тут если и не понимают, то не все. Воткнуть аннотацию в конкретный метод чудой библиотеки — это станет моим хитом надолго. Я, правда, для этого предложил бы прямо бимы патчить, так надежнее.


                1. Sirikid
                  25.02.2017 23:25
                  +2

                  Вот вы пишите, а в теме не разобрались, речь идет о внешних аннотациях.


                  1. am-amotion-city
                    26.02.2017 10:43
                    -4

                    Вот вы алогично инкриминируете мне неконсистентные агностичные комментарии, а лучше бы грамматику спряжения глаголов подучили.


                    Внешние аннотации волшебным образом влияют на весь мир вокруг. Иными словами, аннотацией к одной функции из сторонней библиотеки можно легко поломать другую используемую проектом библиотеку, причем так, что проблема всплывет через год в продакшене.


                    1. Sirikid
                      26.02.2017 11:16
                      +6

                      Внешние аннотации волшебным образом влияют на весь мир вокруг.

                      Разве? Я думал что они влияют только на то, как библиотека будет выглядеть из моего кода.
                      Как может быть иначе если обе библиотеки это jar-файлы?


  1. Dimezis
    21.02.2017 01:47

    Не считаю язык плохим, но в принципе, с каждым приведенным аргументом согласен, тоже раздражают эти вещи жутко.

    Мне непонятно главным образом 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.


    1. creker
      21.02.2017 01:59
      +8

      2) Почему создатели языка считают, что только компилятор должен решать как обезопасить код, а не программист?

      Потому что в современном мире компилятор должен пытаться максимально решить проблемы на стадии компиляции, а не оставлять их на рантайм. Пришли времена, когда компилятор может быть действительно умным, а не просто транслятором одного кода в другой. Поэтому сейчас так популярны статические проверки и генерация кода за счет аннотаций специальных. Где-то это может небольшое неудобство, но оно приносит очень много пользы в перспективе. И null-safety как раз пример этого. Rust так вообще кардинально пошел по этому пути — там вся модель работы с памятью на аннотациях и статических проверках


      1. Dimezis
        21.02.2017 02:42

        Я согласен, null-safety — это хорошо. Но всему должна быть мера.
        Я конкретно про вышеупомянутый пример.


    1. lany
      22.02.2017 05:12

      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 не работает. Можно слева добавлять константу? А переменную? А другое поле? А волатильное поле? Напишите спецификацию и вы поймёте, что лучше запретить вообще все повторные чтения, чем описывать, когда они безопасны.


      1. JouriM
        22.02.2017 05:29

        Можете привести определение безопасности, которому соответствует эта ошибка и пример кода, в котором она является небезопасной?
        Приведенный у вас код не имеет отношения к обсуждаемому.


        Второй вопрос: какой процент "ложных срабатываний" ошибки приемлем для комфортной работы?
        Т.е. в каком проценте случаев возникновения этой ошибки код, на котором она возникла, не будет иметь никакого отношения от той "опасности" от которой она защищает.


  1. creker
    21.02.2017 01:55
    +15

    Сразу говорю, что Kotlin в жизни не видел, но очевидно, что вы здесь с позиции «привык, мне и так нормально», когда задача языка исправить то, что продолжает жить чисто как дань совместимости с С без учета того, что конструкция может быть лишь генератором ошибок без профита. Собственно, все новые языки нынче именно этим и занялись и очень успешно. И тут без знания Kotlin видно, зачем и почему все сделано.

    Истерично-бессмысленная война с null-абле

    Проблемы на границах с другим кодом это не причина отказываться не от модной, а необходимой вещи как null safety. В примере кода явно проблемы с диагностическим сообщением, не более — если компилятор умный, то ему должно быть понятно, что если условие не выполнено, то value всегда null и нужно выдавать ошибку. Изменить условие на value == null и ошибка должна исчезнуть. Если конечно котлин достаточно умен.

    Почему присваивание — это не выражение?

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

    Чем не угодил оператор "?:"?

    Тоже самое — оно того не стоит.

    За что убили автоматическое приведение типов?

    Здесь еще более явна причина — оно того не стоит. Помнится, Страуструп сам недоволен тем, насколько простителен С++ в этом плане, хотя он даже жестче С. Совсем неудивительно, что современные языки требуют только явных приведений.

    Недо-typedef

    В данном случае речь как раз о другом названии для типа и нужно это для рефакторинга, чтобы потом эти псевдонимы типов экспортировать, а не как привыкли в С, чтобы прятать реальные типы и давать им красивые имена без всякой реальной пользы. Недоделали может быть, но фича востребованная и есть во многих современных языках в том или ином виде.


    1. areht
      21.02.2017 05:23
      +2

      > > Чем не угодил оператор "?:"?
      > Тоже самое — оно того не стоит.

      А он то какие баги вносит?


      1. gildor
        21.02.2017 08:25
        +2

        Нет, уже много раз говорилось, что тернарный оператор убрали за избыточность.
        if else может делать тоже самое в kotlin, большинство мест где тернарный оператор использовался в джаве это проверка на null и значения по умолчанию прекрасно заменяются .? и элвис оператором.


      1. voddan
        21.02.2017 09:56
        +2

        Тернарный оператор вносит нечитабельный код. В больших проектах на C или Java с завидной переодичностью встречаются ... ? .... : .... выражения на 3 строки, а то и больше. И сделать с этим ничего нельзя — в отличие от if-else, ?-: даже с подсветкой синтаксиса плохо читается.


        1. areht
          21.02.2017 11:02
          +2

          Давайте не смешивать, нечитабельный код вносят программисты.

          Как выше заметили, if-else в Kotlin — это такое же выражение, т.е., в отличии от C и Java, тут тоже можно написать выражение на 3 строчки, которое будет плохо читаться. Да и тот же ReSharper тернарный оператор в if-else рефакторит в один клик. И из 3-х строчного выражения можно мгновенно получить простыню на 2 экрана, но читабельность это, честно говоря, не сильно улучшает.

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

          Впрочем, с программистами, не заботящимися о читабельности, это не самая большая проблема (вы видели что делают фанаты дженериков, например?).


          1. voddan
            21.02.2017 13:20
            -1

            Программисты программистами, а некоторые синтаксические конструкции провоцируют писать плохой код.


            В этом смысле if-else имеет преимущество. Во первых, выражение if-else чаще разносят на отдельные строки, почему то так принято. Во вторых, if-else легче подсветить ярким синим цветом который визуально выделяется в остальном коде, в то время как подсветка ? : такого эффекта не дает.


            Но это все довольно субъективно, и как уже отмечалось, в Kotlin рассматривают возможность добавления тернарного оператора.


            1. areht
              21.02.2017 17:28

              > Программисты программистами, а некоторые синтаксические конструкции провоцируют писать плохой код.

              Именно, я предложил разделять. Перегнуть палку и написать плохо можно далеко не только тернарным, и я в нём не вижу ничего ни уникального, ни плохого.

              Звучит, как будто проблема вообще не в тернарном операторе, а в длинных выражениях. Запретите компилировать выражения, длиннее 40 символов (и применение магических чисел заодно), это решит проблему?

              > Во первых, выражение if-else чаще разносят на отдельные строки, почему то так принято.

              Ну «примите» не писать выражения в 3 строчки, будет не чаще.


      1. adev_one
        21.02.2017 12:16

        Условный тернарный, возможно, потом сделают. Его сложно реализовать из-за того, что "?" и ":" — по отдельности тоже имеют смысл.

        https://discuss.kotlinlang.org/t/ternary-operator/2116/12


        1. zagayevskiy
          21.02.2017 14:25

          Да ну, то что там приведено в пример (двоеточие отдельно используется, ну надо же) — ересь какая-то. Вполне можно в разных правилах грамматики использовать одинаковые терминалы — главное аккуратно. Нет времени досконально разбирать грамматику Котлина, но думается, решить эту "мегапроблему" можно.


  1. Sirikid
    21.02.2017 02:10
    +13

    Самую могучую конструкция для реализации любых циклических действий for в Kotlin превратили в самую бесполезную вещь, реализуемую самим же Kotlin всего одной строкой.

    У for-in своя семантика, это не for из C.


    Где, как и кем эта проперть может быть модифицирована между двумя строчками??!!! Соседним тридом?

    Да.


    Откуда взялась эта абсолютно бредовая уверенность компилятора в том, что каждая буква моей программы — это элемент многопоточной конкуренции?

    Потому что в Java все потоки работают с одной кучей и в общем случае объект не привязан к какому-то отдельному потоку.


    Даже в случае написания жестокого многопоточного кода пересечения тридов случаются на очень малом объеме текста программы, но из-за репрессивной заботы компилятора о такой возможности я имею гиморрой с клинописью постоянно.

    Поэтому вы хотите забить болт пока программа не будет падать?


    Это замечательно компилируется без каких-либо ошибок или предупреждений, запускается и с грохотом схлопытвается cо стандартным NullPointerException т.к. тут Kotlin не проверяет ничего и нигде. И где обеща..., тьфу, декларируемая безопасность?

    Вы получиили что обещано, любите стрелять в ногу — делайте это сколько угодно :^)


    постоянный оверхед, генерируемый компилятором на проверках всех параметров функций и при установке любых значений в моем коде;

    Нет, чистый Kotlin null-safe, проверки будут только на стыке Java и Kotlin.


    нулевую безопасность для любых данных пришедших извне;

    Это естественно, вы можете самостоятельно обработать полученное значение наиболее подходящим для вас способом.


    Чем не угодил оператор "?:"?

    Он не нужен если if это выражение. Кроме того ?: это один из немногих тернарных операторов, его могли убрать что бы упростить грамматику.


    С if все замечательно… кроме полной альтернативности (где такое еще есть?) и того, что часто побочная писанина if () else места занимает больше, чем само выражение.

    Ada, Scala, Haskell, F#, Standard ML, OCaml, Rust, лиспы.


    За что убили автоматическое приведение типов?

    За слабую типизацию.


    Ну ладно, пусть даже отрезали. Привет Pascal. Но зачем в документации-то врать про «there are no implicit widening conversions for numbers»? Где оно "are no", если такое замечательно собирается?
    val i = 10
    val l = 12L
    val f = 12.1
    val l1 = i+100/l-f


    Т.е. авто-приведения типов нет… хотя… оно как-бы есть… но только для выражений… и еще для констант. А вот если передать в качестве параметра надо переменную или там присвоить в переменную без вычислений — тут уже ручная гребля в санях. Ведь это так принципиально, и нужно акцентировать все внимание на том, что вот из этого Int получается именно Long, а из этого Float именно Double.

    Это перегрузка операторов, а не приведение типов.
    Формально мы работаем с объектами, а объекты неявно друг к другу не приводятся. (Примитивы в Java, кстати, тоже не приводятся, просто компилятор за вас расставляет приведения в допустимых местах.)


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

    Посмотрите в зеркало :^)


    1. JouriM
      21.02.2017 03:00
      +3

      Кратенько, по существу т.к. придирки и упражнения с схоластике не особо интересны.


      У for-in своя семантика, это не for из C.

      То что сейчас в Kotlin — это foreach, который именно в таком виде завели в каком-нить С# именно для того, чтобы обозначить его нишу.
      Мне, по сути, без разницы как оно называется.
      У меня претензия к отсутствию альтернативы нормального for. Считайте его хоть из С хоть из Java.


      Нет, чистый Kotlin null-safe, проверки будут только на стыке Java и Kotlin.

      Это неправда.
      Посмотрите генерируемый код.
      А на стыке Java\Kotlin как раз проверок не будет вообще, если на стороне Kotlin явно не указан "?"-й тип.


      Потому что в Java все потоки работают с одной кучей и в общем случае объект не привязан к какому-то отдельному потоку.

      В Java не нужно обеспечивать синхронизацию? Все операции\функции\блоки… атомарны? Компилятор как-то автоматически за меня определит границы блока синхронизации и создаст для них код?
      Если нет, то аргумент не принимается.


      ПС: Я не знаю языков где "объект привязан к какому-то потоку". Такие существуют?


      1. Sirikid
        21.02.2017 03:47
        +6

        Мне, по сути, без разницы как оно называется.
        У меня претензия к отсутствию альтернативы нормального for. Считайте его хоть из С хоть из Java.

        Тогда так и пишите — мне не нравится что выкинули сишный for.


        Стык Java и Kotlin это то место, где платформенный тип теряет свой восклицательный знак, там будет вставлена проверка, которая выбросит NPE если "с той стороны" пришел null.


        В Java не нужно обеспечивать синхронизацию? Все операции\функции\блоки… атомарны? Компилятор как-то автоматически за меня определит границы блока синхронизации и создаст для них код?
        Если нет, то аргумент не принимается.

        У нас архитектура виртуальной машины такая что нельзя реализовать smart cast для изменяемых данных в куче. Возможно я копнул слишком глубоко, smart casts запрещены для всех мутабельных свойств/переменных, хотя для локальных переменных их можно было бы реализовать.


        ПС: Я не знаю языков где "объект привязан к какому-то потоку". Такие существуют?

        Erlang, Rust.


        1. gildor
          21.02.2017 07:53
          +3

          > для локальных переменных их можно было бы реализовать.
          Но ведь smart cast и так работает для локальных переменных, в том числе мутабельных:
          http://try.kotlinlang.org/#/UserProjects/vg26o1hplih1u2c89031fvdbl/svm0e1lu4fplliqtj8c5ejague


          1. Sirikid
            21.02.2017 08:02
            +1

            Знаю, издержки хабро-комментариев. Ещё smart cast не будет работать если переменная изменяется лямбдой.


            1. lany
              22.02.2017 05:24

              Что исключительно логично!


        1. vektory79
          22.02.2017 16:04

          И надо не забывать, что в Java проверка на null с выкидыванием NPE так чудно заоптимизирована, что и не стоит по сути ничего. Если смотреть на сгенерированный машинный код из JIT то там этих проверок и нету вовсе. Всё срабатывает через Uncommon Trap и прерывание процессора.


      1. fly_style
        21.02.2017 08:52
        -5

        Вам рассказать, как заменить цикл for на цикл while, или сами додумаетесь?


      1. voddan
        21.02.2017 10:15
        +3

        То что сейчас в 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 если хотите чтобы в коде было легко разобраться


        1. avost
          22.02.2017 00:37
          -1

          Вообще я насчитал 5 случая применения С-подобного for-а, и считаю что Kotlin ни в чем не проигрывает:
          1) Перебор значений:

          Ну, у вас же тут только тривиальный случай с простейшим условием остановки и единичным шагом, и с единственной переменной. В этом случае правда лучше перебор по диапазону. Но for можно куда более хитрый написать. Да, понятно, можно всё это while'ом сделать, но громоздко и некрасиво же будет. Тем более, реализация в компиляторе сишного (явского) фора — задача банальнее не придумаешь.


    1. voddan
      21.02.2017 10:01

      Нет, чистый Kotlin null-safe, проверки будут только на стыке Java и Kotlin.

      Не совсем, Kotlin вынужден ставить проверки во всех публичных метода на случай если их позовут из Java.


      Другое дело что на JVM null-проверки не только ничего не стоят, а более того, в ряде случаев и ускоряют код, так как позволяют JIT лучше оптимизировать.


  1. SerafimArts
    21.02.2017 03:55
    +1

    Чем не угодил оператор "?:"?

    где такое еще есть?

    В 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? ...


  1. forceLain
    21.02.2017 05:02
    +5

    Откуда взялась эта абсолютно бредовая уверенность компилятора в том, что каждая буква моей программы — это элемент многопоточной конкуренции?


    Дело не в многопоточности, а в том, что value — это не поле, а property. Когда вы вызываете if ( value != null ) или return value, вы обращаетесь не к полю value, а к геттеру, которого в вашем случае не видно, но он может появиться в наследнике класса. А так как геттер — обычная функция, он может вернуть как null так и не null, даже будучи вызваным 2 раза подряд.


    1. forceLain
      21.02.2017 05:40
      +2

      то с картами все значительно печальнее:


      Можно вот так:

      mapOf(
                      "1" to 1,
                      "2" to mapOf(
                              "2.1" to 21,
                              "2.2" to 22
                      ),
                      "3" to mapOf(
                              "3.1" to 31
                      )
              )
      


    1. JouriM
      21.02.2017 06:24

      Это объяснение уже начинает иметь какой-то смысл кроме абстракций, спасибо.
      Есть какие-то подтверждения этого?


      1. forceLain
        21.02.2017 06:44
        +2

        То, что Вы хотите сделать, это:

            fun F() : Int {
                val result = value
                if ( result == null ) return 0
                return result
            }
        


        Или проще:

            fun F() : Int {
                return value ?: 0
            }
        

        В первом примере result уже не property, а локальная переменная(константа), значение которой мы проверяем напрямую, без геттера.


        1. JouriM
          21.02.2017 07:24

          Я знаю несколько способов обойти описанную проблему, к описанному выше еще можно добавить "?.let", но вопрос был не об этом.
          Есть ли какие-то подтверждения того, что запрет на smart cast растет именно из "потенциальной неодинаковости getter-а", а не из-за многопоточности?
          Этот пример говорит об обратном:


          class A {
            private var value : Int? = 0
          
            fun F() : Int {
              if ( value == null ) return 0
              return value
            }
          }

          Ошибка точно та же, гетеров нет. То же и с "@JvmField".


          1. Sirikid
            21.02.2017 07:48

            1. JouriM
              21.02.2017 08:35
              -1

              В ссылках на то, что не имеет отношения к тексту есть какой-то смысл?
              Какой?


              1. Sirikid
                21.02.2017 11:36
                +3

                В данном случае вот этот:


                Classes in Kotlin cannot have fields.


                1. JouriM
                  22.02.2017 03:23
                  -3

                  Это только слова, к тому же без уточнения "non-private" еще и неправильные (и это без учета JvmField).
                  Во всех случаях кроме наличия обоих get\set в классе будет поле.
                  Я не помню, но вроде и с protected-ом будет protected поле, которое будет использоваться напрямую в родном классе и наследниках.


                  Еще раз: зачем приводить бесполезные ссылки?
                  Поверьте, я читал 99% документации, благо ее не вагон.
                  Фактическое поведение компилятора сложнее 3х строчек описания


          1. FirExpl
            22.02.2017 04:45

            Оговорюсь сразу, на Котлине не пишу, но скорее всего вы не можете обратиться к полю value минуя геттер/сеттер от слова никак. Только в кастомном геттере/сеттере разве что.


  1. ZnW
    21.02.2017 06:20
    +1

    «Котлин не изучал, но никак не удержусь полить его тонной ховна, кажется, это модно».
    А может стоит всё-таки сначала поизучать язык? Ах, хотя нет, зачем.

    1) Банально запоролись уже на втором аргументе про null-safety. Ну и то, что кто-то привык к си — вообще не аргумент и кричать про бесполезность null-safety немного стрёмно. Как минимум это дополнительный инструмент построения более стабильной и прозрачной архитектуры.

    image
    И вышеприведенный код отлично компилируется и работает.
    Другой вопрос в приведении типа к 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) А если вот так?
    image
    то с картами все значительно печальнее:

    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 пожаловались.


    1. JouriM
      21.02.2017 06:51
      +1

      Меня просто поржает: с чего такая агрессивность-то?
      Наследие "кг\ам" или просто день не задался?
      Если я чем умудрился обидеть, то прошу прощения.


      В комменте, практически в каждом пункте текст, который вызван простым непониманием вопроса в статье.
      Не проблема, я не заставляю этот текст читать, тем более что, видимо, это противно.
      Это взаимно, так что на этом прощаюсь


      1. ZnW
        21.02.2017 07:00
        +7

        Эх, пофлеймить уже нельзя.
        Признаюсь — конкретно подгорело со стиля написания статьи, слишком много утвердительного тона и громких фраз, которые не очень коррелируют с реальностью.
        Если бы текст статьи был более «вопросительный» и мягкий, я бы и не отписался наверное не вспылил.

        Я понимаю, что статья написана после первых часов опыта работы с Kotlin и тут в основном задаются вопросы к сообществу, но эта форма подачи…


  1. asmrnv777
    21.02.2017 06:23
    -7

    После многих лет разработки под Android изучил Swift и в какой-то мере разработку под iOS на нем — без особых сложностей. Все это время присматривался к котлину. И решился на днях переписать маленький Android-проект с джавы на котлин. Через пару часов у меня нервы сдали и я забил.
    Вот вроде он на свифт похож очень, но что-то с ним не так, мне лично синтаксис и некоторые особенности (вроде синтаксических костылей вместо static методов и полей) совсем не понравились.

    А еще я не понимаю, почему каждый разработчик языка старается придумать что-то уникальное в синтаксисе, никак не влияющее ни на что, кроме создания доп. путаницы. Как я понимаю, Kotlin развивается с оглядкой на Swift, очень уж они похожи, но в одном языке fun, а в другом func. И куча таких мелких различий, которые при работе над двумя проектами на разных, но очень похожих языках неплохо ломают мозг.


    1. JouriM
      21.02.2017 06:36
      +3

      За пару часов язык в принципе освоить невозможно и, соответственно, делать о нем какие-то выводы сложно.


      Свифт, наверное, хорош (когда возникла необходимость писать под иос я, взглянув на синтаксис objective-C сказал что буду это делать только за очень отдельные деньги, и вопрос с тех пор не поднимался, жаль тогда не было свифта) но, к сожалению, за пределами одной платформы его не существует.
      Если же возникает задача для андроида, то, на мой взгляд, Kotlin — это наилучший выбор.
      Уж в сравнении с явой-то точно.


      1. asmrnv777
        21.02.2017 06:44
        +1

        За пару часов язык в принципе освоить невозможно и, соответственно, делать о нем какие-то выводы сложно.

        Само собой, освоить не получится. Но от свифта у меня были совсем другие впечатления за первые пару часов. Однако, это лишь мое субъективное мнение.

        когда возникла необходимость писать под иос я, взглянув на синтаксис objective-C сказал что буду это делать только за очень отдельные деньги, и вопрос с тех пор не поднимался, жаль тогда не было свифта

        +1, я года 4 собирался освоить iOS, каждый раз офигевал от синтаксиса Obj-C и бросал это дело.

        Если же возникает задача для андроида, то, на мой взгляд, Kotlin — это наилучший выбор.

        Вот не уверен — язык очень молодой, меняться же наверное будет. Тот же свифт уже вовсю в третьей версии, и каждый раз переход между мажорными версиями — это боль и жжение в области таза из-за того, что половину языка меняют :(


        1. JouriM
          21.02.2017 07:09
          +1

          Да, скорее всего, будут что-то менять.
          Я лично на это даже надеюсь :)


          Пока я каких-то проблем не испытывал.
          Вот сегодня (вчера) прилетела обнова языка и в ней оказались ампутированными битовые операции для типов Short и Byte. Пришлось править кусок либы и я уже хотел по этому поводу расстроиться, но потом посмотрел на реализацию и на Java и, в общем-то, причина этого действия стала ясна. Меня "убедили" в адекватности этого действия.
          Пока это было максимальное, на что я нарывался.


          Да и, на мой взгляд, движуха в языке не является особой проблемой.
          В любой развивающейся системе будут изменения — этого не избежать.
          Я лично пришел к следующему рецепту: при обновлении смотрю на ньюсы и, если количество решенных проблем или интересных фич перевешивает трудозатраты на адаптацию, то перехожу; если нет — сижу на старой версии.
          Это я по опыту изменений, которые приходилось вносить с принципиальным перелопачиванием многих сотен кб сорсов. В Kotlin-же я пока не видел ничего даже отданно схожего по трудозатратам.


          Я лично выбрал этот язык с прицелом именно на андроид т.к. остальные альтернативы мне показались значительно хуже либо синтаксически и стилистически (например Scala), или принципиально (например, нетипизированный Groovy).
          На мой взгляд, именно Kotlin максимально близок к Swift, но, возможно Вам лучше подойдет что-то другое т.к. иногда лучше перейти на что-то совсем другое, чем "очень похожее, но совсем не такое в мелочах" — будет меньше ошибок и расстройств.


          1. asmrnv777
            21.02.2017 07:18

            Для андроида, на самом-то деле, вариантов не очень много. Джава да котлин. Котлин, как я полагаю, рано или поздно станет официальным языком для ведра, заменив джаву. По крайней мере, это было бы логично.
            Java сейчас официальный язык.
            Остальное — уже как-то совсем не тру. Понятно, что все эти языки компилятся в байт-код джавы и на выходе получаем примерно одно и то же, но как-то неправильно это, прямо как кроссплатформенные аппы на вебвью делать :) К тому же, у скалы и груви, насколько я помню, предназначение другое.

            Меня-то в принципе и джава почти устраивает. По сравнению с Xcode + Swift, Android Studio + Java хотя бы адски не лагает на топовом железе. Я все на старенький макбук грешил, а оно точно так же умудряется лагать на i7-7700K с 32 гигами DDR4-2400… Причем, говорят, Obj-C не лагает, лаги именно в автокомплите свифта :)

            В любом случае, я бы не стал крупные проекты делать на молодых языках. Это все очень круто и ощущения отличные, но потом язык кардинально меняется со следующим релизом, и становится очень больно :)


            1. JouriM
              21.02.2017 07:43
              +2

              Для андроида нет никакой разницы в используемом языке, лишь бы он мог вызывать Java, так что писать можно абсолютно на чем угодно, а уж на любых JVM языках и подавно.
              Если говорить о языках JVM, то разница будет только в настройке процесса компилятции и рюшках, доступных в среде.
              Kotlin в обоих случаях не доставляет абсолютно никаких проблем, но как дела обстоят у других я не интересовался, возможно тоже без проблем.


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


              Насчет крупных проектов — это философский вопрос, на самом деле.
              К примеру я, не так уж и давно, перестал писать вполне себе крупный проект на языке С, который собирался таким зверем как Watcom 8.0, про который многие уже и не знают, наверное.
              На работе народ вполне себе непрерывно пишет на Borland C 3.0.
              Собственно, в самой старости языка нет никакой проблемы с точки зрения поддержки проекта, если функционал этого проекта состоялся.


              1. asmrnv777
                21.02.2017 07:48

                Насчет смены жавы — я просто не верю.
                Причина проста: никто не будет переписывать существующий код

                Сложно сказать, что там у гугла на уме — но Kotlin ведь полностью совместим с джавой. Достаточно просто рантайм принести на девайс, дабы не тащить его в каждой апк-шке.

                Apple ведь точно этим сейчас и занимается — меняет Obj-C на Swift. И даже частично компоненты переписывает — говорят, в нынешней макоси как минимум Dock уже на свифте написан :)


                1. JouriM
                  21.02.2017 08:16
                  -8

                  Любой JVM язык полностью совместим с жавой. По определению :)
                  Вообще, ассемблер JVM крайне примитивен и, если бы в него, в свое время, конструктивно не были бы воткнуты всякие "хаки" для ускорения, то для него можно было бы собрать вобще любой язык, с любыми произвольными возможностями. Т.к. основной задачей любого JVM языка является совсем не поддержка этого ассемблера, а обеспечение точного и однозначного вызова java-языкХ-java, то все они абсолютно одинаково с ней "совместимы": генерируют точно такие же классы, неотличимые в среде выполнения от сгенерированных жавой.


                  С "доложить рантайм" понятно, но стимула делать это у гугла я пока не вижу. С апле ситуация другая совсем. У них крошечный процент рынка мобильной техники, несравнимо более высокий порог входа для разработчика, стоимость самой разработки и нулевой интерес аппартных производителей к их платформе. Swift — это вынужденный шаг по популяризации свой платформы при сохранении закнутого статус-кво. Насколько удачный сказать сложно, но без него апле бы попросту захирел и в мобильной области, как уже случилось с настольной.


                  1. asmrnv777
                    21.02.2017 08:22

                    С «доложить рантайм» понятно, но стимула делать это у гугла я пока не вижу.

                    В принципе, да, тут тяжеловато с точки зрения обратной совместимости — Apple-то свои девайсы очень долго обновляет, и им не проблема принести новые либы. А с Android-вендорами все совсем не так однозначно. Разве что нести рантайм в апк для старых версий ОС, а для новых не нести… Но это куча геморроя.

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

                    Да вроде как за последние 4 года у macOS доля аж на 2.5 процента выросла (до 9.6%), что очень немало для их ценовой категории. Так что пока живее всех живых :)


                    1. JouriM
                      21.02.2017 08:42

                      Мы же вроде про мобилки, а там iOS. МacOS это "гуй на линуксе" или я все перепутал?
                      Впрочем, бог с ними.
                      Любые рассуждения на тему могут\будут\почему все равно голимые домыслы, которые еще и к теме статьи отношения не имеют :)


                  1. gildor
                    21.02.2017 08:30
                    +1

                    > Любой JVM язык полностью совместим с жавой
                    Это не правда. Java != JVM.
                    К тому же не забывайте что совместимость работает (или не работает) в обе стороны, из Java вы можете использовать любой Kotlin код, что не правда для многих других JVM языков, порой очень сложно или невозможно вызвать код написаный на другом JVM языке.
                    Поддерживать полную двухстороннюю совместимость с Java это совсем не легкая задача


                    1. JouriM
                      21.02.2017 08:38

                      Т.е. существуют языки, в которых невозможно устаномить listener для класса на Java?
                      Если это так, то спасибо, не догадывался.
                      А какая ниша применения у такого рода языков?


                      1. grossws
                        21.02.2017 10:26
                        +2

                        Обычно требуется ограничить себя в некоторых фичах, чтобы сделать Java-совместимое API или callback. Например, многие Scala-библиотеки имеют нативное API и дополнительно Java API, частично пересекающееся с нативным.


                        1. JouriM
                          22.02.2017 04:49
                          -2

                          Получается, что то что я написал выше: java-языкХ-java — это вполне правильно.
                          Языков JVM, которые бы не позволяли вызвать ява-код и быть вызванными из него не существует.
                          А то что типы могут потеряться и и тому подобное — это уже не принципиальные особенности т.к. они логически обоснованы разными языками.


                      1. mayorovp
                        21.02.2017 10:26
                        +2

                        Нет, 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 сделали что-то подобное...


                        1. grossws
                          21.02.2017 11:01

                          К примеру, 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


                          1. JouriM
                            22.02.2017 03:31

                            Из scala вызов такого метода с тремя аргументами аргументами типа String становится ошибкой компиляции

                            Во!
                            В Kotlin точно так же.
                            Я пытался написать плагин к JavaDOC и вызвать явовский парсер из Kotlin не смог вообще. Именно из-за обилия перегруженных функций с одинаковыми типами параметров (куча форм вызова main с передачей ей строковых параметров).


                            1. gildor
                              22.02.2017 04:25

                              Так почему не смогли? Он разве не точно так же из Java вызывается?


                      1. gildor
                        21.02.2017 11:32
                        +1

                        > невозможно устаномить listener
                        Там огромное количество эдж кейсов, вроде листенер то установить вы сможете, а вот результирующее значение будет с non-reified generic или еще какими то ограничеями из-за того, что это Java объект, а не объект вашего JVM языка.
                        Если интересно можете посмотреть какие ограничения на фичи накладывает Scala или Closure при вызове из Java и наоборот.


        1. gildor
          21.02.2017 08:14
          +4

          > Вот не уверен — язык очень молодой, меняться же наверное будет.
          У 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, тут еще большой вопрос кто у кого что подсмотрел


        1. xxxTy3uKxxx
          21.02.2017 11:20

          del


    1. Sirikid
      21.02.2017 07:16
      +2

      Как я понимаю, Kotlin развивается с оглядкой на Swift, очень уж они похожи, но в одном языке fun, а в другом func.

      Нет, они развивались параллельно. Мне кажется это общая тенденция современных ЯП — использовать ключевые слова для объявления функций и переменных, как в Pascal, и фигурные скобки для блоков кода, как в C.


      1. JouriM
        21.02.2017 08:01
        +2

        Описание в обратной нотации, т.е. "имя-тип" диктуется не огладкой на Pascal или тенденциями, а простым требованием синтаксиса там, где описние типа опционально. В противном случае текст программы читается (и возможно компилируется) неоднозначно.
        Фигурные же скобки — это просто всего один из трех возможных на клавиатуре вариантов самой краткой записи начала и конца. Квадратные как-то "сроднились" с индексацией, вот и получается что выбрать производителю просто не из чего. Кому хочется больше альтернативщины, те используют круглые, кому что попроще — фигурные :)


        1. guai
          21.02.2017 19:07
          -4

          совершенно не похоже, чтобы имя-тип были чем-то продиктованы, кроме желания выделиться. груви, цейлон и другие языки — как-то обошлись без выворачивания деклараций наизнанку
          просто кто-то сильно любит скалу, наверное :)
          ладно бы еще каст был в том же синтаксисе — так нет, для каста есть отдельный as


      1. grossws
        21.02.2017 10:30
        +1

        использовать ключевые слова для объявления функций и переменных, как в Pascal

        Почему не сказать сразу "как в ML" (истоки и того, и другого лежат в 70х)? То же ключевое слово fun было в Standard ML, как минимум.


        1. areht
          21.02.2017 13:12

          > Почему не сказать сразу «как в ML»

          Напомнило, на какой-то конференции MS Баллмер отвечал на вопросы из зала. Слышно было не очень.

          — Будет ли развитие у Windown Home Server?
          — (прислушиваясь) «Windows what?»
          — Exactly.


  1. abreslav
    21.02.2017 09:26
    +15

    Я понимаю, что писать возмущенные тексты приятно :) Не могу сказать того же о чтении таких текстов. Постараюсь по существу ответить на ту часть, которая не покрывается документацией.


    Про тернарный оператор (?:): многим не нравится, мы посмотрим, что можно сделать.
    Про литералы для коллекций: тоже сделаем.
    Объявления новых типов, а не алиасов тоже сделаем.


    Неявные преобразований числовых типов у нас действительно нет. Есть перегрузки для арифметических операций. Мы думаем над тем, как сделать поменьше явных приведений типов, но с этим пока есть некоторые сложности.


    C++ для JVM не сделаем. У нас другие задачи :)


    1. vsb
      21.02.2017 15:39
      +6

      Пожалуйста учитывайте, что многим нравится if-else в текущем виде и не нравится тернарный оператор. То же про отсутствие C-шного for-цикла. Мне Kotlin нравится в том числе тем, что в него не насовали всего, чтобы всем угодить, а постарались по мере возможности обойтись меньшим числом конструкций. Это я не к тому, что не надо ничего добавлять, конечно.


      1. avost
        22.02.2017 01:42
        -4

        Пожалуйста учитывайте, что многим нравится if-else в текущем виде

        Так его убирать и не предлагают, вроде бы.


        и не нравится тернарный оператор

        (удивлённо) — так и не используйте, раз не нравится.


        То же про отсутствие C-шного for-цикла.

        То же про отсутствие C-шного for-цикла. :) ну, правда, ни кто же не заставляет.


        не насовали всего, чтобы всем угодить,

        Это имело бы смысл, если бы речь шла о какой-то языковой экзотике. Но тут-то вещи настолько традиционные, что их отсутствие ничего, кроме недоумения не вызывает. Особенно, отсутствие тернарного оператора при наличии "элвиса".


    1. JouriM
      22.02.2017 03:58
      -2

      Огромное спасибо за отклик.
      Рад что одиозное название не отпугнуло от чтения
      В данном случае, как в аббревиатуре RTFM, ругательная часть просто является обозначением, а не моим отношением :)


      К сожалению, мой английский заточен на общение и кино, а написание довольно абстракных технических вопросов и пояснений мало того что отнимает огромное количество времени, так еще и, скорее всего, вызывает недоумение, поэтому многие вещи, которые хотелось бы иметь или о которых хотелось бы спросить я уточнить на форумах Kotlin не могу.


      Пользуясь случаем хотелось бы обратить внимание на такие возможности:


      1. Добавить компилятору ключи командной строки, которые бы могли влиять на его поведение. В настоящее время ключей практически нет, но их использовани могло бы решить некоторые частные вопросы. Например ключ для отключения механизма null-safety был бы часто полезен.
      2. Во многих языках есть удобная фича: возможность указать ключи (или настройки компиятора) прямо в исходных текстах. Например в С для этого существуют pragma. Использование таких конструкций позволяет локально менять настройки и действует на весь модуль что в яве аннотациями не сделать, насколько я понимаю.

      В результате этих двух пунктов можно было бы вынести какой-то код в отдельное место и указать для него другие правила (например отключить тот же null-safety, который при работе с каким-нить свингом просто мозг выносит т.к. превращает исходник в забор из "!").


      1. gildor
        22.02.2017 04:31

        ключ для отключения механизма 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


        1. JouriM
          22.02.2017 05:00
          -3

          Поняли правильно, но я там дальше написал использование.
          Это не то.


          1. Это глобальный ключ.
            Я не хочу отключать проверки во всем проекте, без возможности указать его действие для модуля он для меня бесполезен.
            Можно указать раздельные ключи индивидуально для каждого файла в IntelliJ?


          2. Насколько я понял этот ключ делает не то что я хочу.
            Он убирает оверхед проверок, а мне нужно другое: а) отключить исключение из smart-cast для var и б) временно выключить ошибки преобразования, т.е. временно интерпретировать типы не как "?", а как "!", что позволит мне оперировать nullable без огорода "!!" в коде.


      1. abreslav
        22.02.2017 13:37
        +2

        Пользуясь случаем хотелось бы обратить внимание на такие возможности:

        Вводить такие ключи и прагмы — это крайняя мера, мы пока попробуем без них. (Аннотации на файл в Котлине есть, но их для таких целей мы использовать пока не хотим.)


        В данном случае, как в аббревиатуре RTFM, ругательная часть просто является обозначением, а не моим отношением :)

        Неприятнее всего было, конечно, не название, а то отношение к нам, которое выражено в тексте.


  1. vanxant
    21.02.2017 09:52
    -6

    Серьезно? Они предлагают для массивов arrayOf вместо нормального [...]? Даже в пыхе это лет пять как прикрутили.


  1. gildor
    21.02.2017 11:41
    +1

    Зато научитесь понимать отличать 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>>>


    1. mayorovp
      21.02.2017 12:26
      +2

      Э… вообще-то у матрицы ранг всегда 2 и она обязана быть выровненной. А то, что вы привели, даже как назвать-то и не знаю...


      1. Lamaster
        21.02.2017 13:52

        Рваный массив


        1. mayorovp
          21.02.2017 13:55

          Нет, рваный массив — синоним разреженного, а не невыровненного массивы 4го ранга. Но, в любом случае, проблема не в том, как описать это на русском — а в том чтобы придумать имя типа на английском :)


      1. gildor
        21.02.2017 18:07

        Окей-окей, согласен название не удачное и тем более применения, я просто сослался на пример который привел автор статьи, что если у вас действительно такая проблема, то typealias на которые так же были в статьи критикуемы, прекрасно могут с этим помочь


        1. fogone
          22.02.2017 08:18

          Может так?

          Matrix3d, Matrix4d
          


          1. mayorovp
            22.02.2017 08:41

            Увы, но название "Matrix3d" уже зарезервировано под квадратную матрицу 4x4… :)


            1. fogone
              22.02.2017 08:57

              сколько я видел, квадратные матрицы всегда называются просто MatrixN, потому что постфикс `d` обозначает измерение. В такой нотации матрица 4х4 должна называться Matrix4 и это звучит вполне логично. Можно посмотреть например на opengl


              1. mayorovp
                22.02.2017 09:01

                google://matrix3d


                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 описывает проективное преобразование трехмерного объекта.


                1. fogone
                  22.02.2017 11:20

                  осталось понять, почему матрицу трансформации не назвали матрицей трансформации, а назвали matrix3d :-)


                  1. mayorovp
                    22.02.2017 11:25

                    Потому что слово "трансформация" уже используется в имени свойства. Зачем дублировать?


                    А вот отличать проективное преобразование в пространстве от преобразования на плоскости — надо!


                    1. fogone
                      22.02.2017 14:04

                      ну, да, в контексте другой функции, для простоты, чтобы различить от такой же на плоскости… это всё не про «правильное» название


          1. rraderio
            22.02.2017 11:33

            Array3D, Array4D


      1. avost
        22.02.2017 14:21
        +1

        Вообще-то, ранг матрицы не обязан равняться двум. То есть может, конечно, если в матрице два линейно-независимых столбца (строки), но это совершенно не обязательное условие существования матрицы.


        1. mayorovp
          22.02.2017 14:35

          Уточнение: когда массив — матрица, его ранг всегда 2. Ранг массива и ранг матрицы — это разные понятия...


          1. avost
            22.02.2017 16:15
            +1

            Так немного лучше, хотя "ранг массива" в русском языке называется размерностью массива :)


  1. guai
    21.02.2017 11:46
    -10

    Довелось пописать на котлине, и тоже сложилось впечатление, что всё, что в нем есть полезного, было еще в xtend — вывод типов, лямды (ну они сейчас и в яве есть). А всё, чем он отличается от xtend'а — надергали хз откуда, им ML наверное, который мало кто в глаза видел.
    Тоже наткнулся на отсутствие тернарного оператора, особенно странно, что его укороченная версия — элвис — есть.
    Зачем перенесли типы в конец деклараций? По-моему только хуже стало. Есть дефолтные значения у параметров, функции получаются длинные — в итоге возвращаемый тип где-то за экраном оказывается. А мне в первую очередь интересен именно он.
    Сокращение function как fun — WAT?
    Убогие строки. В груви, откуда надергали многое, они гораздо более удобные.
    Нет синтаксиса для коллекций.
    Почему выкинули статические методы и поля — хз. Какие такие баги это должно предотвращать?
    В концепцию кообъекта для класса еще даже не пытался въехать, но есть сомнения в ее целесообразности. Принцип наименьшего удивления уже идет лесом.
    Все классы финальные по дефолту. Куча либ, тот не спринг, делают свои обертки поверх моих классов через наследование. Приходится руками натыкивать везде open class. И это типа пресловутый компактный синтаксис?
    А более всего напрягает, что чуваки из джетбрэйнса на подобные замечания реагируют в духе, мы тут самые умные, котлин уже идеален, менять ничего не будем.


    1. guai
      21.02.2017 15:17
      -3

      Минусующие уже могут начинать писать словами, что из вышеприведенных тезисов им не нравится. Готов обсудить каждый пункт.


  1. leventov
    21.02.2017 12:00
    +5

    null-абле

    Это слово раздвинуло мое сознание. Почему не "nullable"?


    тред, трид

    Поток


    while-ом

    Просто "while", слово на латинице в русском тексте не склоняется.


  1. Serg_de_Adelantado
    22.02.2017 04:02

    Не претендую даже на звание профана в Kotlin, но, на мой дилетантский взгляд, вот на этом участке кода:

    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
    


    1. JouriM
      22.02.2017 04:09
      -1

      Это всего лишь кратенький пример иллюстрация.
      Обойти в нем использование "!!" можно несколькими способами.
      Но в реальной жизни, к сожалению, не все они удобны или применимы.
      К примеру, при работе с интерфейсом swing в одной функции может происходить общение с кучей элементов интерфейса, каждый из которых nullable и с ним производится всего 1-2 действия.
      Ни оборачивать каждый элемент в лямбду, ни заводить локальную переменную для них не имеет никакого смысла — это просто хуже читаетя и занимает больше места.
      Приходится городить кучи "!!" в местах, которые гарантированно не null при использовании.
      Это достает, вплоть до написание глобальной аннотации к классу.


      1. gildor
        22.02.2017 04:41

        Не уверен про ваш случай с Swing, но как я понял это близко к ситуации у нас в Android при работе с UI и вьюхами. Мы убираем nullability через lazy проперти, так же вы можете использовать lateinit


        1. JouriM
          22.02.2017 05:11

          Да, аналогично, но в андроиде ее практически нет.


          Во-первых, у котлина есть автоматическое создание доступа к элементам описанным в лайауте (т.е. создавать переменные в классе вообще не нужно).


          Во-вторых, в андроиде есть дизайнер.
          В свифте дизайнер(ы) напоминают "жалкое подобие левой руки" и, как правило, весь инерфейс создается в рантайме полностью вручную.


          В-третих, из-за наличия дизайнера, в коде под андроид вы оперируете УЖЕ созданным интерфейсом и, соответственно, в активити\фрейме… можно использовать любой способ доступа к элементам (в том числе вручную lazy, если вас устраивает безумный оверхед на классах для каждой анонимной функции). При ручном создании интерфейса этим "лейзам" просто неоткуда брать компонент, поэтому используются всякие аналоги вида "firstAssign", но их не всегда удобно использовать.


          1. gildor
            27.02.2017 13:44

            Ну про дизайнер вы зря, за 5 лет в Android никогда не пользовался визуальным дизайнером кроме как превью.
            А почему не подходит lateinit?


    1. fogone
      22.02.2017 08:32

      Не претендую даже на звание профана… но, на мой дилетантский взгляд
      профан, насколько можно верить словарю ушакова — это значит «Человек, совершенно несведущий в чем-нибудь», однако дилетант — это " Занимающийся [чем-либо] как любитель (не профессионально)". Получается, что дилетант несколько более сведущ в предмете, чем профан. Сори за занудство, просто мне самому бывает очень неприятно, когда я понимал какое-то слово неправильно и употреблял, а мне про это даже никто не сказал.


  1. rraderio
    22.02.2017 04:44
    -1

    ПС: В виде отдельного гвоздя в голову я пожелаю кому-нибудь написать библиотеку для работы с матрицами. Зато научитесь понимать отличать Array<Array<Array<Array>>> и Array<Array<Array>> с первого взгляда и с любого расстояния.


    typealias Matrix4D<Double> = Array<Array<Array<Array<Double>>>>
    typealias Matrix3D<Double> = Array<Array<Array<Double>>>
    


  1. prostovovan
    23.02.2017 02:27
    -1

    Не то, что бы я критикую Kotlin, но положение, когда его создатели находятся все время в процессе понимания, что надо в язык добавить, а что убавить — не является обнадеживающим для его перспектив. А если еще и обратная совместимость для такого молодого и не претендующего на громкую славу ЯП будет постоянно ломаться, то это вообще.
    В разработке ЯП важен баланс инженерного (частного) и академического (системного) подходов. Kotlin повело в инженерную крайность.


    1. voddan
      23.02.2017 03:10

      А если еще и обратная совместимость <...> будет постоянно ломаться

      Надеюсь тема про совместимость взялась не из довольно тухлого срача в 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


      1. prostovovan
        25.02.2017 02:30
        +1

        Спасибо, пропустил. Будем надеяться, что у них получится!