如何制作参数化的枚举生成宏?

时间:2019-08-11 19:06:59

标签: macros haxe

Now Solved

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

有两个描述使用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>

正如我在本文前面所展示的,有一篇文章描述了如何添加字段,而不是类型。我不知道如何通过宏设置这些参数类型,并且似乎有一些限制,但是没有记录。非常感谢使用该命令的任何指针。通常,您更希望对构建宏进行定义,而不是手动进行,以增加参数化来定义枚举序列。特别是因为您可以将每个宏生成的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的有效且灵活的方式

1 个答案:

答案 0 :(得分:1)

@:build()的确不是正确的方法,因为它只能构建一种特定的类型。相反,您可以将initialization macroContext.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
  • 类型只能“按需”生成

您可以找到示例here