COMMUNITY

Generate type aliases for structures in a class

macro

(Marcelo Serpa) #1

Hello everyone!

I could use some help writing a macro that will automatically generate a type alias for a structure.

Let’s say I have a Templates class like this (which basically acts as a module here for a collection of “templates”):

class Templates {
   static var foobar = {a: 'foobar', b: 'barfoo'}
   static var barfoo = {a: 'foobar', b: 'barfoo'}
   // and more...
}

What I’d like to do is for that structure to automatically have an alias, without the need to define a typedef manually. I.e I want to take advantage of Haxe’s type inference and keep things DRY to a maximum.

I’m pretty sure I could do it with a build Macro. It’d accept the Templates class as parameter, go through each of the static fields, and generate an alias for each of the structures (at least that’s what I’d like to do):

import Templates;

@:build(SuperMagicThing.build(Templates))
class Client {
   function useIt(theTemplate: foobarTemplate) { }
}

foobarTemplate would be a typedef to the {a: 'foobar', b: 'barfoo'} ({a: String, b: String}).

Is a build macro the best way to accomplish that? Could someone put me in the right direction?

Thanks!


(Juraj Kirchheim) #2

I suppose there’s multiple ways to go. One would be to have a @:genericBuild macro that can just produce a type from a given expression. Here’s an example: http://try-haxe.mrcdk.com/#633a6

Whether or not you actually alias the types derived in this manner then becomes a separate question.


(Marcelo Serpa) #3

@back2dos Now that I see your example, emulating javascript’s typeof (when used by TypeScript to extract a type from structures) is exactly what I wanted, so thanks a bunch for this! This is actually better than automagically generating types like I asked for originally.

A couple of follow-up questions:

  1. This is a quite useful heper and I actually would expect this to be part of the Type class in std. Do you know why it’s not part of std? Could we have it as part of the std lib or tink_*? :thinking: cc @nadako;
  2. Why is the square-brackets syntax needed there (i.e <[foo]> instead of <foo>)?;
  3. Why are you using TInst there? The docs say it represents an instance of a class, but unless I’m missing something, we’re not passing an instance there in the type param, or are we?
  4. Could you explain this syntax TInst(_, [TInst(_.get().kind => KExpr(v), _)]), specifically the =>. Also, where does _ come from, from the first assignment in TInst(_ <-- here?;

And regarding our shiny new community - is this type of question better suited for StackOverflow? cc @jdonaldson (I just saw a discussion about this here).

Thanks again!


(Joshua Granick) #4

I, for one, would love to see this stuff here. One place to search for macro related questions :wink:


(Juraj Kirchheim) #5

I very much think so.

That one place already has a dedicated URL https://stackoverflow.com/questions/tagged/haxe+macros :wink:


(Juraj Kirchheim) #6

The Type class in the std lib is all about runtime reflection, so I don’t think that’d make for a good fit.

That’s because of how the parser works. Syntactically, in the angular brackets, a type is expected. Any token that would initiate a type, i.e. {, ( or an identifier, make the parser expect a type. For example if you have Typeof<foo>, then the parser first sees Typeof<foo and things, oh, ok, so foo is a package name and we’ll be parsing a fully qualified type path. With foo not being a type path (among other things because an upper case identifier for the type being missing), you get a syntax error.

There are cases where you can omit the [] safely, but I think you’ll be better off just using them :wink:

There are two nested TInst here. The outer one is Typeof itself, which is in fact a class, at least as far as syntax and representation in the compiler go. The inner one represents the value of the type parameter. Because valid values for type parameters historically always were types, some trick is needed to represent the expression as a type. So it’s wrapped in a class named Expr from a module with no name and has it’s kind set to KExpr.

The => is an extractor. So _.get().kind => KExpr(v) means "take the value (which _ is a placeholder for in the left hand side of an extractor), call it’s get method, take the kind property of the result and then => match that value against KExpr(v)". However the first and last _ mean “match with anything and ignore it”.


(Marcelo Serpa) #7

Why is a class represented as TInst? It’s confusing because the documentation states it represents a class instance: https://api.haxe.org/haxe/macro/Type.html#TInst.


(Juraj Kirchheim) #8

Let’s look at this:

class Foo {
  public function new() {}
}

var foo:Foo;

What values will foo accept? It will accept instances of Foo. You can write foo = new Foo(). You cannot write foo = Foo.

Similarly:

var bar:Typeof<[StringTools.htmlEscape]>;

What values will bar accept? Well, it will accept instances of Typeof<[StringTools.htmlEscape]>, whatever that means - the @:genericBuild macro gets to decide that.

If you find this unintuitive, I’m not surprised. It is however logically consistent :wink:


(Marcelo Serpa) #9

Ah, makes sense now. Thanks!