Re[2]: NUDA
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.04.11 16:49
Оценка:
Здравствуйте, adinetz, Вы писали:

A>Итак, постараюсь уточнить и конкретизировать пожелания:


A>1) Работа с кодом одной функции из другой функции


Функции принципиально типизируются в неопределенном порядке. Более того под управлением IDE типизация может быть неоднократной. Так что попытки лезть из одной функции в другую принципиально опасны.

Лучше поступать по другому. Есть как минимум два варианта:
1. Вручную типизировать функции в макро-атрибуте работающем на стадии WithTypedMembers. Пример этого подхода можно наблюдать здесь. Поясню код комментариями:
      def convertMethod(name : string, meth : MethodBuilder, isDependent = true) : PExpr
      {
        #region Type method body
        // Запоминаем нетипизированное тело метода (т.е. его PExpr)
        def body = meth.Body;
        def oldEmitDebug = typer.Manager.Options.EmitDebug; 
        typer.Manager.Options.EmitDebug = false; // выключаем генерацию отладочной информации чтобы упростить анализ
        def methodTyper = Typer(tb, null, meth, meth.Env); // создаем тайпер который может типзиировать тело метода
        methodTyper.RunFullTyping(); // запускает типизацию тела метода (вручную)
        typer.Manager.Options.EmitDebug = oldEmitDebug; // восстанавливает настройку генерации отладочной информации
        #endregion

        // Обрабатывает body как нам нужно. В body к этому моменту находится PExpr 
        // у которого (и у все подвыражений) в свойстве TypedObject находится ссылка 
        // типизированно выражение (TExpr, TParametr и т.п.).

        // Восстанавливаем тело метода в нетипизированное выражение.
        // Здесь можно поместить новое тело (измененное). Важно только чтобы в исходном теле не было ошибок.
        meth.Body = body;


A>Где это можне понадобиться — встраивание (inline) функций.


Локальные функции и так автоматом инлайнятся при соблюдении некоторых условий. Условия следующие:
1. Функция должна быть объявлена в том же блоке кода где она используется. В 2.0 это ограничение постараемся устранить.
2. Функция не должна использоваться как первоклассное значение.
3. Функция должна вызваться ровно один раз. В принципе это ограничение и можно устранить введением некой пометки функции.


A>
A>inline f(i: int) : int {
A>  ... 
A>}

A>g() : void {
A>  // ... 
A>  a = f(i); // требуется заинлайнить функцию f
A>  // ... 
A>}
A>


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

В Nemerle 2.0 постараемся ввести специальную стадию на которой будет не сложно делать подобные преобразования.

A>В функции g() нужно заинлайнить вызов функции f(), для этого в момент вызова макроса (или другого механизма, при помощи которого выполняется инлайн), требуется иметь код (в виде PExpr) функции f. Этот код извлекается при помощи макроса-атрибута inline, применяемого к f(). И если сначала работает этот макрос, а потом делается инлайн функции f() в функции g() — всё хорошо. Если наоборот — насколько я понимаю, сейчас нет возможности явно дёрнуть этот макрос. Насколько я понимаю, это можно добавить — надо разрешить одновременную трансляцию нескольких функций + флажок, оттранслирована ли уже функция.


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

A>Кстати, может трансляция одной функции (как f) несколько раз. Например, для функции указано, что у неё на этапе компиляции генерируются разные версии для константных значений какого-либо параметра. Тогда новую версию надо генерировать всякий раз, когда встречается новое значение константного параметра (понимаю, катится в сторону C++). Или ещё пример: функция может быть использована на ГПУ, и требуется сгенерировать для неё OpenCL-код, но только в том случае, когда она используется в какой-то другой функции, для которой уже генерируется OpenCL-код. Сейчас это решается требованием пометить все такие функции макросом nucode, но ведь мы хотим упросить жизнь программистам и не перегружать их лишними аннотациями ?


Все это возможно. Платой будет замедление компиляции в два раза. Как я уже говорил выше, в макро-атрибует нужно стипизировать тела методов запомнив предварительно нетипизированные тела, проанализировать их и сгенерировать новые тела. При этом можно и новые версии (специализированные) сгенерировать.

A>2) Навешивание атрибутов на объекты языка (выражения, члены типов, переменные)


A>Простейший способ это сделать руками — завести глобальный словарь вида (объект, имя атрибута) -> (значение), сейчас так и делаю. Какие с этим возникают проблемы:

A>- как в такой системе прикреплять атрибуты к локальным переменным? Это основная проблема. Объект Name() однозначно переменную не идентифицирует, объект LocalValue() не сохраняется, если типизация выполняется дважды. А сейчас она как раз выполняется дважды, один раз вручную, один раз — ожиданием, пока будут протипизированы все выражения
A>- как наследовать атрибуты выражения, если оно подаётся на вход макросу, а на выход подаётся другое? Вот это проблема не техническая, скорее всего, здесь нет простого решения
A>- наконец, возникает слишком много объектов, для которых можно получать атрибуты. Т.е. имя может быть локальной переменной, членом типа, к члену типа может быть обращение по полному/сокращённому имени, через this/base и т.д. Везде атрибуты приходится получать по-разному. Хотелось бы иметь более унифицированный механизм, как это делать

Достаточно будет если для PExpr и TExpr завести поле UserData типа "словарь", как это сейчас сделано для TypeBuilder.

A>3) Доступ к семантической информации на этапе компиляции макросов


A> Прежде всего это А) типы выражений Б) объекты (переменные, члены класса и т.д.), сопоставленные именам. Если к типам доступ кое-как есть (и с ним становится значительно труднее, если начинается раскрытие макросов вручную), то с переменными совсем плохо. Например, задача: заменить все вхождения переменной i в выражение на константу (или другое выражение).


Тут проблема в том, что макросы как раз эту семантику и строят. По этому мы не можем производить типизацию до раскрытия макросов. Однако доступна двупроходная схема. Сначала типизируем выражение как есть (со стандартным раскрытием макросов), а потом анализируем что получилось и заменяем на что-то другое. Единственное ограничение — первый проход должен быть удачным (не должен приводить к ошибкам типизации).

Однако подобные замены вообще лучше осуществлять на поздних стадиях, когда происходит обработка типизированного представления. В компиляторе эти стадии называются шаги T2-T4. Можно сделать не сложную плагинную систему позволяющую вставлять свои шаги преобразования типизированного АСТ между этими стадиями. Это не сложно и можно сделать уже сейчас.

A>И если будет переменная в более вложенной области с тем же именем, например:


A>Проблемы эти возникают ещё и от того, что сейчас в Немерле процессы раскрытия макросов и типизации совмещены. В принципе, можно было бы их разнести, и по крайней мере часть макросов раскрывать после полной типизации.


Это невозможно. Компилятор не знает что делат макрос и как интерпретировать код содержащий макросы. По сему типизировать макросы он не может. Я уже объяснил выше то как можно это обойти.

A>Похожим образом процесс организован, например, в xoc, расширяемом компиляторе языка C.


А можно в двух словах о том, что такого интересного есть в этом xoc-е? А то читать 110 страниц как-то в лом.

A>4) Более гибкие механизмы обращения к расширению (макросу) — здесь несколько вариантов:


A> А) применение макроса к более широкому выражению, нежели то, к которому он применяется синтаксически. Например, есть переменная-массив, нужно в этом массиве поменять порядок индексов (для более быстрой работы с памятью). Синтаксически это выглядит, например, так:


A>
A>permut(2, 1) def a = array(m, n) : array[2, float]; // объявление + перестановка индексов
A>// ...
A>// использование переменной a
A>// ...
A>


A>Т.е. непосредственным аргументом макроса является только объявление переменной a, в то время как реально на вход по-хорошему должны подаваться все последующие выражения, т.к. в них тоже нужно внести изменения, как минимум — поменять там порядок индексов при обращении к этому массиву, чтобы код работал правильно. Ровно поэтому сейчас сложно писать какие-то нетривиальные макросы, применяемые вот так к переменным.


Ну, то есть сделать макрос вида:
macro Permut(a : int, b : int, exprs : list[PExpr])
  syntax ("permut", "(", a, ")", exprs)
{
  ...
}

Который позволит захватывать выражения идущие от permut и до конца текущего блока?

Это только в Nemerle 2.0.

В прочем еще не ясно, что делать с подобными случаями:
[/Nemerle]
permut(2, 1) def a = array(m, n) : array[2, float];
...
permut(2, 1) def b = array(m, n) : array[2, float];
...
[/Nemerle]
По этой логике второй permut окажется вложенным в "exprs" первого. Это нормально?

A> Б) макрос как обычный член класса (вызов метода, обращение к полю, оператор и т.д.). При вызове такого "члена" на самом деле срабатывает макрос, и код этого макроса подставляется на место вызова этого самого члена. Так можно организовать инлайн функций или генерацию специальных версий тела функции для определённых значений параметров (см. пункт 1).


Мы обдумывали "макросы-расширения". Тут основная сложность заключается в том, что методы могут быть перегружены (да и другие члены в некоторых случаях тоже). Получится неоднозначность. Для ее разрешения сначала надо будет типизировать выражение так как-будто макрос-расширение это обычный метод (иди другой член), а уже потом заменить обращение к нему на макрос. В общем-то это возможно, но определение такого макроса будет несколько запутанным, так как по сути придется сначала объявить метод-расширение, а потом привязать к нему макрос который будет подставляться вместо этого метода (после того как будет разрешена ссылка на метод). Например, это можно оформить вот так:
macro Where[T](this source : Seq[T], predibate : T -> bool) : Seq[T]
{
  ...
  // генерируем код который должен быть обязательно совместим с типами выражений передаваемых в качестве параметров и с типом возвращаемого значения (допустима ко/контр-вариантность).
}

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

Фактически это нечто похожее на шаблоны С++ только их код можно генерировать программно.

A> В) макрос, который генерирует "член" класса (реально это будет, скорее всего, external-метод), если этот член не определён.


Ничего не понял.

A>Здесь можно привести несколько примеров. Первый: прокси-класс, который реально перенаправляет обращения к полям/методам к другому классу.


Это уже есть и работает.

A> Второй: класс, поля которого — константы, соответствующие римским цифрам (этот пример вроде очень любят в Ruby).


А можно хотя бы псевдо-код?

A>Наконец, третий пример, более реальный: в OpenCL есть векторные типы фиксированной длины. Например, int8 — вектор длины 8 из данных типа int. У них есть специальная операция swizzle, т.е. у вектора можно получить любую перестановку значений его полей. Например, если i — типа int8, то можно написать: i.s1s1s3s3s5s4s7s6, и так для любой комбинации полей вектора. Если для 4-х все комбинации ещё можно сгенерировать статически, то для 8 и 16 это невозможно. Соответственно, можно придумать макрос, который вот такие вот комбинации будет обрабатывать и генерировать динамически.


Что мешает это делать сейчас?

A> Г) Вызов макроса/расширения по произвольному паттерну.


Это то что я описал выше? Или имеется в виду что-то иное?

A> Самый общий вариант, через него можно реализовывать другие. По сути, в этом случае получается система правил и стратегий преобразования, что-то типа языка Stratego. С помощью такого инструмента можно будет добавить в язык массивное программирование.


Опять же, можно примеры псевдокода?

A>5) Управление порядком раскрытия макросов. Например, "не раскрывать макросы if/for/while, раскрывать все остальные". Сейчас я это делаю руками (нарушая попутно инкапсуляцию некоторых методов), думаю, уместнее бы такой функциональности быть в самом языке.


А какова цель этого? Сдается мне, что все это подпадает под подход описанный мною в самом начале моего ответа.

A>Ну и по мелочам:


A>* Можно ли при помощи макросов генерировать большое количество всякого using <что-то>?


Или я не понял вопроса, или ответ — да. Какая разница сколько и чего генерировать?


A>* Зачем при квазицитировании явно писать $(i:name), $(i:int) и т.д., ведь тип подставляемого объекта уже известен?


Типы вычисляются только при типизации. А работа а АСТ ведется раньше. Паттерны (квази-цитаты относятся именно к ним), так вообще с типизацией дело не имеют. Это чисто синтаксическая фича. Потому, и приходится эту информацию брать у пользователя.

A>* Хотелось бы иметь стандартное функции проверки PExpr на одинаковость.


А чем не удовлетворяет Equals?

A>И было бы ещё лучше, если бы её можно было вызывать, просто несколько раз используя переменную в паттерне


Как в Хаскеле? Да идея хорошая, то но относящаяся к макросам. Постараемся реализовать в Н2.

A>* На мой взгляд вещи типа += и т.д. обрабатываются как-то уж слишком запутанно


В смысле?

A>* Есть/будет ли возможность задавать приоритет макросов или макроопераций?


Я не знаю что такое операции. Но приоритеты для макросов верхнего уровня будут в Н2. Цель — позволить описать последовательность раскрытия макросов одного уровня, так чтобы одни могли пользоваться результатами других. Речь была об этом?

A>* Будет ли дерево кода становиться более однородным, т.е. будут ли всякие MatchCase и проч. сливаться с PExpr?


А что это может дать?
Планируется слить PExpr и TExpr. На начальных стадиях типизации (где используются макросы) их PExpr будет доступна информация о типах (по мере ее появления). Так же АСТ (в том числе и PExpr) станет неизменяемым. Так что можно будет спокойно хранить на него ссылки.

A>* Иногда хочется вставлять список выражений не как { ..$es }, а с дополнительными выражениями вокруг, например

A>
A>{ mutable a = 5; ..$es; ..$es1 a; }
A>


Это возможно и сейчас. Вот здесь находится незамысловатая функция (PrepandExprs) которая делает нечто подобное. А здесь и здесь ее применения. Общая идея очень проста. Берем имеющийся блок и вставляем в его начало свой код.

A>Планируется ли такая конструкция? Она помогла бы значительно сократить число скобок


Скобки она не уменьшила бы. Но постараемся упростить работу со списком выражений. Но это уже только в Н2.

A>* Планируется ли давать макросам доступ к строкам комментариев? Например, есть макрос, который присваивает переменной значение из командной строки — он же может использоваться для генерации текста "usage: ...", который составлять из комментариев


Что-то я этого вообще не понял. Генерировать комментарии что ли? А кто их увидит то?

A>* Сейчас нет возможности передавать расширениям параметры командной строки ncc. Планируется ли такая возможность?


Кто такие расширения? А получить значение командной строки как раз таки можно. Выше я привел пример такого код см. typer.Manager.Options.EmitDebug. Это и есть доступ к параметрам командной строки.
http://nemerle.org/Banners/?g=dark
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.