Как стать автором
Обновить

Комментарии 13

Мсье знает толк…
Вы чёкнутый :)
Если бы в С+ сделали нормальную «интерпретацию во время компиляции» (известную также как синтаксические макросы), было бы очень хорошо. А этот пример (как и весь Boost) лишний раз показывает, что бывает, когда в языке чего-то изначально не предусмотрено, а программистам очень хочется.
Интерпретация во время компиляции очень хорошо сделана в языке D. С другой стороны, есть инструментарий (тот же ReSharper) который умеет делать интерпретацию во время написания кода (не во время компиляции!) и тем самым у вас появляется возможность использовать шаблоны и потом их «инлайнить» в код. Только проблема в том что если что-то изменится, придется переписывать все снова.
Мне понравилось как синтаксические макросы устроены в Nemerle. А про D очень мало информации (в книге Александреску этого нет, на официальном сайте простейший пример). Конструкция mixin — там просто строки, в которых код. Непонятно, можно ли эти строки конструировать программно, если можно — какие операции разрешены для времени компиляции и т.д. И в целом как-то бессистемно все… может вы расскажете?
А про D очень мало информации (в книге Александреску этого нет, на официальном сайте простейший пример). Конструкция mixin — там просто строки, в которых код. Непонятно, можно ли эти строки конструировать программно, если можно — какие операции разрешены для времени компиляции и т.д.


Можно, причем двумя разными способами. Первый — выполнение функций в compile time, второй — через шаблоны. Для примера рассмотрю первый вариант. Практически любые функции, которые не используют IO, указатели и другие низкоуровневые фичи языка можно выполнять на этапе компиляции. Для этого нужно просто использовать функцию в контексте, в котором у нее нет выбора, кроме как выполниться при компиляции (например, присвоить результат к константе enum).

Иллюстрация: есть два энума A и B, нужно сгененрировать switch по элементам первого энума, который поставит в соответствие каждому элементу A элемент в B.
import std.stdio;

enum A
{
    a1,
    a2,
    a3
}

enum B
{
    b1,
    b2,
    b3
}

// Берем два энума и название переменной, для которой будет
// сгенерирован switch
string genSwitch(E1, E2)(string var)
    if(is(E1 == enum) && is(E2 == enum))
{
    // Просим компилятор выдать нам tuple всех элементов типа
    enum E1Members = __traits(allMembers, E1);
    enum E2Members = __traits(allMembers, E2);
    // Проверяем, чтобы их длинны совпадали, проверка во время компиляции
    static assert(E1Members.length == E2Members.length);

    // Используем все обычные средства для работы со строками
    // для генерации свитча
    string s = "final switch("~var~")\n{\n";
    foreach(i, member; E1Members)
    {
        s ~= "\tcase("~E1.stringof~"."~member~"):\n\t{\n";
        s ~= "\t\twriteln("~E2.stringof~"."~E2Members[i]~");\n";
        s ~= "\t\tbreak;\n\t}\n";
    }
    return s~"}";
}

void main()
{
    auto a = A.a2;
    
    // Способ вывести что-нибудь во время компиляции в консоль
    pragma(msg, genSwitch!(A, B)("a"));
    // Встраиваем сгенеренный свитч
    mixin(genSwitch!(A, B)("a"));
}


Во время компиляции увидим:
final switch(a)
{
    case(A.a1):
    {
        writeln(B.b1);
        break;
    }
    case(A.a2):
    {
        writeln(B.b2);
        break;
    }
    case(A.a3):
    {
        writeln(B.b3);
        break;
    }
}


P.S. Это просто иллюстрация. Генерацию через шаблоны того же кода, если интересно, то тоже приведу.
Приводите конечно.
Все же шаблоны больше подходят для операций над типами, проверки их свойств и т.п. Но для генерации кода тоже подходят:

// Через variable length arguments можно в шаблон
// передавать практически что угодно
template GenSwitch(E1, E2, TS...)
{
    // Но лучше проверять, что именно пользователь передал
    static assert(TS.length == 1);
    static assert(is(typeof(TS[0]) == string));
    enum var = TS[0];

    enum E1Members = __traits(allMembers, E1);
    enum E2Members = __traits(allMembers, E2);
    static assert(E1Members.length == E2Members.length);

    // Циклы заменяем рекурсией через nested templates
    private template GenBody(TSS...)
    {
        // через TSS передаем текущий номер итерации
        enum i = TSS[0];
        // и оставшийся кусок от E1Members
        alias TS = TSS[1..$];

        static if(TS.length == 0) // Дно рекурсии
        {
            // Процедура 'возврата значения/типа' из шаблона
            // нужно объявить enum или alias,
            // имя которого совпадает с именем шаблона
            enum GenBody = "";
        }
        else
        {
            enum GenBody = "\tcase("~E1.stringof~"."~TS[0]~"):\n\t{\n"
                         ~ "\t\twriteln("~E2.stringof~"."~E2Members[i]~");\n"
                         ~ "\t\tbreak;\n\t}\n"
                         ~ GenBody!(i+1, TS[1..$]);
        }
    }

    enum GenSwitch = "final switch("~var~")\n{\n" 
                    ~GenBody!(0, E1Members)
                    ~"}";
}
В книжке Александреску делается акцент на том, что строки можно генерировать в compile-time, еще он в качестве примера приводит возможность написания compile-time DSL на D.
Смотрю на Smalltalk, древний как… в общем очень древний, где минимальной единицей компиляции является не модуль/программа, а метод, и душа радуется — чтобы произвести какие-то вычисления во время компиляции, пишете например ##(65535 factorial) — и получите почти килобайтный экземпляр BigInteger, вкомпилённый прямо в байт-код метода. Я это привёл просто к примеру, здесь не про «нафига это надо», а «смотрите как МОЩНО умеет!».

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

Алан Кей поддел в своё время C++ за кой-какие его «шероховатости», и видимо не зря :)
Всё время, пока я писал пост, меня не покидала мысль: «Это или уже есть в Бусте, или пишется там в три строки».


Эта мысль преследует меня постоянно, когда нет возможности использовать Boost
Оу, да. Смешались в кучу кони, люди… Исправил.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации