일반화된 메서드 문법

일반화된 메서드 문법 (Generalized Method Syntax)

메서드 정의에서 타입 파라미터 절이 반드시 맨 앞에 하나만 와야 했던 제약이 풀렸어요. Scala 3에서는 타입 파라미터 절을 여러 개, 그리고 원하는 위치에 둘 수 있습니다. 그래서 '용어 파라미터에 의존하는 타입 파라미터' 같은 새로운 설계 패턴도 가능해졌죠. 이번 글에서는 그 문법 변화가 어떤 식으로 이루어졌는지 하나씩 살펴볼게요.

출처: Scala 3 Reference

본문

메서드가 달라진 건 using 절이 추가된 것만이 아니에요. 타입 파라미터 절도 이제 몇 개든, 어느 위치에든 쓸 수 있게 됐습니다.

문법 변화 (Syntax Changes)

Scala 2에서

기존 문법은 타입 파라미터 절을 0개 또는 1개만 허용했고, 그 뒤에 용어(term) 절을 원하는 만큼, 그리고 선택적으로 implicit 절을 하나 붙일 수 있었어요:

def foo[T, U](x: T)(y: U)(z: Int, s: String)(a: Array[T])(implicit ordInt: Ord[Int], l: List[U])

Scala 3에서

새 문법은 타입 절을 얼마든지 허용하는데, 단 서로 인접하면 안 돼요. (implicit 절은 using 절을 쓰는 쪽이 권장되니 참고해 두세요.)

def foo[T, U](x: T)(y: U)[V](z: V, s: String)(using Ord[Int])[A](a: Array[A])(implicit List[U])

변하지 않은 것

클래스 정의와 타입 선언은 영향이 없어요. 여전히 타입 절은 맨 앞에 최대 하나만 올 수 있습니다.

동기 (Motivation)

새 문법은 기존 문법의 강력하면서도 자연스러운 확장이에요. 직관적이고 읽기 쉬우면서도 새로운 설계 패턴을 가능하게 해 주죠.

의존형 타입 절 (Dependent Type Clauses)

타입 절이 용어 절 뒤에 올 수 있게 되면서, 이제 용어 파라미터에 의존하는 타입 파라미터를 만들 수 있어요:

trait Key { type Value }
trait DB {
  def get(k: Key): Option[k.Value] // dependent result type
  def getOrElse(k: Key)[V >: k.Value](default: V): V // dependent type parameter
}

V를 그냥 k.Value로 바꾸는 것과는 같지 않다는 점에 주의하세요. 예를 들어 k.ValueSome[Int]라면, getOrElse(k)[Option[Int]](None)처럼 써서 Number를 돌려받을 수 있는 건 바로 위처럼 타입 파라미터를 둘 때뿐이에요.

세부 사항 (Details)

적용 (Application)

메서드 적용은 그대로예요. 여러 타입 절이 기대되는데 전부 전달되지 않으면, 가장 오른쪽 것들이 추론됩니다.

특히 다음 코드는 타입 체크가 통과하지 않아요. 인자 CharC에만 유효하다고 해도 말이죠:

def triple[I <: Int](using Ordering[I])[C <: Char](a: I, b: C) = ???
triple[Char](0, 'c') // error: Char does not conform to upperbound Int

확장 메서드 (Extension Methods)

확장 메서드도 같은 문법을 따라요. 예를 들어 다음은 유효한 선언이에요:

extension [T](l1: List[T])
  def zipWith[U](l2: List[U])[V](l3: List[V]): List[(T,U,V)]

언제 쓸까 (When to use)

가능하면 항상 타입 절 하나를 맨 앞에 두는 걸 권장해요. 예를 들어 위의 확장 메서드 zipWithzipWith[U, V](l2: List[U], l3: List[V]): List[(T,U,V)]처럼 쓰는 편이 낫습니다. 반면 getOrElse 메서드는 맨 앞 타입 절로는 표현할 수 없으니, 위 예시 그대로 쓰는 게 권장돼요.

형식 문법 (Formal syntax)

DefDcl            ::=  DefSig ‘:’ Type
DefDef            ::=  DefSig [‘:’ Type] ‘=’ Expr
DefSig            ::=  id [DefParamClauses] [DefImplicitClause]
DefParamClauses   ::=  DefParamClause { DefParamClause } -- and two DefTypeParamClause cannot be adjacent
DefParamClause    ::=  DefTypeParamClause
                    |  DefTermParamClause
                    |  UsingParamClause
DefTypeParamClause::=  [nl] ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’
DefTypeParam      ::=  {Annotation} id [HkTypeParamClause] TypeParamBounds
DefTermParamClause::=  [nl] ‘(’ [DefTermParams] ‘)’
UsingParamClause  ::=  [nl] ‘(’ ‘using’ (DefTermParams | FunArgTypes) ‘)’
DefImplicitClause ::=  [nl] ‘(’ ‘implicit’ DefTermParams ‘)’
DefTermParams     ::=  DefTermParam {‘,’ DefTermParam}
DefTermParam      ::=  {Annotation} [‘inline’] Param
Param             ::=  id ‘:’ ParamType [‘=’ Expr]