nadako's rants

Haxe 3.1 is here!

The new release of Haxe compiler is finally out to be awesome around the world, thanks to its core developers and contributors (even I helped by bugging people and learning ocaml, yay!).

So, what's new there, apart from tons of bugfixes (especially related to the abstracts feature introduced in 3.0)? Let's see...

Enum abstracts

This is something I have written about when it first appeared in Haxe Git repository. Enum abstracts, as the name implies are combination of enums and abstracts and is used to define a finite set of constant values of a given type with proper compile-time checking for exhaustiveness and correctness. You can read more about them in my original article, but here's what they look like:

@:enum abstract State(Int)
{
    var Idle = 0;
    var Walk = 1;
    var Fire = 2;
}

The vars defined here actually become public static inline vars of the State type which is an abstract over Int as written above, you can access them as i.e. State.Idle, or even just Idle if the expression is known to be of that State type, just like Haxe enums, but in run-time they will become plain integers, because of how abstracts work in Haxe.

Extractors

Extractors is a nice new pattern matching feature, allowing to apply preprocessing to a value before matching with a pattern in the case expression. The syntax is expr => pattern, where expr is a custom extractor expression in which the original value being matched is accessed with the _ identifier. For example:

var s = "hello";
switch (s)
{
    case _.toUpperCase() => "HELLO":
        trace(1);
    case StringTools.urlDecode(_) => "hello":
        trace(2);
    case "a" + _ => "ahello":
        trace(3);
}

One thing that extractors help with is working with haxe.macro.Type, as it has Ref<T> objects that needs to be dereferenced before further matching, i.e. matching a String type would now look like this:

var a = Context.getLocalType();
switch (a)
{
    case TInst(_.get() => {pack: [], name: "String"}, []):
    case _:
}

EnumValue.match

This is a handy little addition helping to check whether given value of an enum matches the required pattern or not, so instead of writing:

var matches = switch (a) { case A(1): true; default: false; };

You can simply write:

var matches = a.match(A(1));

Linking with .NET assemblies directly for C#

Similar to Flash and Java libraries, you can now directly add .NET libraries to the compiled application when compiling to C#. Haxe compiler now parses the .NET assembly file (i.e. the DLL), extracting and converting types to Haxe. This eliminates the need to write extern declarations for .NET libraries. The command-line option is -net-lib MyLib.dll. Here's a pic related:

In addition to this feature, C# target now supports native .NET attributes, delegates and events. All these changes together should improve Haxe/C# interoperability a lot.

Besides these huge additions, C# target also received much of bug-fixing and other stabilization-related love.

Generic build

This is also something I already wrote about recently. This is a new type building facility available through macros and I think this is kind of huge feature and will have many uses (i describe one in my post).

The basic idea is that you can change or even build a completely new type for each instance of a generic (parametrized) type. You specify the build function with @:genericBuild meta, just like with @:build-macros, but instead of returning array of fields for the type being build, the build function returns a whole new type. And the most important is that the @:genericBuild-macro will be executed for each combination of type parameters, so you can modify the type depending on that.

That allows, for example, to re-implement @:generic, as Simon Krajewski has shown in the original pull request, or replace MacroType with a better syntax, or implement a read-only type builder as I did myself. Read my original article for more details.

ECheckType syntax

Those who work with macros probably know about ECheckType - the wrapper expression that allows to check whether given expression unifies with given type. However, despite being an expression, there was no actual syntax to write it in your code and you could only generate it with a macro. Now there's a new syntax for that: (expr : MyType), where expr is any expression and MyType is, well, your type :-) The result of this expression is typed as the given type, so it may be used to type the Dynamic expression, or trigger abstract @:to conversion. For example:

static function getData():Dynamic return "hello";

static function main()
{
    trace((getData() : String).toLowerCase());
}

Typed AST

This is an addition to the Haxe macro API. You can now access the AST after it's fully processed and typed but before actual output generation. The data structure for that is TypedExpr from haxe.macro.Type module that was just a reference in 3.0, now it's a structure similar to haxe.macro.Expr but having type information and making much more sense from a program flow point of view. For example, instead of working with some identifiers, you work with local vars. This has various uses, such as static analysis or custom target generation, as shown by Heinz Hölzer's hx2python.

Context.getExpectedType

Talking about macro API, there is also another nice addition: Context.getExpectedType function returns the type of an expression being expected on the macro call site. For example, it returns Int for this macro call: var a:Int = myMacroCall();

So...

Haxe 3.1 release seems to be more stable than 3.0, considering how many bugs was fixed and the new features are really making the language more expressive. I'd like to give special thanks to Andy Li who made Haxe unit test suite up and running on Travis CI thus improving stability of the whole Haxe project.