Pull to refresh

Comments 29

И опять магия и трюки. В языке D конечно учтены многие ошибки дизайна C++, но метапрограммирование все так-же натянуто — впечатление, что его прикручивали уже после разработки языка.
Мне не нравится разделение типов и значений, но если их приравнивать, то получается система с зависимыми типами. Развитие в эту сторону мне кажется логичным.

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

Знаком еще с Template Haskell, там оперируешь с AST, это по мощности где-то посередине между примесями и шаблонами. Я бы его не назвал удобным, опасность чуть ниже примесей, а удобства не прибавляет.
Мне нравится подход языка Nemerle. Там есть «макросы» — специальные функции, начинающиеся с ключевого слова «macro». По сути это функции, выполняющиеся во время компиляции. В них можно обычным императивным способом выполнять код, который имеет доступ к API компилятора. Этим функциям можно передать объекты кода в виде AST-деревьев. С одной стороны, никаких строк — с другой стороны, никакой шаблонной магии.

А что вы имеете в виду под «разделение типов и значений»? Я наверное не знаком с зависимыми типами, интересно что это такое.
А что вы имеете в виду под «разделение типов и значений»?

Если аргумент значение, то используется enum, если тип, то alias. Осталось совсем немного, чтобы оперировать с типами как с значениями. На словах это будет все равно непонятно, вот пример из Idris:
(++) : Vect n a -> Vect m a -> Vect (n + m) a
(++) Nil ys = ys
(++) (x :: xs) ys = x :: xs ++ ys

Главная мысль — в сигнатуре вектора используется значение его длины, поэтому тип как бы «зависит» от своих значений, добавил новый элемент в вектор — тип изменился. Также видно, что даже в сигнатуре функции происходят некие вычисления. Возможно, это самый органичный способ вписать мету в язык. Увы я знаю о зависимых типах не так уж много, чтобы делать какие-то выводы.

Там есть «макросы» — специальные функции, начинающиеся с ключевого слова «macro». По сути это функции, выполняющиеся во время компиляции. В них можно обычным императивным способом выполнять код, который имеет доступ к API компилятора. Этим функциям можно передать объекты кода в виде AST-деревьев. С одной стороны, никаких строк — с другой стороны, никакой шаблонной магии.

Но это же получается boilerplate код, от D подхода при генерации через compile-time function evaluation не отличается (нужно заметить, что в D не полностью строковой подход, можно примешивать только целые expressions или объявления).
Макросы и шаблоны реально оперируют не типами и не значениями, а фрагментами AST. То что традиционно шаблоны в С++ в качестве аргументов получают исключительно типы и значения (почему-то только целочисленные) — это ИМХО недоразумение. У меня при работе с низкоуровневым программингом для микроконтроллеров не раз возникала необходимость сделать шаблон, в который можно было бы передать просто фрагмент кода (даже не функцию, а просто код в фигурных скобках). Приходилось делать костыли с сишными лексическими макросами, которые как вы понимаете, далеко не лучшее решение в программировании.

Поэтому все эти alias и enum в D воспринимаются как унаследованные от С++ костыли. Конечно, должен быть некий механизм ограничения того, что мы передаем в шаблон — если там предусмотре тип, то это должен быть только тип а не что угодно. Но на самом верхнем уровне это именно нода синтаксического дерева.
Через alias параметры можно передавать символы, это, конечно, не целое AST, но уже его часть. Разработчики заняли довольно принципиальную позицию о том, что вместо операций над AST удобнее собирать строки и примешивать. Максимум, что можно ожидать в будущих версиях — расширение traits возможностью получать AST в виде списка-дерева из строк и работать с ним через compile-time функции.
Я был неправ, AST макросы давно обсуждаются DIP50, но пока не реализованы (первое упомнинание как о «запланированной» фиче это 2010 г.).
Макросы обсуждались ещё на первом DConf в (страшно представить!) 2007 году, с тех пор в языке существует ключево слово «macro», которое на практике не используется. Со временем было принято решение что генерация кода через CTFE/mixin — решение более универсальное и простое для изучения новичками, хотя и существенно уступающее в гигиене для более сложных проектов. На данный момент введение AST макросов не рассматривается, DIP50 — просто мысли одно из активных участников сообщества.

В целом макросы вещь важная и нужна, но не столь однозначно превосходящая CTFE/mixin, как может показаться. Преобразование существующего кода — да, тут вопросов не возникает. Но поддержка произвольных строковых DSL куда проще реализуется простым лексическим разбором этих самых DSL.

Лично мне в текущей системе не хватает аргументов-выражений (по аналогии с тем, как alias это аргумент-символ), это решило бы множество задач, где пригодились бы макросы.
Нужно заметить, что сложные грамматики не запихнешь в compile-time, не хватает памяти. Распарсить D код с модификациями не получится без серьезной доработки эффективности CTFE.
Это дефект реализации CTFE, фундаментально тут никакой проблемы нет. Всего лишь исправление issues.dlang.org/show_bug.cgi?id=6498 уменьшит потребление памяти на порядок и больше.

Экспериментальный альтернативный front-end для D (https://github.com/deadalnix/SDC) использует LLVM JIT для CTFE и вообще не имеет таких проблем :)
Думал, что SDC заглох. Меня он привлекает в первую очередь как compiler as a library, можно было бы использовать D как встраиваемый скриптовый язык.
Нет, он очень даже жив. deadalnix выступал с презентацией на недавнем DConf: dconf.org/2014/talks/sechet.html + планируется в качестве одной из заявок на GSoC.
Видео с конференции правда придётся ждать долго, т.к. выступал он последним, а публикуют их по порядку :) Но архитектура намного более перспективная, чем жуткая каша в исходниках DMD.
Что люди не делают, только чтобы не познавать Haskell… Я когда-то был в эйфории от метапрограммирования на C++, но после Haskell это смешно и неудобно.
Можно пояснить, что именно вы имели ввиду? Я познал Haskell на среднем уровне и не помню там меты, кроме банальных вычислений на типах (Nat и вектора на них) и TH, который требует отдельного прогона компилятора, оперирует AST и с легкостью получает невалидный код.

Тем более, почему бы императивным языкам не иметь элементы функционального подхода в мете, если это удобно и zero-cost для производительности? На Haskell нужно специально затачивать код под производительность, зачастую теряя в читаемости.
template Tuple(T...)
{
    alias Tuple = T;
}

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

Ссылка по теме: wiki.dlang.org/DIP54
Ни разу не видел эту рекомендацию. Называть их «TemplateArgumentList» — ужасное решение, весь код разбухнет раза в 3-4, а делать alias на тот же Tuple (или другое короткое имя, мб ETuple) — костыль.
Разбухание кода 1-5% разработчиков, которые активно использует метапрограммирование — терпимая цена за упрощение документации для всех остальных. Это я говорю как тот самый разработчик, чей код может разбухнуть :) В любом случае, имя Tuple уже используется стандартной библиотекой для std.typecons.Tuple, поэтому этот вариант вообще недопустим в обучающих материалах.

Кстати рекомендуемый короткий alias — List и Pack соответственно.
Спасибо, List и Pack действительно лучше. Переведу код на них.

P.S. Меня никогда не смущало наличие второго «Tuple» в std.typecons, т.к. все попытки его использовать разбивались о плохую поддержку IDE и проблемы с сериализацией, а еще он не работает с immutable.
В std.typetuple уже есть private Pack и он не является по смыслу 'StrictTuple', итого dmd не позволяет мне использовать название Pack.
Да, DIP54 ещё не закончен (я работаю над этим по мере свободного времени), пока что в «живых» проектах приходится выкручитываться на свой вкус. Я изначально говорил только об учёбных материалах, вводящих в заблуждение.
Тогда пока вместо Pack буду использовать StrictList. С обучающими примерами сложнее: нельзя же пока использовать Pack, так как, если пользователь попробует скомпилировать пример, то сразу наткнется на эту проблему.
Стоящее предложение. Почему бы тогда не сделать alias с параметрами доступными не только в шаблонах?
У меня устаревшие сведения, полгода назад не мог делать параметризированные alias:
/// Alias for vector of 2 elements
alias vec2(T) = Vector!(T, 2);
/// Alias for vector of 3 elements
alias vec3(T) = Vector!(T, 3);
Да, этот синтаксис появился в прошлом релизе компилятора.

alias Name(T)  = T

эквивалентно
template Name(T)
{
    alias Name = T;
}

… просто более короткая форма. То же самое и для enum.
Простите за тупые вопросы но как мне сделать глобальный мутабельный обьект-структуру?

struct Predmet{ x: int }

в функции я пишу так:
let predmet = Predmet{ x: 15};
а как зделать вне функции чтобы глобально?
struct Predmet { int x; }

Predmet globalPredmet = Predmet(15);

unittest
{
    std.stdio.writeln(globalPredmet);
}


Или, если нужны сложные вычисления для создания структуры, то можно через конструктор модуля:
struct Predmet { int x; }

Predmet globalPredmet;

static this()
{
    globalPredmet = Predmet(15);
}

unittest
{
    std.stdio.writeln(globalPredmet);
}
простите походу я ещё и пример не из того языка приложил :) спасибо… всё работает… буду теперь писать на D.
Sign up to leave a comment.

Articles