연산자 오버로딩은 말그래도 연산자로 쓰이고 있는 친구들을 오버로딩해서 정의해서 사용하는 방식이다.
이는 굉장히 강력하지만 위험할 수 있다.
만약 factorial을 정의해서 쓴다하면 아래와 같이 정의하고 싶을 수도 있다
fun Int.factorial(): Int = (1..this).product()
fun Iterable<Int>.product(): Int = fold(1) { acc, i -> acc * i }
operator fun Int.not() = factorial()
print(10 * !6) // 7200
과연 될까? 안 된다.
이 함수 이름이 not() 이므로 논리 연산에 사용해야지 팩토리얼 연산에 사용하면 안된다.
코드 가독성과 작성에도 굉장히 혼란스럽고 오해를 일으킨다.
코틀린에서 각 연산자의 의미는 항상 같게 유지된다.
스칼라와 같은 일부 프로그래밍 언어는 무제한 연산자 오버로딩을 지원하지만 이 정도의 자유는 많은 개발자가 이 기능을 오용하게 만든다.
+
가 일반적으로 쓰이고 있지 않다면 연산자를 볼 때마다 개별적으로 이해하려고 노력을 해야한다. (+어렵기까지!)
코틀린에서는 각각의 연산자에 구체적인 의미가 있으므로 이러한 문제가 없다.
따라서 !
을 사용하는 것은 관례에 어긋난다.
관례를 충족하는지 아닌지 확실하지 않을 때는 어떨까?
만약 함수를 세 배한다는 것은 무슨 의미일까?
1) 같은 함수를 세번 반복하는 새로운 함수 생성
operator fun Int.times(operation: () -> Unit): ()->Unit = {
repeat(this) { operation() }
}
val tripleHello = 3 * { print("Hello") }
tripleHello() // HelloHelloHello
2) 이런 코드가 세 번 호출
operator fun Int.times(operation: () -> Unit): ()->Unit = {
repeat(this) { operation() }
}
3 * { print("Hello") } // HelloHelloHello
이럴 때처럼 의미가 명확하지 않다면, infix를 사용하자
infix fun Int.timesRepeated(operation: () -> Unit) = {
repeat(this) { operation() }
}
val tripleHello = 3 timesRepeated { print("Hello") }
tripleHello() // HelloHelloHello
톱레벨 함수를 사용하는 것도 좋다
(사실 n번 함수를 호출하는 것은 아래와 같이 stdlib
에 구현되어 있다...)
repeat(3) { print("Hello") } // HelloHelloHello
바로 DSL(도메인 특화 언어, Domain Specific Language)를 설계할 때 연산자 오버로딩 규칙을 무시해도 된다
고전적인 HTML DSL
body {
div {
+"Some text"
}
}
문자열 앞에 String.unaryPlus가 사용된 것을 볼 수 있다. 이렇게 해도 되는 건 DSL 코드이기 때문~
연산자 오버로딩은 그 이름의 의미에 맞게 사용하자
연산자 의미가 명확하지 않다면, 연산자 오버로딩을 사용하지 않는 것이 좋다. 대신 이름이 있는 일반 함수를 쓰자.
꼭 연사자 같은 형태로 사용하고 싶다면, infix
확장 함수 또는 톱레벨(클래스 또는 다른 대상 내부에 있지 않고, 가장 외부에 있는 함수)
함수를 쓰자