Каковы особенности продолжений, на которые опирается Раку (до)?

Тема ограниченных продолжений почти не обсуждалась среди энтузиастов языков программирования в 1990-х и 2000-х годах. В последнее время он снова стал одним из основных в дискуссиях о языках программирования.

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

Дословное цитирование (с поправкой на форматирование) из онлайн-сообщения [1], написанного лицом, руководившим работой по добавлению продолжений в JVM:

  • Асимметричный: когда продолжение приостанавливается или завершается, выполнение возвращается вызывающему (из Continuation.run()). Симметричные продолжения не имеют понятия вызывающего. Когда они уступают, они должны указать другое продолжение для передачи выполнения. Ни симметричные, ни асимметричные продолжения не могут быть сильнее друг друга, и каждое из них может использоваться для моделирования другого.

  • С накоплением: продолжение можно приостановить на любой глубине в стеке вызовов, а не в той же подпрограмме, где начинается разделенный контекст, когда продолжение не имеет стека (как в случае C #). Т.е. у продолжения есть свой стек, а не только один фрейм подпрограммы. Стекированные продолжения более эффективны, чем бесстековые.

  • С разделителями: продолжение фиксирует контекст выполнения, который начинается с определенного вызова (в нашем случае, тело определенного запускаемого объекта), а не все состояние выполнения вплоть до main(). Разграниченные продолжения строго более эффективны, чем неограниченные (http://okmij.org/ftp/continuations/undelimited.html), последний считается практически бесполезным (http://okmij.org/ftp/continuations/against-callcc.html).

  • Несколько подсказок: продолжения могут быть вложенными, и в любом месте стека вызовов любое из включающих продолжений может быть приостановлено. Это похоже на вложение блоков try / catch и выдачу исключения определенного типа, которое раскручивает стек до ближайшего улова , который его обрабатывает, а не только до ближайшего улова. Примером вложенных продолжений может быть использование Python-подобного генератора внутри виртуального потока. Код генератора может выполнять блокирующий вызов ввода-вывода, который приостанавливает продолжение включающего потока, а не только генератор: https://youtu.be/9vupFNsND6o?t=2188

  • Одноразовый / без повторного входа: каждый раз, когда мы продолжаем приостановленное продолжение, его состояние изменяется, и мы не можем продолжать его из одного и того же состояния приостановки несколько раз (т. е. мы не можем вернуться во времени). . Это отличается от реентерабельных продолжений, где каждый раз, когда мы приостанавливаем их, возвращается новый неизменяемый объект продолжения, который представляет конкретную точку приостановки. Т.е. продолжение - это единичный момент времени, и каждый раз, когда мы продолжаем его, мы возвращаемся в это состояние. Реентерабельные продолжения строго более мощные, чем невозвратные; то есть они могут делать вещи, которые совершенно невозможны с помощью всего лишь однократного продолжения.

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


Продолжения Aiui не отображаются напрямую в Raku, поэтому, возможно, правильным ответом, связанным с Raku (в отличие от Rakudo), будет отсутствие продолжений. Но мне это непонятно, поэтому в дальнейшем, в котором я описываю то, что, как я надеюсь, может быть в ответе, если мне очень повезет, я сделаю вид, что имеет смысл говорить о них в контексте обоих Раку. и Ракудо как два разных мира.

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

  • разработка языка на 100 лет, текущая базовая семантическая [выполнение?] Модель Raku требует, как минимум, одноразовых продолжений с разделением на несколько запросов без стека. .

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

  • Rakudo реализует требуемую в настоящее время семантику продолжения.

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

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

(Или что-то вроде этого.)

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

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

PS. Спасибо @Larry, который понимал вещи достаточно глубоко, чтобы знать, что продолжения должны быть частью картины; Стефану О'Риру за его вклад, включая начальные реализации того, что я считаю одноразовым продолжением с разделителями и несколькими подсказками; и jnthn за воплощение мечты в реальность.

Сноски

1 Ведется работа по внедрению продолжений в качестве первоклассной конструкции для JVM. Ключевой движущей силой этих усилий является Рон Пресслер. Вышеизложенное основано на сообщении, которое он написал в ноябре .


person raiph    schedule 09.07.2020    source источник


Ответы (1)


Ракудо использует продолжения как стратегию реализации двух функций:

  • _1 _ / _ 2_ - для реализации ленивых итераторов
  • Делаем await в пуле потоков неблокирующим

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

  • С накоплением - да, потому что нам нужно иметь возможность выполнять take или await на любой глубине в стеке вызовов относительно gather или рабочего цикла рабочего пула потоков. Например, вы можете написать алгоритм рекурсивного обхода графа внутри gather, а затем take каждого обнаруженного узла. Для await это основная разница между await и await Raku, как видно на многих других языках: вам не нужно проводить рефакторинг на всем протяжении стека вызовов.
  • С разделителями - да. Операция сброса продолжения устанавливает тег (или приглашение), и когда мы выполняем операцию управления продолжением, мы разрезаем стек по этому разделителю. Я не могу представить, как бы вы реализовали задействованные функции Raku без их разделения.
  • Множественные запросы - да, это необходимо, потому что вы можете выполнять итерацию одного источника данных, предоставленного gather внутри реализации другого gather, или выполнять await внутри gather.
  • Асимметричный - после выполнения продолжения выполнение продолжается после reset инструкции. В случае await мы идем и находим другую задачу в очереди рабочих задач, а в случае take мы возвращаемся к pull-one методу итератора и можем вернуть взятое значение. Я думаю, что этот подход хорошо подходит для языка, где только несколько функций используют продолжения.
  • Одноразовый / без повторного входа - да, и, по крайней мере, в MoarVM безопасность памяти среды выполнения зависит от этого свойства. Это обеспечивается атомарной операцией сравнения и обмена, поэтому, если два потока будут участвовать в гонке для вызова продолжения, только один сможет добиться успеха. Никакие функции Raku не нуждаются в дополнительной сложности, которую предполагают повторные продолжения.
  • Клонируемый - нет, потому что это не нужно для функций Raku. Теоретически это не так уж и ужасно для реализации в MoarVM, говоря «да, мы можем это сделать», но я подозреваю, что это поднимает много вопросов, например, насколько глубоко должно быть клонировано. Если вы просто клонируете все записи вызовов и тому подобное, вы все равно будете совместно использовать Scalar контейнеров, Array и т. Д. Между клонами.

Насколько я понимаю - хотя я слежу за этим издалека - продолжения JVM по крайней мере частично нацелены на то же пространство дизайна, в котором находится механизм Raku await, и поэтому я был бы удивлен, если бы они в конечном итоге не предоставили то, что Raku потребности. Это явно упростило бы компиляцию кода Raku в JVM (в настоящее время он выполняет глобальное преобразование CPS, как и генерацию кода, что, как ни странно, оказалось проще, чем я ожидал), и почти наверняка он будет работать намного лучше, потому что требуется преобразование вероятно, скрывает довольно много вещей с точки зрения JIT-компилятора.

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

person Jonathan Worthington    schedule 09.07.2020
comment
Спасибо! Я думаю, что вы говорите, что продолжения Раку должны быть асимметричными, но я не уверен на 100%. Кроме того, afaik весь механизм исключений (особенно .resume после предупреждений и т. Д.) Полагается на эти продолжения, верно? Итак, gather / take и await были лишь двумя примерами из нескольких / многих, верно? - person raiph; 09.07.2020
comment
@raiph Как отмечалось в вашем исходном посте, ни симметричные, ни асимметричные продолжения не являются более мощными, чем друг друга, и в этом случае их здесь не должно быть. Раньше я не рассматривал симметричный подход, хотя я действительно не вижу, какие преимущества он может предложить в контексте реализации Raku (я могу видеть это, если ваш верхний уровень, возможно, представляет собой цикл событий ...) - person Jonathan Worthington; 09.07.2020
comment
Ах, и две упомянутые мною особенности - это действительно полный список вещей, которые можно использовать для продолжения. :-) Исключения запускаются на вершине стека, и мы разворачиваем после выполнения блока CATCH, поэтому возобновляемое исключение - это не что иное, как невыполнение раскрутки, и поэтому не требует продолжения. - person Jonathan Worthington; 09.07.2020
comment
И я тоже :-) - person Elizabeth Mattijsen; 09.07.2020