암시적 변환

암시적 변환 (Implicit Conversions)

암시적 변환은 scala.Conversion 클래스의 given 인스턴스로 정의해요. 이 클래스는 scala 패키지에 다음과 같이 정의되어 있어요.

abstract class Conversion[-T, +U] extends (T => U):
  def apply (x: T): U

예를 들어 String에서 Token으로의 암시적 변환은 이렇게 써요.

given Conversion[String, Token]:
  def apply(str: String): Token = new KeyWord(str)

별칭(alias)을 쓰면 더 간결하게 표현할 수 있어요.

given Conversion[String, Token] = new KeyWord(_)

암시적 변환은 세 가지 상황에서 컴파일러에 의해 자동으로 적용돼요.

  1. e의 타입이 T인데 T가 그 식의 기대 타입 S에 부합하지 않을 때.
  2. e가 타입 T인 선택 e.m에서, Tm이라는 멤버를 정의하지 않을 때.
  3. e가 타입 T인 애플리케이션 e.m(args)에서, T가 이름 m의 멤버를 (일부) 정의하지만 그중 어느 것도 인자 args에 적용될 수 없을 때.

첫 번째 경우 컴파일러는 타입 T의 인자를 타입 S에 매핑하는 given scala.Conversion 인스턴스를 찾아요. 두 번째와 세 번째 경우에는 args가 있으면 그에 적용될 수 있는 멤버 m을 정의하는 타입으로 타입 T의 인자를 매핑하는 given scala.Conversion 인스턴스를 찾아요. 그런 인스턴스 C를 찾으면 식 eC.apply(e)로 대체돼요.

출처: Scala 3 Reference

본문

예시 (Examples)

Predef 패키지에는 기본 숫자 타입을 java.lang.Number의 하위 클래스에 매핑하는 "auto-boxing" 변환들이 들어 있어요. 예를 들어 Int에서 java.lang.Integer로의 변환은 다음과 같이 정의할 수 있어요.

given int2Integer: Conversion[Int, java.lang.Integer] =
  java.lang.Integer.valueOf(_)

"마그넷(magnet)" 패턴은 메서드의 많은 변형을 표현할 때 쓰이기도 해요. 메서드의 오버로드 버전을 정의하는 대신, 메서드가 특별히 정의된 "마그넷" 타입의 인자를 하나 이상 받게 하고, 다양한 인자 타입을 그 타입으로 변환할 수 있게 해요. 예시를 볼게요.

object Completions:

  // The argument "magnet" type
  enum CompletionArg:
    case Error(s: String)
    case Response(f: Future[HttpResponse])
    case Status(code: Future[StatusCode])

  object CompletionArg:

    // conversions defining the possible arguments to pass to `complete`
    // these always come with CompletionArg
    // They can be invoked explicitly, e.g.
    //
    //   CompletionArg.fromStatusCode(statusCode)

    given fromString    : Conversion[String, CompletionArg]               = Error(_)
    given fromFuture    : Conversion[Future[HttpResponse], CompletionArg] = Response(_)
    given fromStatusCode: Conversion[Future[StatusCode], CompletionArg]   = Status(_)
  end CompletionArg
  import CompletionArg.*

  def complete[T](arg: CompletionArg) = arg match
    case Error(s) => ...
    case Response(f) => ...
    case Status(code) => ...

end Completions

이 설정은 complete를 단순히 오버로드하는 것보다 복잡하지만, 일반 오버로드를 쓸 수 없을 때(위 경우처럼 Future[...] 인자를 받는 오버로드 메서드를 둘 가질 수 없으니까)나 일반 오버로드가 변형의 조합 폭발을 일으킬 때는 여전히 유용할 수 있어요.