오른쪽 결합 확장 메서드: 세부 사항
오른쪽 결합 확장 메서드: 세부 사항 (Right-Associative Extension Methods: Details)
확장 메서드가 가질 수 있는 가장 일반적인 시그니처와, 이름이 :로 끝날 때 오른쪽 결합 연산자로 취급되는 과정을 자세히 살펴볼게요. 특히 컴파일러가 파라미터 순서를 어떻게 바꾸는지, 그래서 호출 시 어떤 점을 조심해야 하는지가 핵심이에요.
본문
확장 메서드가 가질 수 있는 가장 일반적인 시그니처는 다음과 같아요.
- 선택적인 타입 절
leftTyParams - 아마 비어 있는 using 절 목록
leadingUsing - 단일 파라미터
leftParam(명시적 용어 절 안에) - 아마 비어 있는 using 절 목록
trailingUsing - 이름 (그 앞에
def키워드) - 선택적인 타입 절
rightTyParams - 선택적인 단일 파라미터
rightParam(명시적 용어 절 안에) - 임의 개수의 임의 절
rest
예를 들어 볼게요.
extension [T] // <-- leftTyParams
(using a: A, b: B)(using c: C) // <-- leadingUsing
(x: X) // <-- leftParam
(using d: D) // <-- trailingUsing
def +:: [U] // <-- rightTyParams
(y: Y) // <-- rightParam
(using e: E)(z: Z) // <-- rest
확장 메서드는 이름이 :로 끝나고, 그 직후에 단일 명시적 용어 파라미터(즉, rightParam이 존재)가 이어질 때 오른쪽 결합 연산자(SLS §6.12.3 참조)로 취급돼요. 위 예제에서 그 파라미터는 (y: Y)예요.
Scala 컴파일러는 x +: xs 같은 오른쪽 결합 중위(infix) 연산을, x가 순수 표현식이거나 call-by-name 파라미터라면 xs.+:(x)로, 그렇지 않으면 val y = x; xs.+:(y)로 전처리해요. 일반적인 오른쪽 결합 중위 메서드가 오른쪽 피연산자의 클래스에 정의되기 때문에 이 처리가 필요해요. 이 스왑을 보완하기 위해 오른쪽 결합 확장 메서드의 확장(expansion)은 역(逆) 파라미터 스왑을 수행해요. 더 정확히 말하면, rightParam이 존재한다면 확장 메서드 확장의 전체 파라미터 시퀀스는 다음과 같아요.
leftTyParams leadingUsing rightTyParams rightParam leftParam trailingUsing rest
즉, leftParams trailingUsing을 rightTyParam rightParam과 바꾼다고 볼 수 있어요.
예를 들어 위의 +:: 메서드는 이렇게 될 거예요.
<extension> def +:: [T]
(using a: A, b: B)(using c: C)
[U]
(y: Y)
(x: X)
(using d: D)
(using e: E)(z: Z)
파라미터 사이에 의존성이 있는 오른쪽 결합 확장 메서드를 쓸 때는 이 확장을 염두에 둬야 해요.
이 확장은 또한 중위가 아닌 형태로 확장 메서드를 호출할 때도 약간의 불일치를 만들어내요. 호출 지점에서 인자 순서를 수동으로 뒤집어야 해요. 예를 들어 볼게요.
extension [T](x: T)
def *:(xs: List[T]): List[T] = ...
y.*:(ys) // error when following the parameter definition order
ys.*:(y)
*:(y)(ys) // error when following the parameter definition order
*:(ys)(y)
이 표현의 또 다른 한계는 def의 타입 파라미터를 명시적으로 전달하는 것이 불가능하다는 거예요(접두사 형태로 호출하지 않는 한). 예를 들어 볼게요.
extension (x: Int)
def *:[T](xs: List[T]): List[T] = ...
xs.*:[Int](1) // error when trying to set T explicitly
오른쪽 결합 확장 메서드의 확장은 컨텍스트 파라미터를 명시적으로 전달할 수 있는 순서에도 영향을 줘요.
그룹 확장도 직관적이지 않게 동작할 수 있어요. 일반적으로 그룹 안의 모든 확장은 수신자(receiver)에 대한 확장이에요. 그런데 그 확장 중 하나가 오른쪽 결합 확장 메서드라면, 그 하나는 인자의 타입에 대한 확장이 돼요. 예를 들어 볼게요.
extension (a: Int)
def :+(b: Long): Long = ... // extension on Int
def +:(b: Long): Long = ... // extension on Long