Реализация поддержки «Генератор» на пользовательском языке - PullRequest
3 голосов
/ 15 апреля 2010

У меня есть немного фетиша для языкового дизайна, и в настоящее время я играю с моим собственным языком хобби. (http://rogeralsing.com/2010/04/14/playing-with-plastic/)

Одна вещь, которая действительно вызывает у меня кровотечение, это "генераторы" и ключевое слово "yield". Я знаю, что C # использует преобразование AST для преобразования методов перечислителя в машины состояний.

Но как это работает на других языках? Есть ли способ получить поддержку генератора в языке без преобразования AST? например Используют ли такие языки, как Python или Ruby, преобразования AST, чтобы решить эту проблему?

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

1 Ответ

5 голосов
/ 15 апреля 2010

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

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

Наиболее известным из них, вероятно, является GOTO. С помощью GOTO вы можете создать любую другую конструкцию потока управления: IF-THEN-ELSE, WHILE, FOR, REPEAT-UNTIL, FOREACH, исключения, потоки, вызовы подпрограмм, вызовы методов , вызовы функций и так далее, а также сопрограммы и генераторы.

Почти все процессоры поддерживают GOTO (хотя в процессорах они обычно называют его jmp). Фактически, во многих процессорах GOTO - это конструкция потока управления only , хотя сегодня собственная поддержка по крайней мере вызовов подпрограмм (call) и, возможно, некоторая примитивная форма обработки исключений и / или примитив параллелизма (Сравнение и замена) обычно также встроены.

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

C имеет своего рода «дрянную» форму продолжений (setjmp и longjmp), которые гораздо менее мощны и менее просты в использовании, чем «настоящие» продолжения, но они достаточно мощны для реализации генераторов (и фактически может использоваться для реализации полных продолжений).

На платформе Unix setcontext может использоваться как более мощная и более высокоуровневая альтернатива setjmp / longjmp.

Другая конструкция потока управления, которая хорошо известна, но, вероятно, не приходит на ум в качестве низкоуровневой сборки субстрата. другие конструкции потока сверху являются исключениями. Есть статья, в которой показано, что исключения могут быть более мощными, чем продолжения, что делает исключения по существу эквивалентными GOTO и, следовательно, универсально мощными. И, действительно, исключения иногда используются в качестве универсальных конструкций потока управления: проект Microsoft Volta, который скомпилировал байт-код .NET в JavaScript, использовал исключения JavaScript для реализации потоков и генераторов .NET.

Не универсальный, но, вероятно, достаточно мощный для реализации генераторов - это просто оптимизация хвостового вызова. (Хотя я могу ошибаться. У меня, к сожалению, нет доказательств.) Я думаю, вы можете преобразовать генератор в набор взаимно рекурсивных функций. Я знаю, что конечные автоматы могут быть реализованы с использованием хвостовых вызовов, поэтому я вполне уверен, что генераторы тоже могут, поскольку, в конце концов, C # реализует генераторы как конечные автоматы. (Я думаю, что это работает особенно хорошо вместе с ленивой оценкой.)

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

Итак, как же делают другие реализации генераторов?

У Lua нет генераторов как таковых, но у него полно асимметричных сопрограмм. Основная реализация C использует setjmp / longjmp для их реализации.

В Ruby также нет генераторов как таковых, но есть Enumerator с, которые можно использовать в качестве генераторов. Enumerator s не являются частью языка, они являются функцией библиотеки. MRI реализует Enumerator с использованием продолжений, которые, в свою очередь, реализуются с использованием setjmp / longjmp. YARV реализует Enumerator s, используя Fiber s (именно так Ruby произносит "сопрограммы"), а эти реализуются с использованием setjmp / longjmp. Я полагаю, что в настоящее время JRuby реализует Enumerator с использованием потоков, но они хотят переключиться на что-то лучшее, как только JVM получит более совершенные конструкции потока управления.

В Python есть генераторы, которые на самом деле являются более или менее полноценными сопрограммами. CPython реализует их, используя setjmp / longjmp.

...