관리 메뉴

나만을 위한 블로그

[이펙티브 코틀린] 아이템 12. 연산자 오버로드를 할 때는 의미에 맞게 써라 본문

책/Effective Kotlin

[이펙티브 코틀린] 아이템 12. 연산자 오버로드를 할 때는 의미에 맞게 써라

참깨빵위에참깨빵 2022. 7. 10. 19:54
728x90
반응형

예를 들어 팩토리얼을 구하는 함수를 생각해 본다.

 

fun Int.factorial(): Int = (1..this).product()

fun Iterable<Int>.product(): Int =
    fold(1) { acc, i -> acc * i }

 

이 함수는 Int 확장 함수로 정의돼 있어 편하게 쓸 수 있다.

 

fun main() {
    print(10 * 6.factorial()) // 7200
}

 

팩토리얼은 ! 기호를 써서 표기하지만 코틀린은 이런 연산자를 지원하지 않는다. 그러나 아래처럼 연산자 오버로딩을 활용하면 만들 수 있다.

 

// not() = "!" 기호
operator fun Int.not() = factorial()

fun main() {
    print(10 * !6) // 7200
}

 

이렇게 할 수는 있지만 이렇게 해선 안 된다. 이 함수의 이름이 not인 것에 주목하라. 함수명이 not이므로 논리 연산에 사용해야지 팩토리얼 연산에 쓰면 안 된다. 코드를 이렇게 작성하면 오해의 소지가 있다. 모든 연산자는 연산자 대신 함수로도 호출할 수 있다.

 

코틀린에서 각 연산자의 의미는 항상 같게 유지된다. 예를 들어 아래 코드를 본다.

 

x + y == z

 

이 코드는 언제나 다음과 같은 코드로 변환된다.

 

(x.plus(y))?.equal(z) ?: (z == null)

 

이는 구체적인 이름을 가진 함수고 모든 연산자가 이런 이름이 나타내는 역할을 할 거라고 기대된다. 이처럼 이름만으로 연산자 사용이 크게 제한된다. 따라서 팩토리얼을 계산하기 위해 ! 연산자를 쓰면 안 된다.

 

분명하지 않은 경우

 

관례를 충족하는지 아닌지가 확실하지 않을 때가 문제다. 예를 들어 함수를 세 배 한다는 건 무슨 의미인가?

 

operator fun Int.times(operation: () -> Unit): () -> Unit =
    { repeat(this) { operation() } }

val tripledHello = 3 * { print("Hello") }

fun main() {
    tripledHello()  // HelloHelloHello
}

 

의미가 명확하지 않다면 infix를 활용한 확장 함수를 쓰는 게 좋다. 일반적인 이항 연산자 형태처럼 사용할 수 있다.

 

infix fun Int.timesRepeated(operation: () -> Unit) = run { repeat(this) { operation() } }

val tripledHello = 3 timesRepeated { print("Hello") }

fun main() {
    tripledHello    // HelloHelloHello
}

 

톱 레벨 함수를 쓰는 것도 좋다. 사실 함수를 n번 호출하는 건 다음 같은 형태로 이미 stdlib에 구현돼 있다.

 

규칙을 무시해도 되는 경우


지금까지 확인한 연산자 오버로딩 규칙을 무시해도 되는 중요한 경우가 있다. 바로 도메인 특화 언어(DSL)를 설계할 때다.

반응형
Comments