3.3 제네릭(Generic)
3.3 제네릭(Generic)
보통 Haxe 컴파일러는 타입 매개변수를 가졌더라도 클래스나 함수를 하나만 생성해요. 이는 대상 언어의 코드 생성기가 타입 매개변수가 어떤 타입이든 될 수 있다고 가정해야 하는 자연스러운 추상화를 낳습니다. 생성된 코드는 타입 검사를 수행해야 할 수도 있는데, 이는 성능에 해로울 수 있어요.
클래스나 함수에 @:generic 메타데이터(metadata)를 붙이면 제네릭(generic) 으로 만들 수 있습니다. 컴파일러는 타입 매개변수 조합마다 이름이 맹글링(mangled)된 별개의 클래스나 함수를 생성해요. 이런 명세는 정적 대상(static targets)의 성능 중요 코드 구간에서 출력 크기가 커지는 대가로 성능 향상을 얻을 수 있습니다:
@:generic
class MyValue<T> {
public var value:T;
public function new(value:T) {
this.value = value;
}
}
class Main {
static public function main() {
var a = new MyValue<String>("Hello");
var b = new MyValue<Int>(42);
}
}
여기서 MyValue<String>처럼 명시적 타입을 보는 것이 다소 특이해 보일 수 있어요. 타입 추론(type inference)이 비슷한 상황을 자주 처리하기 때문이죠. 하지만 이 경우에는 명시적 타입이 필요해요. 컴파일러가 제네릭 클래스를 생성할 때 정확한 타입을 알아야 하기 때문입니다. JavaScript 출력이 그 결과를 보여줘요:
(function () { "use strict";
var Test = function() { };
Test.main = function() {
var a = new MyValue_String("Hello");
var b = new MyValue_Int(5);
};
var MyValue_Int = function(value) {
this.value = value;
};
var MyValue_String = function(value) {
this.value = value;
};
Test.main();
})();
MyValue<String>과 MyValue<Int>가 각각 MyValue_String과 MyValue_Int가 되었음을 확인할 수 있어요. 제네릭 함수의 경우도 비슷합니다:
class Main {
static public function main() {
method("foo");
method(1);
}
@:generic static function method<T>(t:T) {}
}
역시 JavaScript 출력이 명확하게 보여줘요:
(function () { "use strict";
var Main = function() { }
Main.method_Int = function(t) {
}
Main.method_String = function(t) {
}
Main.main = function() {
Main.method_String("foo");
Main.method_Int(1);
};
...
출처: Generic
본문
기본 동작과 @:generic
일반적으로 타입 매개변수가 있어도 컴파일러는 클래스/함수를 하나만 생성하고, 타입 매개변수를 임의 타입으로 취급합니다. @:generic 메타데이터를 붙이면 타입 매개변수 조합마다 별도의 클래스/함수를 만들어 성능을 높일 수 있어요. 이름은 맹글링되어 구분됩니다.
장단점
@:generic은 정적 대상에서 타입 검사 오버헤드를 줄여 성능을 올려주지만, 생성 코드가 커지고 명시적인 타입 지정이 필요해요. 출력 크기가 커지는 대가를 치르는 대신 성능 중요 코드에서 이득을 얻을 수 있답니다.
생성 코드 확인
JavaScript 출력을 보면 MyValue<String>이 MyValue_String으로, MyValue<Int>가 MyValue_Int로 각각 별도의 클래스로 생성됨을 확인할 수 있어요. 함수의 경우도 method_String·method_Int처럼 별도 함수로 생성됩니다.