Scala 2 Implicits와의 관계
Scala 2 Implicits와의 관계 (Relationship with Scala 2 Implicits)
Scala 3의 새로운 컨텍스트 추상화 기능은 많지만 전부는 아니에요. 그중 상당수가 Scala 2의 implicits로 대응될 수 있어요. 이 페이지에서는 새 기능과 옛 기능 사이의 관계를 정리해 드릴게요.
본문
Scala 2 Implicits로 Scala 3 컨텍스트 추상화 개념 흉내 내기 (Simulating Scala 3 Contextual Abstraction Concepts with Scala 2 Implicits)
Given 인스턴스 (Given Instances)
given 인스턴스는 implicit object, implicit class, implicit method의 조합으로 대응시킬 수 있어요.
given intOrd: Ord[Int] { ... }
implicit object intOrd extends Ord[Int] { ... }
given listOrd: [T] => (ord: Ord[T]) => Ord[List[T]] { ... }
class listOrd[T](implicit ord: Ord[T]) extends Ord[List[T]] { ... }
final implicit def listOrd[T](implicit ord: Ord[T]): listOrd[T] =
new listOrd[T]
- 파라미터가 없는 given 인스턴스는 implicit object로 대응돼요. 예를 들어 ~는 ~로 대응돼요.
- 매개변수화된 given은 클래스와 implicit method의 조합으로 대응돼요. 예를 들어 ~는 ~로 대응돼요.
- 별칭 given은 implicit method나 implicit lazy val로 대응돼요. 별칭이 타입 파라미터도 컨텍스트 파라미터도 없으면 lazy val로 취급돼요. 다만 오른쪽이 단순한 참조인 경우는 캐시 없이 그 참조로의 전달자(forwarder)를 쓸 수 있어요.
예를 들어 볼게요.
given global: ExecutionContext = new ForkJoinContext()
val ctx: Context
given Context = ctx
위 코드는 다음과 같이 대응돼요.
final implicit lazy val global: ExecutionContext = new ForkJoinContext()
final implicit def given_Context = ctx
익명 Given 인스턴스 (Anonymous Given Instances)
익명 given 인스턴스는 컴파일러가 합성한 이름을 얻는데, 그 이름은 구현한 타입(들)에서 재현 가능한 방식으로 생성돼요. 예를 들어 위의 IntOrd와 ListOrd given의 이름을 생략하면 다음 이름이 대신 합성돼요.
given given_Ord_Int: Ord[Int] { ... }
given given_Ord_List: [T] => (ord: Ord[T]) => Ord[List[T]] { ... }
합성된 타입 이름은 다음으로 이루어져요.
- 접두사
given_, - 구현한 타입(들)의 단순 이름(들) — 접두사를 모두 빠뜨리고,
- 그 타입들의 최상위 인자 타입 생성자(top-level argument type constructor)의 단순 이름(들).
튜플은 투명한 것으로 취급돼요. 즉 F[(X, Y)] 타입은 F_X_Y라는 합성 이름을 얻어요. 직접 구현된 함수 타입 A => B는 A_to_B로 표현돼요. 다른 타입 생성자의 인자로 쓰이는 함수 타입은 Function으로 표현돼요.
Using 절 (Using Clauses)
Using 절은 대체로 Scala 2의 implicit 파라미터 절에 대응돼요. 예를 들어
def max[T](x: T, y: T)(using ord: Ord[T]): T
는 Scala 2에서 이렇게 쓰여요.
def max[T](x: T, y: T)(implicit ord: Ord[T]): T
주요 차이는 그런 파라미터의 적용(application)에 관해 있어요. using 절의 파라미터에 대한 명시적 인자는 정의 문법을 그대로 반영해 (using ...)로 써야 해요. 예를 들어 max(2, 3)(using IntOrd)처럼요. Scala 2는 대신 일반적인 적용 max(2, 3)(IntOrd)를 써요. Scala 2 문법에는 본질적인 모호성과 제약이 있는데, 새 문법은 그것을 해결해요. 예를 들어 여러 개의 implicit 파라미터 목록은 옛 문법에서는 쓸 수 없었어요. 물론 "Aux" 패턴에서 보조 객체를 써서 흉내 낼 수는 있었지만요.
summon 메서드는 Scala 2의 implicitly에 대응돼요. Shapeless의 the 메서드와 정확히 같아요. summon(또는 the)과 implicitly의 차이는, summon은 요청한 타입보다 더 정확한 타입을 반환할 수 있다는 점이에요.
컨텍스트 Bound (Context Bounds)
컨텍스트 bound는 두 언어 버전에서 같아요. 각각에 해당하는 형태의 implicit 파라미터로 확장돼요.
Note: 마이그레이션을 쉽게 하기 위해, Scala 3의 컨텍스트 bound는 한정된 기간 동안 옛 방식의 implicit 파라미터로 대응되는데, 그 인자는 using 절이나 일반 인자 목록 어느 쪽으로든 전달할 수 있어요. 옛 방식의 implicit이 deprecated되면 컨텍스트 bound는 대신 using 절로 대응될 거예요.
확장 메서드 (Extension Methods)
확장 메서드는 Scala 2에 직접적인 대응이 없지만, implicit class로 흉내 낼 수 있어요. 예를 들어 확장 메서드
extension (c: Circle)
def circumference: Double = c.radius * math.Pi * 2
는 어느 정도 다음과 같이 흉내 낼 수 있어요.
implicit class CircleDecorator(c: Circle) extends AnyVal {
def circumference: Double = c.radius * math.Pi * 2
}
given 인스턴스에서 구현되는 트레이트의 추상 확장 메서드에는 Scala 2에 직접적인 대응이 없어요. 그것을 흉내 내는 유일한 방법은 implicit class를 import를 통해 제공하는 거예요. Simulacrum 매크로 라이브러리가 어떤 경우에는 이 과정을 자동화할 수 있어요.
타입 클래스 파생 (Type Class Derivation)
타입 클래스 파생은 Scala 2 언어에 직접적인 대응이 없어요. 비슷한 기능은 Shapeless, Magnolia, scalaz-deriving 같은 매크로 기반 라이브러리로 얻을 수 있어요.
컨텍스트 함수 타입 (Context Function Types)
컨텍스트 함수 타입은 Scala 2에 대응이 없어요.
Implicit By-Name 파라미터 (Implicit By-Name Parameters)
Implicit by-name 파라미터는 Scala 2에서 지원되지 않지만, Shapeless의 Lazy 타입으로 어느 정도 흉내 낼 수 있어요.
Scala 3에서 Scala 2 Implicits 흉내 내기 (Simulating Scala 2 Implicits in Scala 3)
Implicit 변환 (Implicit Conversions)
Scala 2의 implicit 변환 메서드는 Scala 3에서 scala.Conversion 클래스의 given 인스턴스로 표현할 수 있어요. 예를 들어
implicit def stringToToken(str: String): Token = new Keyword(str)
대신 이렇게 쓸 수 있어요.
given stringToToken: Conversion[String, Token]:
def apply(str: String): Token = KeyWord(str)
또는
given stringToToken: Conversion[String, Token] = KeyWord(_)
Implicit Class (Implicit Classes)
Scala 2의 implicit class는 확장 메서드를 정의하는 데 자주 쓰이는데, 확장 메서드는 Scala 3에서 직접 지원돼요. implicit class의 다른 용도는 일반 클래스와 given Conversion 인스턴스의 쌍으로 흉내 낼 수 있어요.
Implicit 값 (Implicit Values)
Scala 2의 implicit val 정의는 Scala 3에서 일반 val 정의와 별칭 given으로 표현할 수 있어요. 예를 들어 Scala 2의
lazy implicit val pos: Position = tree.sourcePos
는 Scala 3에서 이렇게 표현할 수 있어요.
lazy val pos: Position = tree.sourcePos
given Position = pos
추상 Implicits (Abstract Implicits)
Scala 2의 추상 implicit val이나 def는 Scala 3에서 일반 추상 정의와 별칭 given으로 표현할 수 있어요. 예를 들어 Scala 2의
implicit def symDecorator: SymDecorator
는 Scala 3에서 이렇게 표현할 수 있어요.
def symDecorator: SymDecorator
given SymDecorator = symDecorator
구현 상태와 일정 (Implementation Status and Timeline)
Scala 3 구현은 Scala 2의 implicits와 새로운 추상화를 모두 구현해요. 사실 Scala 2의 implicits 지원은 2.13과 Scala 3 사이의 공통 언어 부분집합의 필수 요소예요. 새로운 추상화로의 마이그레이션은 자동 재작성(automatic rewriting)을 이용할 수 있게 함으로써 지원될 거예요.
채택 패턴에 따라 옛 방식의 implicit은 Scala 3.0 다음 버전부터 deprecated되기 시작할 수 있어요.