Given의 다른 형태들

Given의 다른 형태들 (Other Forms of Givens)

given 인스턴스라는 개념은 꽤 일반적이에요. 이 페이지에서는 앞에서 다루지 않았던 given의 여러 형태를 살펴볼게요. 단순 구조적 given부터 조건 given, 이름 있는 조건 파라미터, 이름 없는(by-name) given, given 매크로, 패턴에 묶인 given, 그리고 부정(negated) given까지요.

출처: Scala 3 Reference

본문

단순 구조적 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 별칭은 inlinetransparent 수식어를 가질 수 있어요. 예를 들어 볼게요.

transparent inline given mkAnnotations: [A, T] => Annotations[A, T] = ${
  // code producing a value of a subtype of Annotations
}

mkAnnotationstransparent이기 때문에, 적용(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