Given의 다른 형태들
Given의 다른 형태들 (Other Forms of Givens)
given 인스턴스라는 개념은 꽤 일반적이에요. 이 페이지에서는 앞에서 다루지 않았던 given의 여러 형태를 살펴볼게요. 단순 구조적 given부터 조건 given, 이름 있는 조건 파라미터, 이름 없는(by-name) given, given 매크로, 패턴에 묶인 given, 그리고 부정(negated) given까지요.
본문
단순 구조적 Given (Simple Structural Givens)
어떤 given은 별칭이나 추가 멤버 선언 없이 그냥 클래스를 인스턴스화해요. 예를 들어 볼게요.
class IntOrd extends Ord[Int]:
def compare(x: Int, y: Int) =
if x < y then -1 else if x > y then +1 else 0
given IntOrd()
이 경우 given 절은 위의 IntOrd() 같은 클래스 생성 표현식 하나로만 이루어져요.
파라미터가 있는 조건 Given (Conditional Givens with Parameters)
조건 given은 파라미터와 함께 정의할 수도 있어요. 예를 들어 볼게요.
given (config: Config) => Factory = MemoizingFactory(config)
여기서 (config: Config)는 조건을 표현하는 컨텍스트 파라미터를 나타내요. 즉 Config 타입의 given을 합성할 수 있다면 Factory given도 합성할 수 있다는 뜻이에요.
타입 파라미터와 컨텍스트 파라미터는 결합할 수 있어요. 예를 들어 위의 listOrd 인스턴스는 다음과 같이 표현할 수도 있어요.
given listOrd: [T] => Ord[T] => Ord[List[T]]:
...
def compare(x: List[T], y: List[T]) = ...
예제에서 볼 수 있듯이 각 파라미터 섹션 뒤에는 =>가 따라와요.
컨텍스트 파라미터에 이름을 붙이는 것도 가능해요.
given listOrd: [T] => (ord: Ord[T]) => Ord[List[T]]:
...
이름 없는(by-name) Given (By Name Givens)
일반적으로는 given을 다시 평가하는 것을 피하고 싶지만, 재평가가 필요한 상황도 있어요. 예를 들어 변경 가능한(mutable) 변수 curCtx가 있고, 그 변수의 현재 값을 반환하는 given을 정의하고 싶다고 해볼게요. 일반적인 given 별칭으로는 안 돼요. 기본적으로 given 별칭은 lazy val로 매핑되기 때문이죠. 이 경우 빈 파라미터 목록을 가진 조건 given을 써서 이름 없는(by-name) 평가를 지정할 수 있어요.
val curCtx: Context
given context: () => Context = curCtx
이 정의 덕분에, Context가 summon될 때마다 컨텍스트 함수를 평가해서 curCtx의 현재 값을 만들어내요.
Given 매크로 (Given Macros)
given 별칭은 inline과 transparent 수식어를 가질 수 있어요. 예를 들어 볼게요.
transparent inline given mkAnnotations: [A, T] => Annotations[A, T] = ${
// code producing a value of a subtype of Annotations
}
mkAnnotations가 transparent이기 때문에, 적용(application)의 타입은 오른쪽의 타입이 돼요. 그리고 그 타입은 선언된 결과 타입 Annotations[A, T]의 진짜 하위 타입일 수 있어요.
구조적 given도 inline 수식어를 가질 수 있어요. 하지만 transparent 수식어는 허용되지 않아요. 구조적 given의 타입은 이미 시그니처에서 알려져 있기 때문이죠.
예를 들어 볼게요.
trait Show[T]:
inline def show(x: T): String
inline given Show[Foo]:
inline def show(x: Foo): String = ${ ... }
def app =
// inlines `show` method call and removes the call to `given Show[Foo]`
summon[Show[Foo]].show(foo)
given 인스턴스 안의 inline 메서드는 transparent일 수 있다는 점을 기억해 두세요.
패턴에 묶인 Given 인스턴스 (Pattern-Bound Given Instances)
given 인스턴스는 패턴 안에도 나타날 수 있어요. 예를 들어 볼게요.
for given Context <- applicationContexts do
pair match
case (ctx @ given Context, y) => ...
첫 번째 조각에서는 applicationContexts를 열거해서 Context 클래스의 익명 given 인스턴스를 확립해요. 두 번째 조각에서는 pair 선택자의 첫 번째 절반을 매칭해서 ctx라는 이름의 Context given 인스턴스를 확립해요.
각 경우에 패턴에 묶인 given 인스턴스는 given과 타입 T로 이루어져요. 이 패턴은 타입 지정 패턴(type ascription pattern) _: T와 정확히 같은 선택자와 매칭돼요.
부정 Given (Negated Givens)
가끔은 어떤 다른 타입의 given 인스턴스가 없을 때 암시적 탐색(implicit search)이 성공하도록 하고 싶을 때가 있어요. 이런 종류의 부정을 구현하는 특별한 클래스 scala.util.NotGiven이 있어요.
어떤 쿼리 타입 Q에 대해서도, Q에 대한 암시적 탐색이 실패할 때에만 NotGiven[Q]가 성공해요. 예를 들어 볼게요.
import scala.util.NotGiven
trait Tagged[A]
case class Foo[A](value: Boolean)
object Foo:
given fooTagged: [A] => Tagged[A] => Foo[A] = Foo(true)
given fooNotTagged: [A] => NotGiven[Tagged[A]] => Foo[A] = Foo(false)
@main def test(): Unit =
given Tagged[Int]()
assert(summon[Foo[Int]].value) // fooTagged is found
assert(!summon[Foo[String]].value) // fooNotTagged is found
요약 (Summary)
다음은 주로 쓰이는 given 절 형태의 요약이에요.
// Simple typeclass
given Ord[Int]:
def compare(x: Int, y: Int) = ...
// Parameterized typeclass with context bound
given [A: Ord] => Ord[List[A]]:
def compare(x: List[A], y: List[A]) = ...
// Parameterized typeclass with context parameter
given [A] => Ord[A] => Ord[List[A]]:
def compare(x: List[A], y: List[A]) = ...
// Parameterized typeclass with named context parameter
given [A] => (ord: Ord[A]) => Ord[List[A]]:
def compare(x: List[A], y: List[A]) = ...
// Simple alias
given Ord[Int] = IntOrd()
// Parameterized alias with context bound
given [A: Ord] => Ord[List[A]] =
ListOrd[A]
// Parameterized alias with context parameter
given [A] => Ord[A] => Ord[List[A]] =
ListOrd[A]
// Deferred given
given Context = deferred
// By-name given
given () => Context = curCtx
이 모든 절은 이름 있는 형태로도 존재해요.
// Simple typeclass
given intOrd: Ord[Int]:
def compare(x: Int, y: Int) = ...
// Parameterized typeclass with context bound
given listOrd: [A: Ord] => Ord[List[A]]:
def compare(x: List[A], y: List[A]) = ...
// Parameterized typeclass with context parameter
given listOrd: [A] => Ord[A] => Ord[List[A]]:
def compare(x: List[A], y: List[A]) = ...
// Parameterized typeclass with named context parameter
given listOrd: [A] => (ord: Ord[A]) => Ord[List[A]]:
def compare(x: List[A], y: List[A]) = ...
// Simple alias
given intOrd: Ord[Int] = IntOrd()
// Parameterized alias with context bound
given listOrd: [A: Ord] => Ord[List[A]] =
ListOrd[A]
// Parameterized alias with context parameter
given listOrd: [A] => Ord[A] => Ord[List[A]] =
ListOrd[A]
// Abstract or deferred given
given context: Context = deferred
// By-name given
given context: () => Context = curCtx