【问题标题】:How to declare instantiation in Haxe macro function如何在 Haxe 宏函数中声明实例化
【发布时间】:2015-08-24 09:45:53
【问题描述】:

我想创建一个为我生成此代码的宏:

if (myEntity.get(Attack) == null) myEntity.add(new Attack());
if (myEntity.get(Confused) == null) myEntity.add(new Confused());
if (myEntity.get(Defend) == null) myEntity.add(new Defend());
if (myEntity.get(Offense) == null) myEntity.add(new Offense());

在代码中我想这样声明/使用它:

EntityMacroUtils.addComponents(myEntity, Attack, Confused, Defend, Offense);

当前的宏函数如下所示:

macro public static function addComponents(entity:ExprOf<Entity>, components:Array<ExprOf<Class<Component>>>):Expr
{
    var exprs:Array<Expr> = [];
    for (componentClass in components)
    {
        var instance = macro $e { new $componentClass() }; // problem is here
        var expr = macro if ($entity.get($componentClass) == null) $entity.add(instance);
        exprs.push(expr);
    }
    return macro $b{ exprs };
}

这个宏函数不正确,我得到错误:

EntityMacroUtils.hx:17:字符 22-43:找不到类型:$componentClass

问题是我不知道如何定义new $componentClass()。我将如何解决这个问题?

我还想避免在输出代码中出现Type.createInstance

【问题讨论】:

    标签: macros haxe


    【解决方案1】:

    以编程方式生成实例化代码的一种方法是使用“老派”枚举 AST 构建(兼容 Haxe 3.0.1+):

    // new pack.age.TheClass()
    return {
        expr:ENew({name:"TheClass", pack:["pack", "age"], params:[]}, []),
        pos:Context.currentPos()
    };
    

    使用具体化改进语法是可能的:

    // new pack.age.TheClass()
    var typePath = { name:"TheClass", pack:["pack", "age"], params:[] };
    return macro new $typePath();
    

    现在,为了方便的“实例化助手”函数语法,我们需要做一些变形来从宏函数中收到的表达式中提取类型路径:

    // new Foo(), new pack.Bar(), new pack.age.Baz()
    instantiate(Foo, pack.Bar, pack.age.Baz);
    
    macro static function instantiate(list:Array<Expr>)
    {
        var news = [for (what in list) {
            var tp = makeTypePath(what);
            macro new $tp();
        }];
        return macro $b{news};
    }
    
    #if macro
    static function makeTypePath(of:Expr, ?path:Array<String>):TypePath 
    {
        switch (of.expr)
        {
            case EConst(CIdent(name)):
                if (path != null) {
                    path.unshift(name);
                    name = path.pop();
                }
                else path = [];
                return { name:name, pack:path, params:[] };
    
            case EField(e, field):
                if (path == null) path = [field];
                else path.unshift(field);
                return makeTypePath(e, path);
    
            default:
                throw "nope";
        }
    }
    #end
    

    【讨论】:

    • 谢谢,我试试。仍在尝试通过宏和表达式具体化找到我的方式。有时我觉得我理解它是如何工作的,直到它没有:)
    • 这是我唯一想不通的事情之一。
    • 不错!您需要将完整路径传递给宏对吗?
    • 是的,这个答案不解析类型参数,也不解析不完全限定的类型 - 我相信可以添加。
    【解决方案2】:

    如果有人需要答案,我得到了这个感谢 Haxe IRC 聊天中的 ousado:

    如果你只在宏中这样做,你可以这样做:

    var ct = macro : pack.age.SomeTypename;
    var tp = switch ct { case TPath(tp):tp; case _: throw "nope"; }
    var expr = macro new $tp();
    

    ..或者,如果您明确构造tp

    var tp = {sub:'SomeTypeName',params:[],pack:['pack','age'],name:"SomeModuleName"}
    

    如您所见,这里明确给出了复杂类型路径。

    不幸的是,Haxe 对于表达式位置的类型并没有真正的简洁语法。您可以通过 ( _ : TypeName ) 来提供包含 ComplexType 的表达式。

    但是如果你想传递一个类型作为参数,你可以这样做:

    import haxe.macro.Expr;
    using haxe.macro.Tools;
    
    class Thing {
        public function new(){}
    }
    class OtherThing {
        public function new(){}
    }
    
    class TMacroNew {
    
        macro static function instances( arr:Array<Expr> ) {
    
            var news = [for (e in arr) {
                var ct = switch e.expr { case EParenthesis({expr:ECheckType(_,ct)}):ct; case _: throw "nope"; };
                var tp = switch ct { case TPath(tp):tp; case _: throw "nope"; };
                macro new $tp();
            }];
            trace( (macro $b{news}).toString());
            return macro $b{news};
        }
    
    
        static function main(){
            instances( (_:Thing), (_:Thing), (_:OtherThing) );
        }
    }
    

    ..如果你想要一个类型列表,你可能想要一个像( _ : L&lt; One,Two,Three&gt; )这样的参数列表。

    【讨论】:

    • 在得到这些信息后,我意识到我不想要宏(如果语法更奇怪的话),但我会用手把它写出来。
    • 哈,所以有一种具体化的方式——我更新了我的回复,甚至找到了一种保持语法简洁的方法!
    • 我认为文档可以在这方面进行改进,我在这里看到了具体化方式haxe.org/manual/macro-reification-expression.html 它只说function $name() { },您可以从中猜出语法存在。
    【解决方案3】:

    接受的答案是有问题的,因为它在涉及类型参数或应包括对非名义类型的支持时中断。

    我使用两种替代方法更新了示例,以便为类型列表提供更简洁的表示法,同时仍然允许实际类型的语法。

    import haxe.macro.Expr;
    using haxe.macro.Tools;
    
    class Thing {
        public function new(){}
    }
    class OtherThing {
        public function new(){}
    }
    
    class TPThing<T>{
        public function new(){}
    }
    
    class TMacroNew {
    
        macro static function instances( e:Expr ) {
            var tps = switch e.expr { 
                case EParenthesis({expr:ECheckType(_,TPath({params:tps}))}):tps; 
                case ENew({params:tps},_):tps;
                case _: throw "not supported"; 
            }
            var type_paths = [ for (tp in tps) switch tp { 
                case TPType(TPath(tp)):tp; 
                case _: throw "not supported"; 
            }];
            var news = [for (tp in type_paths) macro new $tp()];
            trace( (macro $b{news}).toString());
            return macro $b{news};
        }
    
    
        static function main(){
            instances( (_:L<Thing,Thing,OtherThing,TPThing<Int>> ) );
            instances( new L<Thing,Thing,OtherThing,TPThing<Int>>()  );
        }
    }
    

    编辑: L&lt; ... &gt; 中的 L 可以是任何有效的类型名称。它的唯一目的是允许以有效的语法编写以逗号分隔的类型列表。由于宏函数将表达式作为参数,因此我们必须使用允许/需要类型的表达式,例如:( _ :T ), new T(), var v:T, function(_:T):T {}

    【讨论】:

    • 感谢您的加入!在这种情况下,L 到底是什么?
    • L 没有被使用——你可以为一个类型使用任何可能的有效名称。或者,您可以强制使用特定名称,也许是描述性的名称,例如new FOR_ALL&lt;T1,T2,T3,...&gt;().
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多