Search
Write a publication
Pull to refresh
15
0
Send message

Всё так. Единственное, с чем не соглашусь - это

корутины это про то, где надо высоко конкурентный код писать

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

Ну и статья в целом, о том что Project Loom не ограничивается виртуальными потоками: с помощью не публичного API можно реализовать свои корутины, отличающиеся от Котлиновских

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

конкретно с synchronized, да пока Loom работает плохо, но c другими примитивами синхронизации(ReentrantLock, Semaphore и т.д.) виртуальные потоки работать умеют.

Для конкретно моих Loom-корутин добавить аналог kotlinx.coroutines.sync.Mutex не сложно, но да нужно будет либо полностью переходить на свои примитивы синхронизации, либо держать в голове, можем ли мы воспользоваться в данном куске кода стандартными средствами синхронизации или нет.
Это, пожалуй, основной аргумент в пользу раскраски кода

Но можно сделать раскраску кода обязательной и для Loom-корутин, воспользовавшись аннотациями и дополнительными линтерами, которые будут проверять раскраску во время сборки проекта.

Про synchronized конечно верно.
Но его и в Kotlin-корутинах нельзя использовать. Это общая проблема блокировок в асинхронном коде, существующая как и в Kotlin- , так в Loom-корутинах

Но, справедливости ради, Kotlin-корутины не обязывают использовать только их для эффективного IO.
Решение можно комбинировать: если `Dispatchers.IO` (диспатчер, в котором принято запускать блокирующий код в Kotlin) запускать на виртуальных потоках, то можно автоматически получить неблокирующие IO вызовы.
Другое дело, что Loom-корутинам в принципе не нужно раскрашивать код и их проще отлаживать

Вижу 2 основных минуса перед Kotlin-корутинами:

  • Kotlin-корутины хорошо оптимизированы и на некоторых моих тестах выполняются быстрее Loom-корутин.

  • Kotlin-корутины - универсальное кроссплатформенное решение, не требующее поддержки со стороны рантайма. Что важно, учитывая популярность Kotlin на Android и таргетинг на разные платформы. Loom-корутины же требуют JDK, причем одной из самых новых версий.

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

  • API для плагинов компилятора пока не стабилизорован

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

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

Это обеспечивается за счет использования MethodHandle API. Просто, грубо говоря, при вызове метода заглушки я ему передаю массив MethodHandle[] которые нужно вызвать.

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

upd:
Если вопрос в том, как я определяю стек вызова во время "пробуждения" - в Kotlin stdlib есть функция для этого.

? Баги/вопросы/предложения/замечания можете оставлять в Github issue, Gitter или в личном сообщении мне)

Зачем классы-заглушки?

Классы-заглушки нужны для эмуляции стека вызова. Например, рассмотрим 3 функции, которые по очереди друг друга вызывают:

suspend fun fun1() {
    fun2()
    delay(10)
}

suspend fun fun2() {
    fun3()
    delay(10)
}

suspend fun fun3() {
    delay(10)
    throw Exception()
}

Мы вызваем fun1, после этого вызываются fun2 и fun3 и корутина "засыпает".


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

C моей библиотекой происходит по-другому: генерируется класс-заглушка, содержащий методы fun1 и fun2, fun1 вызывает fun2, а fun2 "будит" корутину. В результате стек вызова содержит все 3 метода. И стектрейс исключения содержит их все.

Сколько их генериться? 

Столько же, сколько содержится классов с suspend-функциями.

Когда?

Для класса A генерируется заглушка во время первого "пробуждения" корутины, содержащей в стеке методы класса A. После этого заглушка кешируется и повторные "пробуждения" не приводят к генерации заглушки.

Можно же, наверное, создать руками любой стектрейс, не?

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

про C# я вообще молчу, мне в силу ряда причин нужен язык на JVM.
И я скорее про пример из habr.com/ru/company/funcorp/blog/558412/#comment_23070268 — сами же сказали, что создадите дублирующую переменную (однобуквенную или нет — не столь важно).
ок. Если вам нравится придумывать названия переменных для каждой ветки, Java, C# — ваш выбор.
А мне нравится smart cast — отличная идея, которая сохраняет строгую типизацию в языке и позволяет не плодить лишних переменных.
Если често — ужас, не зная синтаксиса C#, сложно что-то понять.
Там логика «вызвали метод, если вернулся нулл то ретурн нулл сразу, иначе вот у нас есть переменная с каким-то значением». Вопрос — зачем мне мусорное значение которое вернула TryGetInitWithValue(type) в скоупе?

Если я правильно вас понял — вы хотите вызвать функцию и если ее значение нужного типа, что-то с ней сделать и не выносить переменную в основной скоуп. Ок, в Kotlin это делается так:
(calcValue() as? RequiredType)?.also { value ->
    //в этом скопе (и только в нем) есть value типа RequiredType
}

Хороший пример просто, что вот никаких 90% с проверкой существующей переменной (когда у вас есть выбор) нет. Когда выбора нет — тогда другое дело, конечно, тогда я думаю процент даже не 90 а 100 :)

А как бы вы реализовали такую функцию без создания доп. переменной?
fun addValue(value: Any) {
    if (value is ListenableValue) {
        value.onAdd(this) // onAdd есть у типа ListenableValue
    }
    set.add(value)
}
ну хорошо, для вычисляемых значений Java ввела хороший синтаксис проверки типа с объявлением новой переменной.
Но на моей практике 90% случаев проверки типа происходит на существующей переменной, в этом случае в Java я обязан объявить новую переменную отличающуюся только типом, что выглядет убого (опять же на мой взгляд).
Пункт 1. легко решается просто указанием JvmName для функции. В вашем примере будет:
fun toString(a: Any?): String? {
    return a?.toString()
}

@JvmName("toStringNotNull")
fun toString(a: Any): String {
    return a.toString()
}


про п. 2 согласен, lateinit — костыль, который вставили для поддержки существующих Java библиотек. Но даже в вашем примере, я бы не стал писать lateinit, а просто инициализировал Id нулем, т.к. нет смысла помечать lateinit примитивные поля.

не нравится let, есть простое как дверь решение: разнести объявление переменной и проверку типа

val value = calcValue()
if (values is String) {
    processString(value)
}
Не нужно никакой диверсии. В мире Java-бекенда вы будете использовать JDBC — это Java API для БД. Далее вы, как разработчик, сделали select, проверили, что искомый столбец not null и отметили поле в вашем DOA-объекте not-nullable.
А через месяц другой разработчик сделал alter на таблице, убрал not-null и стал писать строки с null.
Ровно такая же система null-safety и в Kotlin. Люди, которые её ругают, делают это по 2ум причинам:
  1. В Java нет такого null-safety. И никто не мешает в Java записать в non-null поле этот самый null. Учитывая, что большинство используемых библиотек написано на Java, этот аргумент имеет место быть.
  2. Есть способы обойти null-safety через двойной восклицательный знак и lateinit.
ну да, или так
1. Такого нет. (И слава богу).
2. В Java 14, как уже отмечали, прямо как и в С#
if (getUser() instanceof User user) {
    //...
}

Насчет того, что для вычисляемых значений синтаксис чуть красивее получается в Java 14 — не спорю.
1

Information

Rating
Does not participate
Location
Израиль
Registered
Activity