
In the
previous article, I talked a little about haxe - a simple and convenient general purpose language. However, besides simplicity and clarity, there are deep things in it - such as the concept of macros - code that is executed during the compilation process. Why in haxe there are no traditional C-like macros and what possibilities we tear off haxe-macros, and the article will be discussed.
Why are there no C-like macros in haxe?
In C, macros are used quite widely: this is for you and constants, these are definitions for conditional compilation, these are inline functions. They greatly increase the flexibility of the language. However, as a result of mixing, you will agree, quite different functionalities in one concept, macros with C carry with them difficulties in understanding the source text. It seems to me that this is why haxe creator
Nicolas Kanassier decided to divide this concept into components. Thus, in the language appeared separately: constants (static inline var), definitions for conditional compilation (defines) and inline-methods.
Meanwhile, macros in haxe ...
Macros in haxe stand apart - I have not seen this in other languages. Briefly about them you can say the following:
- written in haxe;
- formally, they are static and non-static methods of ordinary classes;
- these methods can have input parameters, and return an arbitrary expression;
- this expression is substituted in the place where the macro is called;
- inside: compiled into neko, and then executed before starting the compilation of the rest of the program code;
- therefore, everything can neko (read / write arbitrary files, access the database, etc.).
What exactly can you do with macros? The following moments come to mind:
- change class contents — insert new variables and methods, change data types, and so on.
- deploy code to something more complicated;
- change meta-information - mark classes / methods / variables to use this later for reflection;
- change definitions of conditional compilation (i.e., your program, for example, may be assembled differently depending on external conditions; for example, you can build a development or production version depending on the value in the configuration file);
- add new data types, delete existing ones, process all types (for example, this allows the documentation generator);
- macros in the form of static methods can be called from the command line options of the compiler (for example, there are already ready macros for importing specified folders with classes, for excluding certain files from compilation and a few more more specific functions).
')
Macros in practice
The author has repeatedly resorted to using macros in real-world tasks. One has only to remember that macros add complexity to the code, which means they should be used only where it is really needed. In the standard haxe libraries, they have built a system for interacting with
SPOD databases. The macros in it allow you to write queries to the database using a hard syntax with auto-completion (like LINQ in C #).
Example: clone () method with adjustable return type
In a typical situation, the clone () method is defined on the base class and then redefined in descendants. The problem with this method is the formal return type. On the one hand, the method defined in the base class should return an object of the “base” type, on the other hand, it should ideally return an object of the type “descendant” redefined in descendants. Thus, the method signature is broken and all typed languages ​​known to me (including haxe) will stop trying to override the method with a different return type. But it would be nice if you could write such code:
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() {
Let's write a macro method that will return the correct type. It is better to immediately place it in a separate file (this will make it easier for the compiler to separate the macro code from the usual one and thus avoid a number of problems, especially since cloning is usually needed in different places and therefore it would be good to have a macro method without being tied to the class to be cloned). A class with a macro will look like this:
Now it's enough to examine Base from Clonable and we will be able to use the cloneTyped () method:
A few notes:
- In the code, I removed the optional (for the case of a single expression) braces around the bodies of the methods and returnable data types for the methods (since the compiler will output them itself).
- Unfortunately, I did not manage to make it so that instead of “cloneTyped” we could write just “clone” (perhaps this point can be corrected).
- The cloneTyped () method will not be in the result code, nor will its calls (instead of cloneTyped () calls, there will be clone () calls with reduction to the required type without a drop in the program operation speed).
- For those who are interested in macros, after the first acquaintance, I recommend reading about reification (“materialization”) - ways to create a haxe-code from a macro for substitution instead of a call, directly writing it down.
findings
Macros in the haxe language are quite a unique and powerful thing that should be used where conventional methods do not work. It is relatively easy to create / use macros yourself, but don't forget about ready-made libraries (see
http://lib.haxe.org/t/macro ). Hope the material was interesting to you.