2.7.2 암시적 캐스팅(Implicit Casts)

2.7.2 암시적 캐스팅(Implicit Casts)

클래스와 달리 추상은 암시적 캐스팅을 정의할 수 있어요. 두 종류의 암시적 캐스팅이 있습니다:

  • 직접(Direct): 추상 타입을 다른 타입으로(또는 다른 타입에서 추상으로) 직접 캐스팅할 수 있게 해줍니다. 추상 타입에 tofrom 규칙을 추가해 정의하며, 추상의 기반 타입과 유니파이되는 타입에만 허용됩니다.
  • 클래스 필드(Class field): 특별한 캐스트 함수 호출을 통한 캐스팅을 허용합니다. 이 함수들은 @:to@:from 메타데이터로 정의되며, 모든 타입에 허용됩니다.

다음 코드 예제는 직접 캐스팅의 예를 보여줘요:

abstract MyAbstract(Int) from Int to Int {
  inline function new(i:Int) {
    this = i;
  }
}

class Main {
  static public function main() {
    var a:MyAbstract = 12;
    var b:Int = a;
  }
}

MyAbstractfrom Intto Int로 선언합니다. 이는 Int에서 할당될 수 있고 Int에 할당될 수 있다는 뜻이에요. 9·10행에서 Int12MyAbstract 타입 변수 a에 할당하고(from Int 덕분), 그 추상을 Int 타입 변수 b에 다시 할당합니다(to Int 덕분).

클래스 필드 캐스팅은 의미론은 같지만 완전히 다르게 정의됩니다:

abstract MyAbstract(Int) {
  inline function new(i:Int) {
    this = i;
  }

  @:from
  static public function fromString(s:String) {
    return new MyAbstract(Std.parseInt(s));
  }

  @:to
  public function toArray() {
    return [this];
  }
}

class Main {
  static public function main() {
    var a:MyAbstract = "3";
    var b:Array<Int> = a;
    trace(b); // [3]
  }
}

@:from을 정적 함수에 추가하면, 그 함수는 인자 타입에서 추상으로의 암시적 캐스트 함수가 됩니다. 이 함수들은 추상 타입의 값을 반환해야 하고, static으로 선언되어야 해요.

마찬가지로 @:to를 함수에 추가하면, 그 함수는 추상에서 반환 타입으로의 암시적 캐스트 함수가 됩니다.

위 예제에서 fromString 메서드는 값 "3"MyAbstract 타입 변수 a에 할당하게 하고, toArray 메서드는 그 추상을 Array<Int> 타입 변수 b에 할당할 수 있게 해요.

이런 종류의 캐스팅을 사용하면, 필요할 때 캐스트 함수 호출이 삽입됩니다. JavaScript 출력을 보면 분명해져요:

var a = _ImplicitCastField.MyAbstract_Impl_.fromString("3");
var b = _ImplicitCastField.MyAbstract_Impl_.toArray(a);

캐스트 함수 두 개를 인라인(inlining)하면 더 최적화할 수 있고, 출력은 다음과 같이 바뀝니다:

var a = Std.parseInt("3");
var b = [a];

적어도 하나가 추상인 타입 A를 타입 B에 할당할 때의 선택 알고리즘(selection algorithm) 은 간단합니다:

  1. A가 추상이 아니면 3으로 간다.
  2. AB를 허용하는 to 변환을 정의하면 6으로 간다.
  3. B가 추상이 아니면 5로 간다.
  4. BA를 허용하는 from 변환을 정의하면 6으로 간다.
  5. 멈춤, 유니피케이션 실패.
  6. 멈춤, 유니피케이션 성공.

(선택 알고리즘 흐름도)

설계상 암시적 캐스팅은 전이적(transitive)이지 않습니다. 다음 예제가 보여줘요:

abstract A(Int) {
  public function new()
    this = 0;

  @:to public function toB() return new B();
}

abstract B(Int) {
  public function new()
    this = 0;

  @:to public function toC() return new C();
}

abstract C(Int) {
  public function new()
    this = 0;
}

class Main {
  static public function main() {
    var a = new A();
    var b:B = a; // valid, uses A.toB
    var c:C = b; // valid, uses B.toC
    var c:C = a; // error, A should be C
  }
}

A에서 B, B에서 C로의 개별 캐스팅은 허용되지만, A에서 C로의 전이 캐스팅은 허용되지 않아요. 이는 모호한 캐스트 경로를 피하고 간단한 선택 알고리즘을 유지하기 위함입니다.

출처: Implicit Casts

본문

두 종류의 암시적 캐스팅

추상은 직접 캐스팅(from/to 규칙)과 클래스 필드 캐스팅(@:from/@:to 메서드)을 통해 암시적 캐스팅을 정의할 수 있어요. 직접 캐스팅은 기반 타입과 유니파이되는 타입에만 쓸 수 있고, 클래스 필드 캐스팅은 모든 타입에 쓸 수 있습니다.

@:from과 @:to

  • @:from을 붙인 정적 함수는 인자 타입에서 추상으로의 암시적 캐스트를 정의해요. 추상 타입을 반환해야 하고 static이어야 합니다.
  • @:to를 붙인 함수는 추상에서 반환 타입으로의 암시적 캐스트를 정의합니다.

사용 시점에 캐스트 함수 호출이 삽입되며, 인라인하면 더 최적화된 코드가 생성됩니다.

선택 알고리즘

할당 시 추상 변환을 선택하는 규칙은 간단해요. Ato 변환, Bfrom 변환을 순서대로 확인해 하나라도 성립하면 성공, 아니면 실패입니다.

전이성 없음

암시적 캐스팅은 전이적이지 않아요. ABBC가 각각 허용되어도 AC는 허용되지 않습니다. 이는 캐스트 경로의 모호성을 피하고 단순한 알고리즘을 유지하기 위한 설계예요.

더 알아보기