10.7 Reflection

10.7 Reflection

Haxe는 타입과 필드의 런타임 reflection을 지원합니다. 런타임 표현이 일반적으로 타깃마다 다르기 때문에 여기서는 특별한 주의가 필요합니다. reflection을 올바르게 사용하려면 어떤 종류의 연산이 지원되고 어떤 것이 지원되지 않는지 이해하는 것이 필요합니다. reflection의 동적 특성 때문에 이것이 항상 컴파일 타임에 결정될 수는 없습니다.

reflection API는 두 클래스로 구성됩니다:

사용 가능한 메서드는 ReflectType API에 자세히 설명되어 있습니다.

reflection은 강력한 도구가 될 수 있지만, 왜 문제를 일으킬 수도 있는지 이해하는 것이 중요합니다. 예를 들어 여러 함수가 String 인자를 기대하고 이를 타입이나 필드로 해석하려고 시도합니다. 이는 타이핑 오류에 취약합니다:

class Main {
  static function main() {
    trace(Type.resolveClass("Mian")); // null
  }
}

그러나 타이핑 오류가 없더라도 예기치 않은 동작에 쉽게 직면할 수 있습니다:

class Main {
  static function main() {
    // null
    trace(Type.resolveClass("haxe.Template"));
  }
}

여기서 문제는 컴파일러가 실제로 타입 haxe.Template을 "보지" 않으므로 출력으로 컴파일하지 않는다는 것입니다. 더 나아가 타입을 보더라도 reflection을 통해서만 사용되는 타입이나 필드를 죽은 코드 제거(dead code elimination)가 제거하는 문제가 발생할 수 있습니다.

또 다른 문제 세트는 설계상 여러 reflection 함수가 Dynamic 타입의 인자를 기대한다는 사실에서 나옵니다. 즉 컴파일러가 전달된 인자가 올바른지 확인할 수 없습니다.

다음 예는 callMethod로 작업할 때 흔한 실수를 보여줍니다:

class Main {
  static function main() {
    // 올바름
    Reflect.callMethod(Main, Reflect.field(Main, "f"), []);
    // 틀림
    // Reflect.callMethod(Main, "f", []);
  }

  static function f() {
    trace("Called");
  }
}

주석 처리된 호출은 함수 인자 func에 문자열 "f"를 할당하는데, 이 인자가 Dynamic으로 지정되므로 컴파일러가 받아들입니다.

reflection으로 작업할 때 좋은 조언은 이를 애플리케이션 또는 API 안의 몇 개 함수로 감싸고, 그 함수들을 그 외 타입 안전한 코드가 호출하는 것입니다. 예는 다음과 같습니다:

typedef MyStructure = {
  name:String,
  score:Int
}

class Main {
  static function main() {
    var data = reflective();
    // 이 지점에서 data는 MyStructure로 잘 타입됨
  }

  static function reflective():MyStructure {
    // 추출할 값들을 얻기 위해 여기서 reflection으로 작업
    return {
      name: "Reflection",
      score: 0
    }
  }
}

reflective 메서드는 내부적으로 reflection(그리고 그 문제로 Dynamic)을 많이 사용할 수 있지만, 반환값은 호출자가 타입 안전한 방식으로 사용할 수 있는 타입 구조입니다.

출처: Reflection

더 알아보기