Every language has its “Dark Corners”, the odd little gotchas or head scratchers that can soak up a bit of time trying to understand and work around.
Haxe is no different.
Here are a couple that I’ve encountered over the last couple of days….
Strange import issue.
package org.pixelami.binding;
import org.pixelami.binding.BindingBuilder;
@:autoBuild(org.pixelami.binding.BindingBuilder.build())
interface IBindableModel {}
Produces this error …
haxe.macro.#Context has no field getLocalClass
Removing the import compiles without incident.
package org.pixelami.binding;
@:autoBuild(org.pixelami.binding.BindingBuilder.build())
interface IBindableModel {}
I’m not even going to spend any time trying to understand that one … just safely file it under “dark corners”.
The next one I believe is very tricky for Haxe newcomers and for this reason I would like to see the language try to reduce the room for ambiguity.
Basically this next “dark corner” is an issue caused by the compiler getting confused about inferring the correct type. Hardly a “big” issue one might think, but when mixed with Haxe’s public inner types – things can get pretty tricky pretty quickly, and the solutions are not always obvious.
Here was my baptism of fire. (in pseudo code)
import haxe.macro.Type;
import haxe.macro.Expr;
import haxe.macro.Context;
.....
var t:ComplexType = TPath({ name : "String", pack : [], params : [], sub : null });
var kind = FVar(t, expr);
Produces this ominous error…
... line 6 : haxe.macro.ComplexType should be haxe.macro.VarAccess
... line 6 : For function argument 'read'
Basically the compiler didn’t like they way I was constructing FVar.
Now luckily I had been working with the ClassField typedef the day before and had noticed that ClassField has a kind field that expects a FieldKind enum that actually defines a different FVar enum.
FVar(read:VarAccess, write:VarAccess).
The FVar that I wanted to be using was the FVar defined in FieldType with the signature
FVar(t:ComplexType,?e:Expr)
Luckily – because I was aware of this other FVar the error message I was seeing did make some sense, it seemed the compiler was incorrectly inferring the type of FVar.
If I hadn’t known about the other FVar – well – I’m thinking I would have been scratching my head, Googling and finally composing an email to the mailing list.
Ok, I thought, I’ll give the compiler a helping hand … I tried…
import haxe.macro.Type;
import haxe.macro.Expr;
import haxe.macro.Context;
.....
var t:ComplexType = TPath({ name : "String", pack : [], params : [], sub : null });
var kind:FieldType = FVar(t, expr);
But this didn’t work…. inference seemingly thwarted by the imports ?
Ok, taking another look … I could see the problem was that I had these two imports…
import haxe.macro.Type;
import haxe.macro.Expr;
And both these Types define ‘conflicting’ inner FVar definitions… hmmm… Haxe – “t’es coquin quoi?”.
If you are going to allow this kind of inference then inner types should have to be explicitly referenced via their full inner type path (no ?).
Ok, let’s fix this PITA. Once you know what the problem is the solution is to give the compiler the explicit hint that it needs.
import haxe.macro.Type;
import haxe.macro.Expr;
import haxe.macro.Context;
.....
var t:ComplexType = TPath({ name : "String", pack : [], params : [], sub : null });
var kind:FieldType = FieldType.FVar(t, expr);
Or, of course…
You could just change the order of the imports … hmmm.
import haxe.macro.Expr;
import haxe.macro.Type;
import haxe.macro.Context;
.....
var t:ComplexType = TPath({ name : "String", pack : [], params : [], sub : null });
var kind:FieldType = FVar(t, expr);
Now as I said in the intro – all Languages have their dark corners, but let’s think about that for a moment.
When I first saw Enum fields just hanging loose inside Haxe code with absolutely no clue as to where they came from, well – let’s say – I was a little concerned. Not least because it was bloody impossible to tell where they were declared, secondly I could see that potential collisions were going to be easy and that this was going to lead to time being wasted looking for the source of the collisions.
To digress slightly here … all time wasted on things like this means less time delivering features. In a commercial team this can lead to less product features delivered which can lead to a loss of revenue which can lead to having to cut the dev team which can lead to little Timmy not getting his new pair of shoes for Xmas because Daddy/Mummy lost their job because a critical release failed due to troubleshooting weird import issues in a Haxe project.
In short Haxe’s lack of strictness in this area could cost little Timmy his new shoes – and so I say – “Please, for the sake of little Timmy’s feet – fix this !”
To my mind – if Haxe is going to have public inner types (and that’s another debate) then the compiler should probably enforce the use of their full inner type namespace.
e.g. Expr.FieldType.Fvar , Type.FieldKind.FVar.
Ok, it’s more characters to type – but it removes ambiguity – and that has to be the overriding priority when designing robustness. Remember – little Timmy’s feet are always growing and if his shoes become too tight – well – this can lead to problems in later life.
Another alternative could be that the compiler simply throws and error when it detects a potential type ambiguity due to name collision of inner types… and then devs just get into the habit of being more explicit when referencing them.