не нужно было ничего возвращать?
Я предположу, что для Джавы вы можете просто сделать перегрузку setOnRequest(Runnable) (на стороне джавы вроде так и не завезли хороший аналог () - > Unit среди функциональных интерфейсов, поэтому там Runnable) или какой-то свой fun interface сделать, если не хочется брать Runnable
Вот, кстати, если сделать fun interface, то оно и в Kotlin и в Java хорошо будет выглядеть.
Ну тут больше было просто интересно. Примерно разобрался в причинах и механизме. Понял как и что именно гуглить. Почитаю сам дальше. Всем спасибо :)
Ну общая идея скорее в том, что () -> Unit будет чем-то вроде KFunction0<Unit>. Unit это generic параметр, поэтому у нас нету лёгкой возможности сказать, что в этой ситуации Unit это просто void (собственно в Джаве самой по себе такая же проблема, из-за чего есть куча функциональных интерфейсов вроде Supplier, Consumer, BiConsumer, Function, BiFunction и тд). Как выше сказано, можно обойти с помощью более удобного метода для Джавы, либо с помощью fun interface, где просто будет возвращаемый тип у функции Unit (поскольку это не generic, то сможет понять, что это можно в void выставить)
Похоже, это в объяснение в точку. Теперь я склоняюсь, что делать DSL на основе «fun interface» это «best practice»: https://t.me/kotlin_jvm/29912 Ну, понятно, что в Gradle есть Action<…> к которому прикручен «sam-with-receiver». Но во всех остальных случаях fun interface звучит интересно. Было бы интересно послушать «почему делать DSL на основе fun interface это плохая практика».
Ну например потому что не всем нужно чтобы это выглядело адекватно из джавы. А вообще для DSL, кажется достаточно ровно одного fun interface (собственно Action с ресивером, написанный на котлине).
из котлина оно тоже выглядит хорошо
С этим не спорю, но в котлине при отсутствии готового fun interface проще написать T.() -> Unit
Да, всё так. Понятно, что «если Java API неважно», то в fun interface смысла немного. А пока для меня звучит, что fun interface это и несложно, и Java будет неплохо выглядеть. Попробую действительно ли так получится.
Имеет, если наследовать. В JS по-моему до сих пор наследовать от функции нельзя.
Попробовал, для DSL работает вот такое, и обязательно -Xjvm-default=all (иначе в JVM lambda не работают и весь смысл теряется) fun interface Action<T>: (T) -> Unit { fun T.execute() override operator fun invoke(receiver: T) { receiver.execute() } } При таком Action работает и DSL в Kotlin, и вызовы в Java без return UNIT.Instance. Но есть. минус: стектрейсы получаются раза в 2 длиннее, чем при обычном T.() -> Unit DSL. Проблема в том, что fun invoke получае Вот стектрейс на простом DSL: at com.acme.test.realtime.ExampleTest$TestMapping$init$2$1$1$1$1.invoke(ExampleTest.kt:102) at com.acme.test.realtime.ExampleTest$TestMapping$init$2$1$1$1$1.invoke(ExampleTest.kt:44) at com.acme.mapping.scopes.PlainTablesScope.table(PlainTablesScope.kt:18) at com.acme.test.realtime.ExampleTest$TestMapping$init$2$1$1$1.invoke(ExampleTest.kt:44) at com.acme.test.realtime.ExampleTest$TestMapping$init$2$1$1$1.invoke(ExampleTest.kt:43) at com.acme.mapping.scopes.InstanceMappingScope.tables(InstanceMappingScope.kt:23) at com.acme.test.realtime.ExampleTest$TestMapping$init$2$1$1.invoke(ExampleTest.kt:43) at com.acme.test.realtime.ExampleTest$TestMapping$init$2$1$1.invoke(ExampleTest.kt:42) at com.acme.mapping.scopes.RdblConfigFileScope.instance(RdblConfigFileScope.kt:31) А вот стектрейс на Action<T> DSL: at com.acme.test.realtime.ExampleTest$TestMapping.init$lambda-8$lambda-7$lambda-6$lambda-5$lambda-4(ExampleTest.kt:102) at com.acme.Action.invoke(Action.kt:7) at com.acme.Action.invoke(Action.kt:3) at com.acme.mapping.scopes.PlainTablesScope.table(PlainTablesScope.kt:19) at com.acme.test.realtime.ExampleTest$TestMapping.init$lambda-8$lambda-7$lambda-6$lambda-5(ExampleTest.kt:44) at com.acme.Action.invoke(Action.kt:7) at com.acme.Action.invoke(Action.kt:3) at com.acme.mapping.scopes.InstanceMappingScope.tables(InstanceMappingScope.kt:24) at com.acme.test.realtime.ExampleTest$TestMapping.init$lambda-8$lambda-7$lambda-6(ExampleTest.kt:43) at com.acme.Action.invoke(Action.kt:7) at com.acme.Action.invoke(Action.kt:3) at com.acme.mapping.scopes.RdblConfigFileScope$sam$com_acme_Action$0.execute(RdblConfigFileScope.kt) at com.acme.Action.invoke(Action.kt:7) at com.acme.Action.invoke(Action.kt:3) at com.acme.mapping.scopes.RdblConfigFileScope.instance(RdblConfigFileScope.kt:32) — Если добавить @Suppress("NOTHING_TO_INLINE") inline fun<T> Action<T>.invokeInline(receiver: T) { receiver.execute() } И заменить .apply(configure) на .also { configure.invokeInline(it) }, то из стектрейсов пропадают почти все Action.invoke.
@noraltavir ^^ вот по-моему, разумный случай для применения inline не ради пефоманса, а коротких стектрейсов для
Стоит ли так заморачиваться ради коротких стек-трейсов?
Продолжаем историю. Нашлась ситуация, когда отдельный fun interface гораздо хуже, чем лямбда: ломается non-local return. И это печально настолько, что я подумываю выпилить этот хитроумный fun interface Action<T> И снова в копилку свидетелей inline для @noraltavir: inline fun позволяет делать non-local return, что для DSL вроде как полезно. —- Иными словами, если есть несколько уровней вложенности, то уже так просто не сделаешь return из глубины. С другой стороны, и в Java тоже невозможно так вернуть управление. Вот и получается дилемма: либо делать на простых T.()->Unit, и тогда страдает Java из-за постоянной необходимости return Unit.INSTANCE, либо на Action<T>, но тогда страдает Kotlin из-за return.
Полезность non-local return зависит от dsl, мне кажется
У меня вот такое в тесте возникло: override fun MappingState.isApplicable(): Boolean { mapping.run { metadata { // <— Это Action<T> if (attributes.isEmpty() || objectTypes.isEmpty()) { return@isApplicable false // <— вот тут return не работает. } } Ну и некая печаль, что у меня вложенность может быть больше, и подобный non-local может быть интереснее из более глубоких уровней. задумался над тем, чтобы добавить class ControlFlowException(val name: String, val result: Any?): Throwable(name) Заодно можно будет делать non-local в java коде 🙂
Тут не очень понятно, почему вместо metadata { } нельзя сделать metadata.run { }
Ну и у metadata {} receiver другой, чтобы можно было extension’ы извне навешивать (см https://t.me/KotlinMoscow/10447 ) Ну и .run на каждом уровне это печаль. И в целом у меня гораздо большая вложенность, и на каждом уровне потенциально интересно делать non-local return. Например: «если в таблице нет колонки NAME, то return false и вообще больше ничего не делаем». configFiles { // <— Action configFile(testName) { // <— Action odb { tables { // <— Action devices = table("R01_DEVICES") { // <— Action realtime() +devicesType if ("NAME" !in columns.names) { return // non-local } } }
А сделать двойное апи ? Или проблемы с резолвом ?
Делать отдельно API под Java — я не согласен. Если уж на то пошло, то я уж лучше не буду ничего делать, и напишу на T.() -> Unit. Но мысль-то была, что fun interface это несильно геморный вариант написания для Kotlin, и при этом получаеся красиво в Java. Вот эта красивость ломается на non-local. Следующий мой подход будет такой: добавлю default метод в Action, который будет называться nonLocalReturn (ну или как там), и кидать он будет ControlFlowException. А в DSL на каждом уровне я буду ловить это исключение, сравнивать с именем текущего метода, и, если наше, то останавливаться, а, если не наше, то пробрасывать дальше. Вроде как тогда и в Java и в Kotlin можно будет делать non-local return. Опять же, вроде, сама фича non-local далеко не всегда нужна (вон, в Gradle мне как-то не доводилось использовать), и для таких редких случав вполне можно и кастомный метод позвать.
Обсуждают сегодня