Котлин-сопрограммы: возможно ли без стандартной библиотеки?

Мой вопрос скорее теоретический. Я новичок в kotlin (прошел только туториал, не писал никакого реального кода).

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

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

TL; DR: Могу ли я запустить сопрограмму, используя чистый котлин, без импорта каких-либо библиотек (каким бы уродливым это ни было)?


person Alexey Nezhdanov    schedule 04.10.2019    source источник
comment
Вы пробовали пройти через kotlinx.coroutines (я предполагаю, что это то, что вы хотите заменить по какой-то причине?) и посмотреть, как это делается?   -  person Pawel    schedule 04.10.2019
comment
Да. Если вы проследите его, то получите что-то вроде SafeContinuation :: resumeWith () (для которого я не нашел источников). Однако мой вопрос не об этом. У меня вопрос: почему запуск - это функция библиотеки, а приостановка - ключевое слово? Почему такая асимметрия?   -  person Alexey Nezhdanov    schedule 04.10.2019
comment
Вы можете спросить на официальном форуме kotlin или сопрограммах github, чтобы иметь больше шансов привлечь внимание того, кто действительно принял это решение. Я предполагаю, что включение suspend в качестве стандартного ключевого слова - это всего лишь небольшой обман со стороны команды разработчиков, потому что в противном случае им пришлось бы прибегать к странным аннотациям маркеров.   -  person Pawel    schedule 04.10.2019
comment
Кажется, я нашел некоторую реализацию, которая может быть связана: coroutineCodegenUtil.kt содержит реализацию invokeDoResumeWithUnit (), которая содержит пару взаимодействий стека и invokevirtual (). Я предполагаю, что они транслируются непосредственно в соответствующие байт-коды. Если kotlin дает нам возможность напрямую генерировать байтовые коды, то меня еще больше смущает, почему приостановка - это не библиотечная функция, а ключевое слово. Но спасибо за указатель, это действительно правильный путь.   -  person Alexey Nezhdanov    schedule 04.10.2019
comment
Судя по тому, что сказано в Kotlin Coroutines Reloaded, поддержка компилятора не ограничивается ключевым словом suspend. Кажется, также есть запуск сопрограмм и приостановка функций сопрограмм. youtube.com/watch?time_continue=2440&v=3xalVUY69Ok   -  person Alexey Nezhdanov    schedule 11.10.2019


Ответы (5)


Могу ли я запустить сопрограмму, используя чистый котлин, без импорта каких-либо библиотек (каким бы уродливым это ни было)?

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

Как это можно сделать, слишком долго для ответа StackOverflow, но попробуйте вызвать метод этого класса Kotlin из Java:

class AsyncWorks {
    suspend fun print() {
        println("Hello")
    }
}

Вы увидите, что хотя метод Kotlin не имеет аргументов, в Java он требует Continuation<? super Unit>. Это то, что делает ключевое слово suspend. Он добавляет Continuation<T> в качестве последнего аргумента нашей функции.

Было бы лучше реализовать обе функции как библиотеки или обе как функции компилятора?

В идеале вы хотите, чтобы все было «функцией библиотеки», поскольку ее легче развить. Удалить ключевое слово из языка очень сложно. Теоретически можно избежать использования suspend в качестве ключевого слова. Quasar, будучи фреймворком, вместо этого использует аннотации. С другой стороны, язык программирования Go предполагает, что все функции приостанавливаются. У всех этих подходов есть свои преимущества и недостатки.
Kotlin решил проявить прагматичность и добавить ключевое слово suspend, оставив решение за разработчиками. Если вам интересна эта тема, я настоятельно рекомендую этот доклад Романа Елизарова, автора сопрограмм Kotlin, который объясняет их решения: https://www.youtube.com/watch?v=Mj5P47F6nJg

person Alexey Soshin    schedule 04.10.2019
comment
Я не думаю, что это правильно. Вы можете использовать сопрограммы и приостанавливать код без библиотеки сопрограмм. Библиотека - это просто набор хороших вспомогательных функций и других абстракций (например, потока), построенных на поддержке сопрограмм компилятора. См. Мой ответ ниже для примера. - person West_JR; 19.10.2020

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

В отличие от этого, launch - это просто обычный библиотечный код, основанный на примитиве приостановки / возобновления.

person Marko Topolnik    schedule 04.10.2019

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

Запустить сопрограмму без каких-либо библиотек можно с помощью startCoroutine < / а>. kotlin.coroutines является частью Kotlin, а не библиотекой.

person Louis Wasserman    schedule 04.10.2019

Отвечая на свой вопрос здесь.

После года Kotlin я склонен думать, что это действительно возможно. Функция языка suspend создает дополнительный класс и создает его экземпляр каждый раз, когда вызывается функция приостановки. Этот класс расширяет ContinuationImpl и сохраняет ход выполнения вашей сопрограммы - до этого момента она могла выполняться.

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

Это все еще асимметрия - ContinuationImpl живет в kotlin.coroutines.jvm.internal, поэтому компилятор предполагает, что этот пакет существует. Если кто-то действительно хочет полностью отказаться от стандартной библиотеки, ему нужно будет реализовать этот пакет, чтобы иметь возможность использовать ключевое слово suspend.

Я не эксперт по котлину, поэтому могу ошибаться.

person Alexey Nezhdanov    schedule 12.10.2020

@ Алексея Сошина не совсем правильно.

Вы можете использовать сопрограммы без библиотеки, и это довольно просто. Вот простейший пример приостановки сопрограммы, который имеет 0 зависимостей от библиотеки сопрограмм.

import kotlin.coroutines.*

fun main() {

    lateinit var context: Continuation<Unit>
    
        suspend {
            val extra="extra"
            println("before suspend $extra")
            suspendCoroutine<Unit> { context = it }
            println("after suspend $extra")
        }.startCoroutine(
            object : Continuation<Unit> {
                override val context: CoroutineContext = EmptyCoroutineContext
                // called when a coroutine ends. do nothing.
                override fun resumeWith(result: Result<Unit>) {
                    result.onFailure { ex : Throwable -> throw ex }
                }
            }
        )
    
        println("kick it")
        context.resume(Unit)
}

Это нормально работает на сайте play.kotlinlang.org.

Как видно из этого кода, любая лямбда, украшенная suspend, имеет startCourtine().

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

Компилятор делает тяжелую работу над сопрограммами, разделяя код на разные методы в каждой возможной точке приостановки. Посмотрите на java-код для этого, и вы увидите, что он разделен на оператор switch. одно дело до приостановки, другое - после.

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

person West_JR    schedule 19.10.2020
comment
Спасибо, @West_JR. Ваш ответ мне очень помог, я смог с ним продвинуться дальше! Однако вопрос был в том, возможно ли это без стандартной библиотеки. Поскольку у вас есть import, вы все еще не достигли этой цели. - person Alexey Nezhdanov; 25.11.2020
comment
Я немного запуталась. Я не использую библиотеку сопрограмм. Все эти вещи, потоки, каналы, программы запуска и т. Д. Находятся в kotlinx. *, Который я не импортирую. Я использую стандартную библиотеку ... которая в основном является средой выполнения kotlin. Я не знаю ни одного приложения kotlin любого размера, которое может работать без стандартной библиотеки. Даже если вы не импортируете его явно, все Int и String находятся в стандартной библиотеке, а также то, как запускается main (). - person West_JR; 30.11.2020
comment
Возможно, мои ожидания неверны. Я обычно представляю компилятор в качестве базового уровня. Так должно быть на всех языках - будь то Kotlin, Java, C или что-нибудь еще. Стандартная библиотека написана на языке, понятном компилятору. По соображениям производительности некоторые части могут быть написаны на более низкоуровневом языке, но это должно быть необязательно. Все остальное построено на стандартной библиотеке. Следовательно, если компилятор знает о стандартной библиотеке, это нарушение абстракции. - person Alexey Nezhdanov; 01.12.2020