I tried writing some basic code to see how Haxe feels, and I naturally started with a simple serializer.
I chose HashLink runtime, betting on it being maintained actively and on deep integration with the language.
However, I came across an issue almost instantly
var arrayNumber:Array<Float> = null;
I couldn’t find a way to validate that the object im trying to serialize is an array.
case value is Array<Int>:
type = ValueType.arrayInt;
After some search I find that:
But there’s also the second problem, which is that the information you’re looking for does not exist at runtime. At runtime, all arrays are Array<Dynamic>. Maybe points started out as an Array<Vector3>, maybe it was Array<Vector2>, or maybe even Array<Array<String>> (though I hope not). Type.typeof() can’t tell the difference.
This left me scratching my head. I nearly instantly abandoned the idea of using Haxe for anything, but I came back (2 minutes ago) to clarify this, before fully giving up on it.
From what I understood this issue is related to Haxe being a “meta” language, so it has no RTTI of its own, and it can’t guarantee that certain constructs like array type info are present in target languages. However.
HashLink is Haxe first runtime, as I understand. So it would make sense for the runtime built specifically to host Haxe, have all the necessary information to resolve any Haxe types at runtime.
This makes me think about this duality, on one hand there is Haxe + Hashlink that could be very tightly coupled and allow first class support for many features common in other languages, and on the other Haxe + multi target, with all it’s issues.
So the question may be something like - If I don’t care about anything else other than using HashLink as the VM/Compiler when working with Haxe, should I suffer from the same issues that Haxe+MT has? Or did I completely misunderstand everything?
Could you clarify what issues you’re running into? Haxe supports RTTI, and a few libraries implement custom serializers (the standard library already provides one).
While Haxe has a strong type system, the usage of Any and Dynamic bypass it completely. Ideally, you’d use it only when handling untrusted input, which you then parse into something your program understands. It can be convenient when writing very dynamic programs as well, at the cost of type safety.
Optionally, you can also check the type of inner elements.
These languages have a runtime, retain all the type information at runtime while still being strongly typed. To my understanding Haxe still behaves like a dynamically typed lua/python despite having HashLink runtime.
I don’t know how to convey this clearer - i see it as an issue, that despite having a fast VM/Runtime Haxe does not allow the conveniences and features other languages with similar runtimes have.
Your suggestion “do it differently/use something else” makes sense for Haxe without a runtime, but when there is one, it doesn’t because the runtime could handle this.
As I understand it, Haxe is meant to be primarily a multi-target language. What you can do with Haxe’s core feature set is supposed to be available to most, if not all, targets. With that in mind, the least capable among its targets really strongly influences what Haxe can expose to all of the other targets.
So, it’s my opinion that you are correctly understanding that, even if a particular target is designed with Haxe in mind, and that target could theoretically expose more powerful features than other targets, it’s not likely that core parts of the language will include those advanced features.
That being said, Haxe often exposes target-specific APIs. These are not part of the core language available to other targets, but they can expose some of the more powerful features of a particular target to Haxe.
In the case of HashLink, that’s the hl package. I’ve noticed that there are some special array classes specifically for HashLink, but I haven’t looked into them deeply. I don’t think that they’re compatible with a regular Haxe Array necessarily, but they might still be worth looking into.
Testing if v is an Array: v is Array … see Try Haxe!
If you want to know if v is an Array<T> for some given T then generally speaking, that is not possible at runtime, because most targets erase parameters at runtime (like Java, that you mentioned before). Then again, this is not something you need for serialization, because you’ll have to introspect every item individually anyway.
Runtime type information doesn’t come for free. Since HashLink is primarily meant for game development, where performance matters and data structures are often tweaked for compactness to achieve better cache locality, I wouldn’t hold my breath for this to happen.
Also Haxe not being strongly coupled to a fat runtime like most managed languages are, is a unique quality of Haxe that is a strength, not a weakness.
Again, HashLink is designed around various performance considerations, like decreasing the overhead of data types that are quite unique to Haxe (e.g. anonymous objects and enums). RTTI is at best orthogonal to that goal, and may even be at conflict with it at times. It’s not a primary concern and it’s not really a good default.
You will also find that Haxe has many compile time features (such as abstract data types) that disappear entirely at runtime. Dragging around all of this is just not practical.
Instead, you should clearly define a problem, determine how much information you can get at compilete time (always preferable) and then see what’s left to close the gap at runtime and you may then have to figure out a platform specific way to get that information, or see how far you can get with the cross platform reflection capabilities:
Perhaps the problem lies herein. Yes, you’ve picked an intersting problem, but it’s not really all that natural. Haxe is a pragmatic language, aimed to solve real world problems, primarily those of a small game studio in Bordeaux, although other community members are using it for other purposes. Building a reflection based serializer is not a real world problem, especially in a language that has one in the standard library: haxe/std/haxe/Serializer.hx at development · HaxeFoundation/haxe · GitHub
The std serializer has been there since Haxe 1 IIRC, when Haxe was a far less capabable language than it is today. It is comparatively slow and relies on reflection, which on many targets makes the generators output extra RTTI, which - as established above - doesn’t come for free.
These days you will more commonly find serializer libraries that are built via macros at compile time, using compile time type inspection, which has full information and can provide meaningful errors (e.g. if you’re trying to serialize a function, the macro can tell you that that’s not going to happen - that or if the serialization is tied to some stateful connection, it could serialize some ID where the deserialized function will use said ID to perform some form of RPC call).
There’s also a bunch of serialization libraries here, all of which are reflection based as far as I could tell at first glance: Tag: serialization
In conclusion I would put it to you that Haxe’s strength lies in the fact that if you want to have behavior that is derived from the structure of the data, then rather than discovering it at runtime and then branching (in essence building an interpreter), you can inspect it at compile time and generate the appropriate code (in essence building a compiler).
And I would really advise you to start with a real world problem and then build some sort of solution from there. Haxe may not even be the best choice. It definitely isn’t if you don’t properly consider how its more advanced and often quite unique features are best leveraged to tackle said problem. If you just try to use it as a dorky version of Java, you definitely will get nowhere