Given 인스턴스
Given 인스턴스 (Given Instances)
given 인스턴스(간단히 "given"이라고도 해요)는 어떤 타입의 "표준(canonical)" 값들을 정의해서, 그 값들이 컨텍스트 파라미터(context parameter)의 인자로 합성되는 데 쓰이게 해요. 이 페이지에서는 given을 정의하는 기본 형태부터 익명 given, 별칭 given, 그리고 문법까지 차근차근 살펴볼게요.
본문
예를 들어 볼게요.
trait Ord[T]:
def compare(x: T, y: T): Int
extension (x: T)
def < (y: T) = compare(x, y) < 0
def > (y: T) = compare(x, y) > 0
given intOrd: Ord[Int]:
def compare(x: Int, y: Int) =
if x < y then -1 else if x > y then +1 else 0
given listOrd: [T: Ord] => Ord[List[T]]:
def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match
case (Nil, Nil) => 0
case (Nil, _) => -1
case (_, Nil) => +1
case (x :: xs1, y :: ys1) =>
val fst = summon[Ord[T]].compare(x, y)
if fst != 0 then fst else compare(xs1, ys1)
이 코드는 Ord 트레이트와 두 개의 given 인스턴스를 정의해요. intOrd는 Ord[Int] 타입을 위한 given을 정의하고, listOrd[T]는 Ord[T]의 given 인스턴스를 가진 모든 타입 T에 대해 Ord[List[T]]의 given을 정의해요. 여기서 [T: Ord] 절은 컨텍스트 bound(context bound)인데, 이것이 조건을 정의해요. 즉 Ord[List[T]] 타입의 given이 존재하려면 Ord[T] 타입의 given이 있어야 한다는 거죠. 이런 조건은 컴파일러가 컨텍스트 파라미터로 확장해요.
익명 Given (Anonymous Givens)
given의 이름은 생략할 수 있어요. 그래서 앞 절의 정의들도 이렇게 표현할 수 있어요.
given Ord[Int]:
...
given [T: Ord] => Ord[List[T]]:
...
given의 이름이 없으면 컴파일러가 구현한 타입(들)으로부터 이름을 합성해요.
Note: 컴파일러가 합성한 이름은 읽기 쉽고 합리적으로 간결하게 정해져요. 예를 들어 위의 두 인스턴스는 다음과 같은 이름을 얻게 돼요.
given_Ord_Int
given_Ord_List
이름 합성의 정확한 규칙은 여기에서 찾을 수 있어요. 이 규칙들은 "너무 비슷한" 타입들의 given 인스턴스 사이에서 이름 충돌이 없음을 보장하진 않아요. 충돌을 피하려면 이름 있는 인스턴스(named instance)를 쓰면 돼요.
Note: 견고한 바이너리 호환성을 보장하려면, 공개적으로 배포되는 라이브러리는 이름 있는 인스턴스를 선호해야 해요.
별칭 Given (Alias Givens)
별칭(alias)을 사용하면 어떤 표현식과 같은 given 인스턴스를 정의할 수 있어요. 예를 들어 볼게요.
given global: ExecutionContext = ForkJoinPool()
이 코드는 global이라는 ExecutionContext 타입의 given을 만들고, 그 값은 오른쪽의 ForkJoinPool()로 결정돼요. global에 처음 접근하는 순간 새 ForkJoinPool이 만들어지고, 이후 global에 접근할 때마다(앞으로도 계속) 그 값이 반환돼요. 이 연산은 스레드 안전(thread-safe)해요.
별칭 given도 익명일 수 있어요. 예를 들어
given Position = enclosingTree.position
Given 인스턴스 초기화 (Given Instance Initialization)
파라미터가 없는 무조건적인(unconditional) given 인스턴스는 처음 접근할 때 필요에 따라(on-demand) 초기화돼요. given이 단순히 어떤 불변(immutable) 값의 별칭이라면, given은 단순한 전달자(forwarder)로 구현되어 값을 캐시해 두는 필드의 비용을 치르지 않아요. given이 조건적(conditional)이라면 참조할 때마다 새 인스턴스가 만들어져요.
문법 (Syntax)
given 인스턴스의 전체 문법은 다음과 같아요. 이 중 일부 given 형태는 별도 페이지인 Other Forms of Givens(다른 given 형태들)에서 설명해요.
TmplDef ::= ... | 'given' GivenDef
GivenDef ::= [id ':'] GivenSig
GivenSig ::= GivenImpl
| '(' ')' '=>' GivenImpl
| GivenConditional '=>' GivenSig
GivenImpl ::= GivenType ([‘=’ Expr] | TemplateBody)
| ConstrApps TemplateBody
GivenConditional ::= DefTypeParamClause
| DefTermParamClause
| '(' FunArgTypes ')'
| GivenType
GivenType ::= AnnotType1 {id [nl] AnnotType1}
given 인스턴스는 예약 키워드 given으로 시작하며, 그 뒤에 다음이 따라와요.
- 선택적인 이름과 콜론
- 선택적인 조건 목록
- 구현하는 타입(들)과 그 구현 — 두 가지 형태: 별칭 given(alias given)과 구조적 given(structural given)
Note given 문법의 일부는 Scala 3.6에서 바뀌었어요. Scala 3.0부터의 원래 문법은 별도 페이지인 Previous Given Syntax(이전 given 문법)에서 설명해요. 원래 문법도 당분간은 계속 지원되지만 시간이 지나면서 deprecated되고 단계적으로 사라질 거예요.