컨텍스트 파라미터
컨텍스트 파라미터 (Context Parameters)
Scala는 **문맥적 추상화(contextual abstraction)**를 위한 두 가지 중요한 기능을 제공해요:
- **컨텍스트 파라미터(Context Parameters)**는 호출부(call-site)에서 프로그래머가 생략할 수 있고 문맥이 자동으로 제공해야 하는 파라미터를 지정하게 해 줍니다.
- Given 인스턴스(Scala 3) 또는 **암시적 정의(Implicit Definitions, Scala 2)**는 컴파일러가 빠진 인자를 채우는 데 사용할 수 있는 용어(term)예요.
출처: Scala 3 Book
본문
컨텍스트 파라미터
시스템을 설계하다 보면 설정(configuration) 같은 문맥 정보를 시스템의 여러 구성 요소에 제공해야 하는 경우가 흔해요. 이를 달성하는 흔한 방법 하나는 설정을 메서드에 추가 인자로 넘기는 거예요.
다음 예제에서는 어떤 웹사이트 설정을 모델링하는 case class Config를 정의하고, 그것을 여러 메서드에 전달해요.
case class Config(port: Int, baseUrl: String)
def renderWebsite(path: String, config: Config): String =
"<html>" + renderWidget(List("cart"), config) + "</html>"
def renderWidget(items: List[String], config: Config): String = ???
val config = Config(8080, "docs.scala-lang.org")
renderWebsite("/home", config)
코드 베이스 대부분에서 설정이 바뀌지 않는다고 가정해 볼게요. config를 (renderWidget 같은) 매 호출마다 일일이 넘기는 것은 아주 지루하고, 프로그램을 읽기도 어렵게 만들어요. 왜냐하면 config 인자를 그저 무시하고 지나가야 하니까요.
파라미터를 컨텍스트로 표시하기
메서드의 일부 파라미터를 컨텍스트로 표시할 수 있어요.
Scala 2에서는 implicit 키워드로 파라미터 섹션을 시작해요:
def renderWebsite(path: String)(implicit config: Config): String =
"<html>" + renderWidget(List("cart")) + "</html>"
// ^
// no argument config required anymore
def renderWidget(items: List[String])(implicit config: Config): String = ???
Scala 3에서는 using 키워드를 씁니다:
def renderWebsite(path: String)(using config: Config): String =
"<html>" + renderWidget(List("cart")) + "</html>"
// ^
// no argument config required anymore
def renderWidget(items: List[String])(using config: Config): String = ???
Scala 3의 using이나 Scala 2의 implicit 키워드로 파라미터 섹션을 시작하면, 호출부에서 컴파일러가 올바른 타입의 인자를 자동으로 찾아야 한다고 알려주는 거예요. Scala 컴파일러는 이렇게 해서 **용어 추론(term inference)**을 수행합니다.
renderWidget(List("cart")) 호출에서 Scala 컴파일러는 스코프 안에 Config 타입의 용어(즉 config)가 있다는 걸 보고 자동으로 renderWidget에 제공해요. 그래서 이 프로그램은 위의 프로그램과 동등해요.
사실 Scala 3에서는 renderWebsite 구현에서 더 이상 config를 참조할 필요가 없으므로, 서명에서 그 이름을 아예 생략할 수도 있어요:
// no need to come up with a parameter name
// vvvvvvvvvvvvv
def renderWebsite(path: String)(using Config): String =
"<html>" + renderWidget(List("cart")) + "</html>"
Scala 2에서는 implicit 파라미터의 이름이 여전히 필수예요.
컨텍스트 인자를 명시적으로 제공하기
컨텍스트 파라미터를 추상화하는 방법과 컴파일러가 인자를 자동으로 제공할 수 있다는 걸 보았어요. 그렇다면 renderWebsite 호출에 어떤 설정을 쓸지는 어떻게 지정할 수 있을까요?
Scala 2에서는 그 값을 일반 인자처럼 명시적으로 제공해요:
renderWebsite("/home")(config)
Scala 3에서는 파라미터 섹션을 using으로 지정했듯이, 컨텍스트 인자도 using으로 명시적으로 제공할 수 있어요:
renderWebsite("/home")(using config)
컨텍스트 파라미터를 명시적으로 제공하는 것은, 스코프 안에 말이 되는 서로 다른 여러 값이 있고 올바른 값이 함수에 전달되도록 확실히 하고 싶을 때 유용해요.
그 외의 모든 경우에는, 다음 섹션에서 보게 되겠지만, 컨텍스트 값을 스코프 안으로 가져오는 또 다른 방법이 있어요.
Given 인스턴스 (Scala 2의 암시적 정의)
컨텍스트 파라미터로 인자를 명시적으로 전달할 수 있다는 걸 보았어요. 하지만 특정 타입에 대해 정식(canonical) 값이 하나뿐이라면, 컴파일러가 그 값을 사용할 수 있게 만드는 더 선호되는 방법이 있어요. Scala 3에서 given, Scala 2에서 implicit으로 표시하는 것이죠.
Scala 2에서는 이렇게 implicit val을 씁니다:
implicit val config: Config = Config(8080, "docs.scala-lang.org")
// ^^^^^^
// this is the value the Scala compiler will infer
// as argument to contextual parameters of type Config
Scala 3에서는 given을 사용해요:
val config = Config(8080, "docs.scala-lang.org")
// this is the type that we want to provide the
// canonical value for
// vvvvvv
given Config = config
// ^^^^^^
// this is the value the Scala compiler will infer
// as argument to contextual parameters of type Config
위 예제에서는 현재 스코프에서 Config 타입의 컨텍스트 파라미터가 생략될 때마다, 컴파일러가 config를 인자로 추론하도록 지정했어요.
Config 타입의 정식 값을 정의했으니, renderWebsite를 다음과 같이 호출할 수 있어요:
renderWebsite("/home")
// ^
// again no argument
컴파일러가 정식 값을 어디에서 찾는지에 대한 자세한 안내는 FAQ에서 볼 수 있어요.
더 알아보기
- 컨텍스트 바운드(Context Bounds), 컨텍스트 추상화의 다른 측면은 "Context Parameters" 및 "Contextual Abstractions" 섹션에서 다룹니다.
- 컴파일러가 정식 값을 탐색하는 규칙에 대한 자세한 내용은 Reference 문서의 FAQ를 확인해 보세요.