![image](https://habrastorage.org/getpro/habr/post_images/2d2/ca6/20d/2d2ca620dad7e0b943b72cbd8f8dc291.jpg)
Если и так существует так много вариантов, то кажется, что изобретать новый не имеет смысла, но каждый перечисленный вариант реализации идей метапрограммирования обладает своими недостатками, которые не совместимы с идей языка Nemerle.
Во-первых Nemerle компилируемый язык программирования, поэтому отпадают механизмы из Ruby, Tcl и LISP.
Макросы C наиболее маломощны из всех, так как существует большой класс задач, которые могут быть автоматизированы с помощью макросов из LISP, но не по зубам макросам C. Кроме того, макросы C не безопасны, так как часто аргумент макроса вычисляется несколько раз, а если это функция с побочным действием, то в программе появляется трудноуловимый баг.
Достоинством кодогенерации является то, что она не имеет отношения к языку, но из этого следует и её недостаток — усложняется сборка проекта.
Шаблоны C++ — хорошая технология, но у неё много родовых травм, например, она работает на уровне текста, есть проблемы с взаимодействием между библиотеками, а так же относительно простой код на шаблонах выглядит относительно просто, но сложный код на шаблонах выглядит очень сложно. Такой подход отличается от негласной истины, что сколь угодно сложный код должен иметь простой интерфейс.
Суммируя, можно описать какими свойствами обладает метапрограммрование в Nemerle:
- Мощная макросистема, аналогичная макросистеме в LISP.
- Макросы распространяются не в виде исходного текста, а в виде откомпилированных сборок — плагинов к компилятору.
- Использование макросов не сложнее использования библиотек.
Так что же такое макрос в контексте Nemerle?
Макрос является отображением (функцией) из синтаксического дерева в синтаксическое дерево. Упрощенно, компилятор по исходному тексту строит синтаксическое дерево, затем, части синтаксического дерева, описывающие классы, помеченные атрибутом макроса, изменяются под действием самого макроса и процесс компиляции продолжается.
Макросы могут изменять синтаксические деревья как конкретного класса, так и всей программы, таким образом, метапрограммирование в Nemerle достаточно мощно. Под мощностью я имею ввиду, что множество задач, которые могут быть автоматизированы в Nemerle достаточно шире, чем в других языках.
Зачем?
Сложно понять зачем и когда использовать метапрограммирование, поэтому лучше всего проанализировать задачи, его использующие. Задача сериализации объектов является задачей метапрограммирования. Предположим, что мы имеем классы, задающие деревъя объектов, например
class A
{
public Foo : int { get; set; }
public Bar : string { get; set; }
}
class B
{
public Baz : A { get; set; }
public Pub : byte { get; set; }
}
* This source code was highlighted with Source Code Highlighter.
Добавим теперь код, отвечающий за сериализацию:
interface ISerializable
{
Serialize(stream : StreamWriter) : void;
}
class A : ISerializable
{
public Foo : int { get; set; }
public Bar : string { get; set; }
public Serialize(stream : StreamWriter) : void
{
stream.WriteLine("<A>");
stream.WriteLine($"<Foo><int>$Foo</int></Foo>");
stream.WriteLine($"<Bar><string>$Bar</string></Bar>");
stream.WriteLine("</A>");
}
}
class B : ISerializable
{
public Baz : A { get; set; }
public Pub : byte { get; set; }
public Serialize(stream : StreamWriter) : void
{
stream.WriteLine("<B>");
stream.WriteLine("<Baz>");
when(Baz!=null) Baz.Serialize(stream);
stream.WriteLine("</Baz>");
stream.WriteLine($"<Pub><byte>$Pub</byte></Pub>");
stream.WriteLine("</B>");
}
}
* This source code was highlighted with Source Code Highlighter.
Очевидно, что существует формальный алгоритм, который переведет код из первого примера в код второго примера, если этот алгоритм оформить на языке Nemerle, то получим макрос. Тогда код с поддержкой сериализации можно будет переписать, например, так:
[MakeSerializable]
class A
{
public Foo : int { get; set; }
public Bar : string { get; set; }
}
[MakeSerializable]
class B
{
public Baz : A { get; set; }
public Pub : byte { get; set; }
}
* This source code was highlighted with Source Code Highlighter.
Где MakeSerializable — вызов макроса.
На основе этого примера легко вывести признак того, что пора использовать метапрограммирование:
Над кодом, который обеспечивает работу основного алгоритма, производяться повторяющиеся операции.
Забавно, что если бы Nemerle был бы mainstream, то многие продукты просто не появились бы, например, средство AOP Postsharp было бы не нужно, так как все его возможности легко и просто реализируются через макросы. Другим известным средством является NUnit, в последней версии добавились парамметризованные тесты, эту возможность опять же легко можно реализовать через макросы в Nemerle для перидущей версии NUnit, к тому же с более высоким контролем типов.
Для тех, кто любит числа, то использование метапрограммирования в моем проекте поиска формул сократило количества кода на 20%, способствовало улучшению дизайна системы, так как осознание того, что над кодом будут происходить преобразования, заставляет писать код более структурировано. Дополнительным плюсом стало то, что использование метапрограммирования облегчило рефакторинг системы.
В заключении, хочу еще раз отметить, что использование метапрограммирования нацелено на сокращение вспомогательного кода и, как следствие, связаных с ним ошибок. Ключевым словом здесь является слово «вспомогательного», поэтому польза от использования метапрограммирования в первую очередь будет заметна в больших проектах.