Abstracts allow overloading of unary and binary operators by adding the @:op
metadata to class fields:
abstract MyAbstract(String) { public inline function new(s:String) { this = s; } @:op(A * B) public function repeat(rhs:Int):MyAbstract { var s:StringBuf = new StringBuf(); for (i in 0...rhs) s.add(this); return new MyAbstract(s.toString()); } } class Main { static public function main() { var a = new MyAbstract("foo"); trace(a * 3); // foofoofoo } }
By defining @:op(A * B)
, the function repeat
serves as the operator method for the multiplication *
operator when the type of the left value is MyAbstract
and the type of the right value is Int
. The usage is shown in line 17, which turns into the following code when compiled to JavaScript:
console.log(_AbstractOperatorOverload. MyAbstract_Impl_.repeat(a,3));
Similar to implicit casts with class fields, a call to the overload method is inserted where required.
The example repeat
function is not commutative: while MyAbstract * Int
works, Int * MyAbstract
does not. The @:commutative
metadata can be attached to the function to force it to accept the types in either order.
If the function should work only for Int * MyAbstract
, but not for MyAbstract * Int
, the overload method can be made static, accepting Int
and MyAbstract
as the first and second types respectively.
Overloading unary operators is similar:
abstract MyAbstract(String) { public inline function new(s:String) { this = s; } @:op(++A) public function pre() return "pre" + this; @:op(A++) public function post() return this + "post"; } class Main { static public function main() { var a = new MyAbstract("foo"); trace(++a); // prefoo trace(a++); // foopost } }
Both binary and unary operator overloads can return any type.
The @:op
syntax can be used to overload field access and array access on abstracts:
@:op([])
on a function with one argument overloads array read access.@:op([])
on a function with two arguments overloads array write access, with the first argument being the index and the second one being the written value.@:op(a.b)
on a function with one argument overloads field read access.@:op(a.b)
on a function with two arguments overloads field write access.abstract MyAbstract(String) from String { @:op([]) public function arrayRead(n:Int) return this.charAt(n); @:op([]) public function arrayWrite(n:Int, char:String) return this.substr(0, n) + char + this.substr(n + 1); @:op(a.b) public function fieldRead(name:String) return this.indexOf(name); @:op(a.b) public function fieldWrite(name:String, value:String) return this.split(name).join(value); } class Main { static public function main() { var s:MyAbstract = "example string"; trace(s[1]); // "x" trace(s[2] = "*"); // "ex*mple string" trace(s.string); // 8 trace(s.string = "code"); // "example code" } }
The method body of an @:op
function can be omitted, but only if the underlying type of the abstract allows the operation in question and the resulting type can be assigned back to the abstract.
abstract MyAbstractInt(Int) from Int to Int { // The following line exposes the (A > B) operation from the underlying Int // type. Note that no function body is used: @:op(A > B) static function gt(a:MyAbstractInt, b:MyAbstractInt):Bool; } class Main { static function main() { var a:MyAbstractInt = 42; if (a > 0) trace('Works fine, > operation implemented!'); // The < operator is not implemented. // This will cause an 'Cannot compare MyAbstractInt and Int' error: if (a < 100) {} } }
The @:op(a())
syntax can be used to overload function calls on abstracts. The metadata is attached to a function, and the signature of that function determines the signature of the call to the abstract. Multiple functions with different signatures can be annotated this way to support overloading:
abstract MyAbstract(Int) from Int { @:op(a()) public function callNoArgs():Int return this; @:op(a()) public function callOneArg(x:Int):Int return x + this; } class Main { static public function main() { var s:MyAbstract = 42; trace(s()); // uses callNoArgs, outputs 42 trace(s(1)); // uses callOneArg, ouputs 43 } }