Haxe Code Cookbook
Haxe programming cookbookMacrosCombine two or more structures

Combine two or more structures

Reading time: 2 minutes

Haxe makes it easy to define extensions for a typedef, but there is no easy way to combine the values of two or more structures to one, like {a:2} and {b:"foo"} to {a:2,b:"foo"}. The following macro does this for you.

Implementation

#if macro
import haxe.macro.Expr;
import haxe.macro.Context;
using haxe.macro.Tools;
using Lambda;
#end


class StructureCombiner {
  // we use an Array<Expr>, because we want the macro to work on variable amount of structures
  public static macro function combine(rest: Array<Expr>): Expr {
    var pos = Context.currentPos();
    var block = [];
    var cnt = 1;
    // since we want to allow duplicate field names, we use a Map. The last occurrence wins.
    var all = new Map<String, { field: String, expr: Expr}>();
    for (rx in rest) {
      var trest = Context.typeof(rx);
      switch ( trest.follow() ) {
        case TAnonymous(_.get() => tr):
          // for each parameter we create a tmp var with an unique name.
          // we need a tmp var in the case, the parameter is the result of a complex expression.
          var tmp = "tmp_" + cnt;
          cnt++;
          var extVar = macro $i{tmp};
          block.push(macro var $tmp = $rx);
          for (field in tr.fields) {
            var fname = field.name;
            all.set(fname, { field: fname, expr: macro $extVar.$fname } );
          }
        default:
          return Context.error("Object type expected instead of "
            + trest.toString(), rx.pos);
      }
    }
    var result = {expr:EObjectDecl(all.array()), pos: pos};
    block.push(macro $result);
    return macro $b{block};
  }

}

Usage

You can import the macro class with using.

using StructureCombiner;

typedef Foo = { a: Int, b: Float }

typedef FooBar = { > Foo, bar: String };

static function callFoo() return { a: 42, b: 3.14 };

static function callBar() return { bar: "42" };

class Main {
  static function main() {
    // you can use the macro with function results
    var fb: FooBar = callFoo().combine(callBar());
    // or with variables and anonymous structures
    var foo: Foo = callFoo();
    fb = foo.combine({ bar: "42" });
    // more parameters are allowed
    fb = {a: 111}.combine({b:13.1}, {bar: "happy hour"});
    // when several structures have the same field, the last wins.
    var fb2 = fb.combine({bar: "lucky strike"});
  }
}

After compiling previous code, the statement fb = {a: 111}.combine({b:13.1}, {bar: "happy hour"}); generates code like: fb = {a: 111, b: 13.1, bar: "happy hour"};. As you can see, the output code's variable declarations are assigned to values which are already mixed (at compile time).

From performance point of view, this is optimal, even more than using native APIs, (like JavaScript's Object.assign() for example).


Contributors:
Sebastián Gurin
Gama11
Mark Knol
AdrianV
Last modified:
Created:
Category:  Macros