How to make an parametrized enum build macro?

本小妞迷上赌 提交于 2019-12-11 04:46:17

问题


Now Solved

I want to build an enum with a macro, including defining its type parameters.

There are a couple of sources describing adding enum fields with 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 regarding parameter types, but that is still left empty.

The idea is to use a macro to generate a specified number of Either enums with increasing amount of parameter types.

//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>

As I showed earlier in this post there is an article describing how to add fields, but not types. I am clueless how to set these parameter types by a macro and it seems like there are some limitations, yet undocumented. Any pointers which command to use for that are highly appreciated. Defining series of Enums with increasing parameterization is typically something you rather want to do with build macros, than to do by hand. Especially since you could pare each macro generated EitherN with a macro generated 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

The same idea would be handy to generate series of Tuples and Functions with increasing amount of parameter types. Would be a efficient and flexible way to generate the right amount of enums, abstracts and typedefs


回答1:


@:build() indeed isn't the right approach here, since that just builds one particular type. Instead, you could use an 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)
                    }
                ]
            });
        }
    }
}

With -D dump=pretty you can see that this generates Either2-10:

With for instance Either2.dump looking like this:

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

Alternatively, you could consider using @:genericBuild() in combination with a Rest type parameter. That would essentially do the same and still use Context.defineType(), with a few advantges:

  • it would allow you to avoid encoding the number of type parameters into the type name (so it would just be Either instead of Either2 / 3 / etc)
  • the amount of type parameters would not be limited to an arbitrary amount such as 10
  • types would only be generated "on demand"

You can find an example here.



来源:https://stackoverflow.com/questions/57452808/how-to-make-an-parametrized-enum-build-macro

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!