연산자 오버로딩은 굉장히 좋은 기능이지만 굉장히 위험할 수도 있습니다 예를 들어 팩토리얼 구하는 함수를 생각해 봅시다
fun Int.factorial(): Int = (1..this).product()
fun Iterble<Int>.product(): Int =
fold(1) {acc, i -> acc *1 }
이 한수는 Int 확장 함수로 정의되어 있으므로, 굉장히 편리하게 사용할 수 있습니다.
코틀린은 10*6! 코틀린은 이런 연산자를 지원하지 않지만, 다음과 같이 연산자 오버로딩을 활용하면, 만들어 낼 수 있습니다.
operator fun Int.not() = factorial()
print(10 * !6)
이렇게 사용할수 있지만 이렇게 사용해서는 안됩니다. 이렇게 사용하면 오해의 소지가 있습니다. 코틀린의 각 연산자의 의미는 항상 같게 유지 됩니다 스칼라와 같은 일부 프로그래밍 언어는 무제한 연산자 오버로딩을 지원합니다, 이정도의 자유는 많은 개발자가 해당 기능을 오용하게 만듭니다. 예를들어 +의 연산자ㅏ 일반적인 의미로 사용되지 않고 있다면 연산자를 볼 때마다 연산자를 개별적으로 이해해야 하기 때문에 코드를 이해하기 어려울 것 입니다. 코틀린은 각각 연산자에 구체적인 의미가 부여 되어있어 이러한 문제가 없습니다. 예를들어
x+y == z -> x.plus(y).equal(z) 같은 코드로 변환 됩니다.
참고로 플로스의 리턴 타입이 nullable이라면 다음과 같이 변환 됩니다
(x.plus(y))?.equal(z) ? : (z == null)
이는 구체적인 이름을 가진 함수이며 모든 연산자가 이러한 이름이 나타내는 역할을 할 거라고 기대됩니다. 이처럼 이름만으로 연산자의 사용이 크게 제한 됩니다, 따라서 팩토리얼을 계산하기 위해서 ! 연산자를 사용하면 안 됩니다. 이는 관례에 어긋나기 때문입니다.
분명하지 않은 경우
하지만 관례를 충족하는지 아닌지 확실하지 않을 때가 있습니다. 예를들어 함수를 세 배 한다는 것은 무슨 의미 일까요?
어떤 사람은 세번 반복하는 함수를 만들어 생각 할 수 있습니다
operator fun Int.times(operation:() -> Unit): () -> Unit =
{repeat(this){operation()}}
val tripledHello = 3 * {print("Hello")}
tripledHello()
물론 어떤 사람은 다음과 같이 이러한 코드가 함수를 세번 호출한다는 것을 쉽게 이해할 수 있습니다.
operator fun Int.times(operation:() -> Unit)
{repeat(this){operation()}}
3 * {print("Hello")}
의미가 명확하지 않다면, inflx를 활용한 확장 함수를 사용하는 것이 좋습니다. 일반적인 이항 연산자 형태처럼 사용할 수 있습니다.
infix fun Int.times(operation:() -> Unit)
{repeat(this){operation()}}
val tripledHello = 3 timesRepeated {print("Hello")}
tripledHello()
톰레벨 함수를 사용하는 것도 좋습니다. 사실 함수를 n번 호출하는 것은 다음과 같은 형태로 이미 stdlibdp에 그현되어 있습니다
repeat(3){print("Hello")}
규칙을 무시해도 되는 경우
지금까지 설명한 연산자 오버로딩 규칙을 무시해도 되는 중요한 경우가 있습니다. 바로 도메인 특화 언어를 설계할 때입니다. 고전적인 HTML DSL을 생가해 봅시다
body{
div{
+"Some text"
}
}
문자열 압에 String.unaryPlus가 사용된 것을 볼 수 있습니다. 이렇게 코드를 작성해도 되는 이유는 이 코드가 DSL 코드이기 때문입니다.
정리
오버로딩은 그 이름의 의미에 맞게 사용해 주세요. 연산자 의미가 명확하지 않다면 연산자 오버로딩을 사용되지 않는 것이 좋습니다. 대신 이름이 있는 일반 함수를 사용하기 바랍니다. 꼬 연산자 같은 형태로 사용하고 싶다면 inflex 확장 함수 또는 톱레벨 함수를 활용하세요