2.7.3 연산자 오버로딩(Operator Overloading)
2.7.3 연산자 오버로딩(Operator Overloading)
추상은 클래스 필드에 @:op 메타데이터를 추가해 단항·이항 연산자를 오버로드할 수 있어요:
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
}
}
@:op(A * B)를 정의함으로써, 왼쪽 값의 타입이 MyAbstract이고 오른쪽 값의 타입이 Int일 때 곱셈 * 연산자의 연산자 메서드로 repeat 함수가 동작해요. 사용은 17행에 나와 있고, JavaScript로 컴파일하면 다음 코드로 바뀝니다:
console.log(_AbstractOperatorOverload.
MyAbstract_Impl_.repeat(a,3));
클래스 필드가 있는 암시적 캐스팅과 비슷하게, 필요할 때 오버로드 메서드 호출이 삽입됩니다.
예제의 repeat 함수는 교환 법칙이 성립하지 않아요. MyAbstract * Int는 되지만 Int * MyAbstract는 되지 않습니다. @:commutative 메타데이터를 함수에 붙이면 어느 순서든 타입을 받도록 강제할 수 있어요.
함수가 Int * MyAbstract에만, 즉 MyAbstract * Int에는 동작하지 않아야 한다면, 오버로드 메서드를 static으로 만들어 Int와 MyAbstract를 각각 첫·두 번째 타입으로 받게 할 수 있어요.
단항 연산자 오버로딩도 비슷합니다:
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
}
}
이항·단항 연산자 오버로드는 모두 어떤 타입이든 반환할 수 있어요.
Haxe 4.0.0 이후 —
@:op문법으로 추상의 필드 접근과 배열 접근을 오버로드할 수 있습니다:
- 인자 하나를 가진 함수의
@:op([])는 배열 읽기 접근을 오버로드해요.- 인자 두 개를 가진 함수의
@:op([])는 배열 쓰기 접근을 오버로드하며, 첫 인자는 인덱스, 둘째 인자는 쓰여질 값입니다.- 인자 하나를 가진 함수의
@:op(a.b)는 필드 읽기 접근을 오버로드해요.- 인자 두 개를 가진 함수의
@:op(a.b)는 필드 쓰기 접근을 오버로드합니다.
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"
}
}
기반 타입 연산 노출(Exposing underlying type operations) —
@:op함수의 메서드 본문은 생략할 수 있어요. 단 추상의 기반 타입이 해당 연산을 허용하고, 결과 타입이 추상에 다시 할당 가능할 때만 가능합니다.
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) ...
}
}
본문
@:op 연산자 오버로딩
@:op(연산자) 메타데이터를 메서드에 붙여 연산자를 오버로드할 수 있어요. @:op(A * B)는 왼쪽이 추상 타입, 오른쪽이 Int일 때의 곱셈을 정의합니다. 표기에서 A는 추상 타입, B는 다른 피연산자 타입을 나타내요.
교환 법칙과 방향
오버로드된 연산자는 기본적으로 교환 법칙이 성립하지 않습니다. @:commutative를 붙이면 어느 순서든 허용하고, static 메서드로 만들면 방향을 고정할 수 있어요.
단항 연산자
@:op(++A)와 @:op(A++) 같은 단항 연산자도 오버로드할 수 있어요. 전자는 전위, 후자는 후위 연산을 정의합니다.
필드·배열 접근 오버로드
Haxe 4.0.0부터 @:op([])로 배열 읽기·쓰기, @:op(a.b)로 필드 읽기·쓰기 접근을 오버로드할 수 있습니다.
기반 타입 연산 노출
@:op 함수의 본문을 생략하면 기반 타입의 연산을 그대로 노출할 수 있어요. 예를 들어 @:op(A > B) static function gt(...)는 기반 Int 타입의 > 연산을 노출합니다.