Operator overloading in Haxe 3.0

436 views
Skip to first unread message

Gary Paluk

unread,
Mar 26, 2013, 9:57:37 PM3/26/13
to haxe...@googlegroups.com
I'm new to Haxe and for my first exercise, I'm trying to implement operator overloading in Haxe 3.0, I've created a dummy class here:

https://github.com/PluginIO/plugin-math-haxe/blob/master/src/plugin/math/algebra/Tuple.hx

When I run this code I get the Tuple object which corresponds to the types in the code, however, it's obvious that I aught to be able to return the AVector type.

var v1: AVector = new AVector( 0, 1, 2 );
var v2: AVector = new AVector( 10, 20, 30 );

var v3: AVector = v1 + v2;
trace
( v3 );

// traces Main.hx:37: [object Tuple]


Am I missing something here?? Please don't tell me it requires casting?!

Max Chase

unread,
Mar 27, 2013, 1:35:48 AM3/27/13
to haxe...@googlegroups.com
Because AVector is an abstract type, it doesn't exist at runtime, which is when the trace statement fires.  AVector exists to provide operator overloads (and a string representation) for Tuples.  In addition, it's meant to be interchangeable with Tuples, so, what exactly did you want an AVector for, if you're not calling operators or performing implicit casts?

Gary Paluk

unread,
Mar 27, 2013, 6:30:24 AM3/27/13
to haxe...@googlegroups.com
Hi Max,

Thanks for replying, I'm doing those things because I'm kinda walking blind (First time using Haxe). I'm not sure how this should be done but my intuition was to avoid casting these objects at all costs due to the wasteful overhead. I know this is a big ask, but is there any chance of giving me a code example of how you believe this should be done? To me, right now, this whole thing looks convoluted and I'm 10 minutes from considering it a failure and I'm really hoping that someone can tell me that I'm wrong :)

Max Chase

unread,
Mar 27, 2013, 1:55:09 PM3/27/13
to haxe...@googlegroups.com
I'm not seeing too much wrong with what you did. So, let's see if I can explain this... (note that I'm not that much more experienced in this area. I've only recently dug into this stuff, with the introduction of abstract types.) 

The compiler analyzes the source for correctness using compile-time types.  Some compile-time types much up with run-time types, and some compile-time types merely describe acceptable run-time types.  So, if you have a class, like Tuple, that's both a run-time type (because you can create instances) and a compile-time type (because you can do things like type function arguments with it).  

There are various ways to create types that only exist at compile time, but abstract types are interesting because, instead of solely matching against other types, they provide extra functionality to an underlying type.  When you use AVector, all of the operations get inlined, so the functions you've defined don't actually get called, but placed directly into the compiled source, when they're used.  Furthermore, if you've got a variable of type AVector, it's actually a Tuple at run-time, but you're still able to call the various operations on it at compile time.


It's all right to be confused by this; abstracts are a new, powerful feature, and they don't quite work right in all cases yet.  (To give you an idea of how powerful, the addition of abstracts allowed the creation of union types (useful for wrapping JS code without fussing with metadata), well-typed operator overloads like you've done, and they almost work for something like... machine-mediated Apps Hungarian notation; the bits of it that don't work are bugs.  I think I also saw someone trying to implement dimensional analysis with them.)

Juraj Kirchheim

unread,
Mar 27, 2013, 2:32:40 PM3/27/13
to haxe...@googlegroups.com
Because Haxe runs on loads of platforms, you need to become
comfortable with the fact, that at runtime, most of its type system is
simply gone (keeping track of this information would cause significant
overhead):

- Type parameters are lost
- Abstract types are replaced by their implementations
- typedefs are replaced by the types they alias
- Function types are lost
- Anonymous types are lost

You can inspect the compile time type of an expression like so:

$type(v3);//will give you AVector

None the less, as Max explained at runtime this information is gone.
This has nothing to do with operator overloading and there is simply
NO WAY to change that, as long as AVector is an abstract type. This
should make this rather clear:

trace($type(new AVector(1, 2, 3)));//reports AVector at compile
time and traces [object Tuple]

If you want a specific string representation at runtime, then the
underlying type (Tuple in this case) must provide it. You can then
simply implement the abstract type's toString method as `return
this.toString()`.

Regards,
Juraj

Gary Paluk

unread,
Mar 27, 2013, 3:42:30 PM3/27/13
to haxe...@googlegroups.com

Because of this disparity of class and abstract, I'm finding many conceptual gotchas. Take this for example, I've stripped everything down to the bare essentials (see attachment)


Why, when they abstracts are essentially the same, does the compiler consider that Foo has no x, y & z fields!? My goal here was to add another operator overload that would allow be to add a Vector3 to a Vector3 object AND a Foo to a Vector3. I mean, this has failed before I got there and I have no idea why. It is common practice to add a Vector to a Matrix for example, as well as to another Vector.

Any ideas?

Juraj Kirchheim

unread,
Mar 27, 2013, 4:43:36 PM3/27/13
to haxe...@googlegroups.com
This is the intended behavior. The type is called "abstract" for a reason. It hides the underlying type. 

By consequence, if you want to expose details about the underlying type, you will have to expose them explicitly.

I think this example should help you understand why:

abstract AVector(Array<Float>) {
     public var x(get, never):Float;
     public var y(get, never):Float;
     public var z(get, never):Float;
     public var length(get, never):Float;
     public inline function new(x:Float, y:Float, z:Float) this = [x, y, z];

     inline function get_x():Float return this[0];
     inline function get_y():Float return this[1];
     inline function get_z():Float return this[2];
     function get_length():Float return Math.sqrt(this[0]*this[0] + this[1]*this[1] + this[2]*this[2])
}

With such an implementation, it is imperative, that the underlying type remains hidden, so that user code doesn't modify it directly (imagine someone called pop on the array for example).

Notice that we define a length here, although the underlying type also has such a property - albeit with a completely different meaning.

Regards,
Juraj
Reply all
Reply to author
Forward
0 new messages