如何制作参数化枚举构建宏?

How to make an parametrized enum build macro?

Now Solved

我想用宏构建一个枚举,包括定义它的类型参数。

有几个来源描述了添加枚举字段 macros , but I havent found any which describe how to build an enum with specified parameter types using macros. There is a documentation entry made for limitations of macros here 关于参数类型,但仍然是空的。

想法是使用宏生成指定数量的 Either 枚举,并增加参数类型的数量。

//Either.hx
@:build(macros.build.EitherBuildMacro.build(10))

// enum Either {} <- this isnt sufficient as we need to generated several 
// enums (in this example 10 of them) with parameter types...

//And it should generate
enum Either2<A,B>{
    _1(value:A);
    _2(value:B);
}

enum Either3<A,B,C>{
    _1(value:A);
    _2(value:B);
    _3(value:C);
}

enum Either4<A,B,C,D>{
    _1(value:A);
    _2(value:B);
    _3(value:C);
    _4(value:D);
}

//etc until enum Either10<A,B,C,D,E,F,G,H,I,J>

正如我之前在此 post 中展示的那样,有一篇文章描述了如何添加字段,而不是类型。我不知道如何通过宏设置这些参数类型,似乎有一些限制,但没有记录。非常感谢任何用于该命令的指针。通过增加参数化来定义一系列枚举通常是您更愿意使用构建宏来完成的事情,而不是手动完成。特别是因为您可以将每个宏生成的 EitherN 与生成的宏 OneOfN abstract


abstract OneOf2<A, B>(Either<A, B>) from Either<A, B> to Either<A, B> {
  @:from inline static function fromA<A, B>(value:A):OneOf<A, B> {
    return _1(a);
  }
  @:from inline static function fromB<A, B>(value:B):OneOf<A, B> {
    return _2(b);  
  } 

  @:to inline function toA():Null<A> return switch(this) {
    case _1(value): value; 
    default: null;
  }
  @:to inline function toB():Null<B> return switch(this) {
    case _2(value): value;
    default: null;
  }
}

abstract OneOf3<A, B, C>(Either<A, B, C>) from Either<A, B, C> to Either<A, B, C> {
  @:from inline static function fromA<A, B, C>(value:A):OneOf<A, B, C> {
    return _1(value);
  }
  @:from inline static function fromB<A, B, C>(value:B):OneOf<A, B, C> {
    return _2(value);  
  } 
  @:from inline static function fromC<A, B, C>(value:C):OneOf<A, B, C> {
    return _3(value);  
  } 

  @:to inline function toA():Null<A> return switch(this) {
    case _1(value): value; 
    default: null;
  }
  @:to inline function toB():Null<B> return switch(this) {
    case _2(value): value;
    default: null;
  }
  @:to inline function toC():Null<C> return switch(this) {
    case _3(value): value;
    default: null;
  }
}

//etc

同样的想法可以很方便地生成一系列参数类型越来越多的元组和函数。将是生成适量枚举、摘要和 typedef

的有效且灵活的方法

@:build() 确实不是正确的方法,因为它只构建了一种特定类型。相反,您可以使用 initialization macro in combination with Context.defineType():

--macro Macro.init()
import haxe.macro.Context;

class Macro {
    public static function init() {
        for (i in 2...11) {
            Context.defineType({
                pack: [],
                name: "Either" + i,
                pos: Context.currentPos(),
                kind: TDEnum,
                fields: [
                    for (j in 0...i) {
                        name: "_" + (j + 1),
                        kind: FFun({
                            args: [
                                {
                                    name: "value",
                                    type: TPath({
                                        name: String.fromCharCode(65 + j),
                                        pack: []
                                    })
                                }
                            ],
                            ret: null,
                            expr: null
                        }),
                        pos: Context.currentPos()
                    }
                ],
                params: [
                    for (j in 0...i) {
                        name: String.fromCharCode(65 + j)
                    }
                ]
            });
        }
    }
}

使用 -D dump=pretty 你可以看到这会生成 Either2-10:

例如 Either2.dump 看起来像这样:

@:used
enum Either2<A : Either2.A,B : Either2.B> {
    _1(value:Either2.A);
    _2(value:Either2.B);
}

或者,您可以考虑将 @:genericBuild()Rest 类型参数结合使用。这基本上会做同样的事情并且仍然使用 Context.defineType(),有一些优势:

  • 它可以让你避免将类型参数的数量编码到类型名称中(所以它只是 Either 而不是 Either2 / 3 / 等等)
  • 类型参数的数量不会限制为任意数量,例如 10
  • 只会生成类型 "on demand"

你可以找到一个例子here