Givens 가져오기

Givens 가져오기 (Importing Givens)

given 인스턴스를 어떻게 불러올지 정할 때는 일반적인 임포트와는 조금 다른 문법이 필요해요. 이 페이지에서는 given을 가져오는 여러 방식과, 그 규칙이 왜 마련됐는지, 그리고 기존 코드를 어떻게 옮겨갈 수 있는지 살펴볼게요.

출처: Scala 3 Reference

본문

given 인스턴스를 가져올 때는 와일드카드 선택자(wildcard selector)의 특별한 형태를 사용해요. 예를 들어 볼게요.

object A:
  class TC
  given tc: TC = ???
  def f(using TC) = ???

object B:
  import A.*
  import A.given
  ...

위 코드에서 object B 안의 import A.* 절은 A의 모든 멤버 중에서 given 인스턴스 tc만 빼고 가져와요. 반대로 두 번째 임포트인 import A.given은 그 given 인스턴스만 가져오죠. 이 두 임포트 절은 하나로 합칠 수도 있어요.

object B:
  import A.{given, *}
   ...

일반적으로 보통의 와일드카드 선택자 *는 given이나 확장(extension)을 제외한 모든 정의를 스코프로 가져오고, given 선택자는 모든 given(확장에서 비롯된 것을 포함해서)을 스코프로 가져와요.

이 규칙에서 얻는 이점은 크게 두 가지예요.

  • 스코프에 있는 given이 어디서 왔는지 훨씬 분명해져요. 특히, 긴 일반 와일드카드 임포트 목록 속에 given이 숨어 들어가는 일이 없어져요.
  • 다른 건 아무것도 가져오지 않고 given만 전부 가져올 수 있게 돼요. given은 익명일 수 있기 때문에 이름을 지정해서 임포트하는 방식이 실용적이지 못한데, 이 점이 특히 중요해요.

타입으로 가져오기 (Importing By Type)

given은 익명일 수 있어서 이름으로 가져오는 게 항상 실용적이진 않아요. 그래서 보통 와일드카드 임포트를 쓰죠. 타입 기준 임포트(by-type import)는 와일드카드 임포트보다 더 구체적인 대안을 제공해서, 무엇이 임포트되는지 더 분명하게 알려줘요. 예를 들어 볼게요.

import A.given TC

이 코드는 A 안에서 TC에 적합한(conform) 타입을 가진 given을 모두 가져와요. 여러 타입 T1,...,Tn의 given을 가져오려면 given 선택자를 여러 개 쓰면 돼요.

import A.{given T1, ..., given Tn}

매개변수화된 타입의 given 인스턴스를 전부 가져오려면 와일드카드 인자를 쓰면 돼요. 예를 들어 다음과 같은 object가 있다고 해볼게요.

object Instances:
  given intOrd: Ordering[Int] = ...
  given listOrd: [T: Ordering] => Ordering[List[T]] = ...
  given ec: ExecutionContext = ...
  given im: Monoid[Int] = ...

이때 임포트 절

import Instances.{given Ordering[?], given ExecutionContext}

intOrd, listOrd, ec 인스턴스를 가져오고 im 인스턴스는 빼요. im은 지정된 어떤 bound에도 맞지 않기 때문이죠.

타입 기준 임포트는 이름 기준 임포트와 섞어 쓸 수 있어요. 하나의 임포트 절에 둘 다 있으면 타입 기준 임포트가 뒤에 와요. 예를 들어 임포트 절

import Instances.{im, given Ordering[?]}

im, intOrd, listOrd를 가져오고 ec는 빼요.

마이그레이션 (Migration)

위에서 설명한 임포트 규칙은, 라이브러리가 옛 방식의 implicit과 일반 임포트를 쓰던 모든 사용자들과 함께 given 및 given 임포트로 옮겨가야 한다는 결과를 가져와요.

다음과 같은 수정 사항들이 마이그레이션의 장벽을 낮춰줘요.

  • given 임포트 선택자는 옛 방식의 implicit도 스코프로 가져와요. 그래서 Scala 3.0에서 옛 방식의 implicit 정의는 *나 given 와일드카드 선택자 어느 쪽으로든 스코프로 가져올 수 있어요.
  • Scala 3.1에서는 * 와일드카드 임포트를 통해 접근하는 옛 방식의 implicit이 deprecation 경고를 내요.
  • 3.1 이후 어떤 버전에서는 * 와일드카드 임포트를 통해 접근하는 옛 방식의 implicit이 컴파일 에러가 돼요.

이 규칙 덕분에 라이브러리 사용자들은 Scala 3.0에서는 given 선택자로 옛 방식의 implicit에 접근할 수 있고, 이후 버전에서는 자연스럽게, 그리고 결국엔 강제적으로 그렇게 하게 돼요. 그러면 라이브러리들은 사용자 기반이 마이그레이션을 마친 뒤 given 인스턴스로 전환할 수 있죠.

문법 (Syntax)

Import            ::=  ‘import’ ImportExpr {‘,’ ImportExpr}
Export            ::=  ‘export’ ImportExpr {‘,’ ImportExpr}
ImportExpr        ::=  SimpleRef {‘.’ id} ‘.’ ImportSpec
ImportSpec        ::=  NamedSelector
                    |  WildcardSelector
                    | ‘{’ ImportSelectors) ‘}’
NamedSelector     ::=  id [‘as’ (id | ‘_’)]
WildCardSelector  ::=  ‘*' | ‘given’ [InfixType]
ImportSelectors   ::=  NamedSelector [‘,’ ImportSelectors]
                    |  WildCardSelector {‘,’ WildCardSelector}