Makros in haxe: Code direkt zur Kompilierungszeit ausführen (und das ist normal)

    In einem früheren Artikel habe ich ein wenig über Haxe gesprochen, eine einfache und bequeme Allzwecksprache. Neben der Einfachheit und Klarheit gibt es jedoch auch tiefgreifende Dinge - wie das Konzept des Makros - Code, der während der Kompilierung ausgeführt wird. Warum es in haxe keine traditionellen C-ähnlichen Makros gibt und welche Möglichkeiten haxe-Makros für uns eröffnen, und der Artikel werden diskutiert.


    Warum gibt es in haxe kein C-ähnliches Makro?


    In C werden Makros häufig verwendet: Dies sind für Sie Konstanten, dies sind Definitionen für die bedingte Kompilierung, dies sind Inline-Funktionen. Sie verbessern die Flexibilität der Sprache erheblich. Aufgrund des Mischens müssen Sie jedoch zugeben, dass ein Konzept ganz unterschiedliche Funktionen enthält. Makros mit C haben Schwierigkeiten, den Quelltext zu verstehen. Es scheint mir, dass der Schöpfer von Haxe, Nicolas Kanasie, aus diesem Grund beschlossen hat, dieses Konzept in Komponenten zu unterteilen. Daher wurde die Sprache separat angezeigt: Konstanten (statische Inline-Var), Definitionen für die bedingte Kompilierung (Defines) und Inline-Methoden.

    Inzwischen Makros in Haxe ...


    Makros in Haxe fallen auf - ich habe dies noch nie in anderen Sprachen gesehen. Kurz dazu können wir folgendes sagen:
    • geschrieben in haxe;
    • formal sind sie statische und nicht statische Methoden gewöhnlicher Klassen;
    • Diese Methoden können Eingabeparameter haben und einen beliebigen Ausdruck zurückgeben.
    • Dieser Ausdruck wird an der Stelle des Makroaufrufs ersetzt.
    • inside: in neko kompiliert und dann ausgeführt, bevor der Rest des Programmcodes kompiliert wird;
    • daher alles, was neko tun kann (beliebige Dateien lesen / schreiben, auf die Datenbank zugreifen usw.).

    Was genau kann man mit Makros machen? Folgende Punkte fallen mir ein:
    • Ändern Sie den Inhalt von Klassen - fügen Sie neue Variablen und Methoden ein, ändern Sie Datentypen und so weiter.
    • Stellen Sie Code für etwas Komplexeres bereit
    • Meta-Informationen ändern - Klassen / Methoden / Variablen zur späteren Verwendung zur Reflexion markieren;
    • Ändern Sie die Definitionen der bedingten Kompilierung (d. h. Ihr Programm kann beispielsweise abhängig von den externen Bedingungen unterschiedlich kompilieren. Sie können beispielsweise abhängig vom Wert in der Konfigurationsdatei eine Entwicklungs- oder Produktionsversion kompilieren).
    • neue Datentypen hinzufügen, vorhandene löschen, alle Typen verarbeiten (so können Sie beispielsweise einen Dokumentationsgenerator erstellen);
    • Makros in Form statischer Methoden können über die Befehlszeilenoptionen des Compilers aufgerufen werden (z. B. vorgefertigte Makros zum Importieren bestimmter Ordner mit Klassen, um bestimmte Dateien von der Kompilierung auszuschließen, und einige spezifischere Funktionen).


    Makros in der Praxis


    Der Autor hat wiederholt auf die Verwendung von Makros bei realen Aufgaben zurückgegriffen. Es ist zu beachten, dass Makros dem Code Komplexität verleihen, was bedeutet, dass sie nur dort verwendet werden sollten, wo sie wirklich benötigt werden. In Standard-Haxe-Bibliotheken bauten sie ein System für die Interaktion mit SPOD- Datenbanken auf . Mit den darin enthaltenen Makros können Sie Datenbankabfragen mithilfe der harten Syntax mit automatischer Vervollständigung schreiben (wie LINQ in C #).

    Beispiel: clone () -Methode mit einstellbarem Rückgabetyp


    In einer typischen Situation wird die clone () -Methode für die Basisklasse definiert und dann in Nachkommen überschrieben. Das Problem bei dieser Methode ist der formale Rückgabetyp. Einerseits sollte eine in einer Basisklasse definierte Methode ein Objekt vom Typ "base" zurückgeben, andererseits, das in Nachkommen neu definiert wurde, idealerweise ein Objekt vom Typ "Nachkommen" zurückgeben. Daher wird die Signatur der Methode verletzt und alle mir bekannten typisierten Sprachen (einschließlich haxe) stoppen den Versuch, die Methode mit einem anderen zurückgegebenen Typ zu überschreiben. Aber es wäre schön, wenn Sie solchen Code schreiben könnten:
    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());
    	}
    }
    

    Schreiben wir eine Makromethode, die den richtigen Typ zurückgibt. Es ist besser, es sofort in eine separate Datei zu stellen (dies vereinfacht die Trennung des Makrocodes vom Compiler vom üblichen und vermeidet dadurch eine Reihe von Problemen, zumal das Klonen normalerweise an verschiedenen Stellen erforderlich ist und es daher schön wäre, eine Makromethode zu haben, ohne an die geklonte Klasse gebunden zu sein). Eine Klasse mit einem Makro sieht folgendermaßen aus:
    // файл 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); 
    	}
    }
    

    Jetzt reicht es aus, Base von Clonable zu erben, und wir können die cloneTyped () -Methode verwenden:
    // файл 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());
    	}
    }
    

    Ein paar Anmerkungen:
    1. Im Code habe ich die optionalen (für den Fall eines einzelnen Ausdrucks) geschweiften Klammern um die Methodenkörper und die Rückgabedatentypen für Methoden entfernt (da der Compiler sie selbst ausgibt).
    2. Leider konnte ich es nicht schaffen, dass wir anstelle von "cloneTyped" einfach "clone" schreiben konnten (vielleicht kann dieser Punkt behoben werden).
    3. Die cloneTyped () -Methode befindet sich nicht im resultierenden Code, ebenso wie ihre Aufrufe (anstelle von cloneTyped () -Aufrufen gibt es clone () -Aufrufe mit der Konvertierung in den gewünschten Typ, ohne dass die Geschwindigkeit des Programms abnimmt).
    4. Für diejenigen, die sich nach meiner ersten Bekanntschaft für Makros interessieren, empfehle ich, etwas über die Verdinglichung („Verdinglichung“) zu lesen - Möglichkeiten, einen Haxe-Code aus einem Makro zu erstellen, um ihn anstelle eines Aufrufs durch direktes Schreiben zu ersetzen.

    Schlussfolgerungen


    Makros in der Haxe-Sprache sind eine ziemlich einzigartige und leistungsstarke Sache, die verwendet werden sollte, wenn herkömmliche Methoden nicht funktionieren. Es ist relativ einfach, Makros selbst zu erstellen / zu verwenden, aber vergessen Sie nicht vorgefertigte Bibliotheken (siehe http://lib.haxe.org/t/macro ). Ich hoffe das Material war interessant für dich.

    Jetzt auch beliebt: