- PVSM.RU - https://www.pvsm.ru -
В предыдущей статье [1] я немного рассказал про haxe — простой и удобный язык общего назначения. Однако, кроме простоты и понятности, есть в нём и вещи глубокие — такие, как концепция макросов — кода, который выполняется в процессе компиляции. Почему в haxe нет традиционных Си-подобных макросов и какие возможности нам отрывают haxe-макросы, и пойдёт речь в статье.
В Си макросы используются довольно широко: это тебе и константы, это и определения для условной компиляции, это и инлайн-функции. Они здорово повышают гибкость языка. Однако, как результат смешения, согласитесь, довольно разного функционала в одной концепции, макросы с Си несут с собой и трудности понимания исходного текста. Как мне кажется, именно поэтому создатель haxe Николя Канасье [2] решил разделить эту концепцию на составляющие. Таким образом, в языке появились отдельно: константы (static inline var), определения для условной компиляции (defines) и inline-методы.
Макросы в haxe стоят особняком — мне не доводилось видеть такого в других языках. Коротко о них можно сказать следующее:
Что именно можно делать с помощью макросов? В голову приходят следующие моменты:
Автор неоднократно прибегал к использованию макросов в реальных задачах. Стоит лишь помнить, что макросы добавляют коду сложности, а значит их стоит использовать лишь там, где это действительно нужно. В стандартных библиотеках haxe на них построена система взаимодействия с базами данных SPOD [3]. Макросы в ней позволяют писать запросы к БД, используя жёсткий синтаксис с автодополнением (наподобие LINQ в C#).
В типичной ситуации, метод clone() определяется у базового класса, а затем переопределяется в потомках. Проблема этого метода — формальный возвращаемый тип. С одной стороны, метод, определённый в базовом классе, должен возвращать объект типа «базовый», с другой стороны — переопределённый в потомках он должен, в идеале, возвращать объект типа «потомок». Таким образом, сигнатура метода нарушается и все известные мне типизированные языки (в том числе и haxe) пресекут попытку переопределить метод с другим возвращаемым типом. А ведь было бы хорошо, если бы можно было писать такой код:
class Base
{
public function new() { }
public function clone() : Base { return new Base(); }
}
class Child extends Base
{
public function new() { super(); }
override function clone() : Base { return new Child(); }
public function childMethod() return "childMethod";
}
class Main
{
static function main()
{
// Хорошо бы, чтобы строка ниже не вызывала ошибки
trace(new Child().clone().childMethod());
}
}
Давайте напишем макрос-метод, который будет возвращать правильный тип. Лучше сразу поместить его в отдельный файл (этим мы упростим компилятору отделение макро-кода от обычного и избежим тем самым ряда проблем, тем более, что клонирование обычно нужно в разных местах и потому хорошо бы иметь макро-метод без привязки к клонируемому классу). Класс с макросом будет выглядеть так:
// файл Clonable.hx
import haxe.macro.Expr;
import haxe.macro.Context;
import haxe.macro.Type;
class Clonable
{
// в нестатические макросы первым параметром всегда отдаётся ссылка
// на выражение, через которое был вызван этот метод
macro public function cloneTyped(ethis:Expr)
{
var tpath = Context.toComplexType(Context.typeof(ethis));
// возвращаем код, который мы хотим подставить в место вызова данного макро-метода;
// как и везде, без денег - ни куда - $ позволяет вставить значение локальной переменной
return macro (cast $ethis.clone():$tpath);
}
}
Теперь достаточно пронаследовать Base от Clonable и мы сможем пользоваться методом cloneTyped():
// файл Main.hx
class Base extends Clonable
{
public function new() { }
public function clone() return new Base();
}
class Child extends Base
{
public function new() super();
override function clone() return new Child();
public function childMethod() return "childMethod";
}
class Main
{
static function main()
{
// Здесь, несмотря на то, что Child.clone() имеет формальный тип Base,
// мы, однако, можем сделать вызов childMethod(), как если бы нам из clone() вернулся тип Child
trace(new Child().cloneTyped().childMethod());
}
}
Несколько замечаний:
Макросы в языке haxe — достаточно уникальная и мощная штука, которую стоит использовать там, где обычные способы не работают. Относительно несложно создавать/использовать макросы самому, однако не стоит забывать про готовые библиотеки (см. http://lib.haxe.org/t/macro [4]). Надеюсь, материал был вам интересен.
Автор: yar3333
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/77006
Ссылки в тексте:
[1] предыдущей статье: https://www.pvsm.ru/post/243199/
[2] Николя Канасье: https://plus.google.com/+NicolasCannasse/
[3] SPOD: http://old.haxe.org/doc/sys/db/spod
[4] http://lib.haxe.org/t/macro: http://lib.haxe.org/t/macro
[5] Источник: http://habrahabr.ru/post/245617/
Нажмите здесь для печати.