본문 바로가기

프로그래밍 언어 기초/KOTLIN

[Kotlin] 함수형 프로그래밍 - Infix 함수, 연산자 오버로딩

개요

오늘은 함수형 프로그래밍의 마지막 포스팅입니다.

이번 포스팅에서는 코틀린의 Infix 함수 그리고 연산자 오버로딩에 대해 알아보도록 하겠습니다.

 


Infix 함수

코틀린에서는 아주 독특한 형태의 함수를 하나 제공합니다.

 

a addNumber b

 


위 코드는 더하기(+), 빼기(-), 곱하기(*), 나누기(/)와 같은 `연산자(Operator)`도 아닌 거 같은데,

알고리즘 또는 프로그램의 동작을 논리적이고 간단한 언어로 설명하는 의사 코드(pseudocode)일까요?

이 코드의 정체는 코틀린의 Infix 함수였습니다.

Infix 함수란 중위 표기법으로 호출할 수 있는 함수입니다.

이는 함수 이름을 중위 연산자처럼 사용할 수 있도록 지원합니다.

여기서 중위 연산자란 연산자 기호가 피연산자 사이에 위치하는 연산 방식을 의미합니다. ( a + b )

 

코틀린에서는 이처럼 함수를 연산자처럼 사용할 때 Infix 함수를 활용할 수 있는데요.

이 함수는 지난 포스팅에서 다루었던, 확장 함수와 유사한 방법으로 함수를 설계할 수 있습니다.

아래는 코틀린에서 Infix 함수를 만드는 방법입니다.

 

infix fun Int.addNumber(x:Int):Int{
    return this + x;
}

fun main() {
    val value1 = 3;
    val value2 = 4;
    val result = value1 addNumber value2
    println(result); // 7
}



위 코드에서 'addNumber'라는 단어는 코틀린에서 이미 정의된 표준 연산자는 아닙니다.

다만, Infix 함수를 통해 마치 연산자처럼 함수를 사용할 수 있도록 하고 있는데요.

 

그렇다고 해서 모든 함수를 다 Infix 함수로 만들 수 있는 것은 아닙니다.

코틀린에서 Infix 함수를 선언하기 위해서는 다음과 같은 규칙을 따라야 합니다.

 

  1.  함수는 멤버 함수일 필요가 없을 경우
  2. 하나의 매개변수를 가지는 함수
  3. 함수 선언 앞에 infix 키워드를 추가 (infix fun ...)

 

코틀린에서 Infix 함수를 사용하면 연산이나 작업의 의도를 더 명확하게 전달할 수 있습니다.

예를 들어, 수학적인 연산을 할 때나 도메인 특정한 작업을 할 때

인픽스 함수를 사용하면 코드가 더 읽기 쉽고 자연스러워집니다.

 

아래는 이에 대한 보다 직관적인 예시를 위하여 이차방정식의 근의 공식을 구하는 Infix 함수를 구현해보았습니다.

이차방정식의 근의 공식을 구하는 식은 아래와 같습니다.

fig 1.0. 이차방정식의 근의 공식

 

import kotlin.math.sqrt

data class QuadraticEquation(val a: Double, val b: Double, val c: Double)

// 인픽스 함수로 이차 방정식의 근을 계산하는 확장 함수
infix fun QuadraticEquation.solve(): Pair<Double, Double> {
    val discriminant = sqrt(b * b - 4 * a * c)
    val root1 = (-b + discriminant) / (2 * a)
    val root2 = (-b - discriminant) / (2 * a)
    return Pair(root1, root2)
}

fun main() {
    val equation = QuadraticEquation(1.0, -3.0, 2.0)
    val (root1, root2) = equation solve()
    println("Root 1: $root1, Root 2: $root2")
}

 


위의 코드에서 QuadraticEquation 클래스는 이차 방정식의 계수 a, b, c를 나타냅니다.

그리고 solve라는 인픽스 함수를 정의하여 이차 방정식의 근을 계산합니다.

solve 함수는 이차 방정식의 근을 쌍으로 반환합니다.
main 함수에서는 이 클래스를 사용하여 이차 방정식의 근을 계산하고 출력합니다.

이처럼 인픽스 함수를 사용하여 이차 방정식의 근을 계산하는 함수를 직관적으로 만들 수 있습니다.

코틀린에서는 이외에도 이미 정의된 Infix 함수를 제공하고 있습니다.
가장 널리 사용되는 것 중 하나는 범위 생성 연산자 ..입니다.
이 연산자는 시작과 끝을 포함하는 범위를 생성하는 데 사용됩니다.
예를 들어, 1..10은 1부터 10까지의 정수 범위를 생성합니다.


또한, 컬렉션에서도 인픽스 함수가 사용됩니다.

예를 들어, in 연산자는 컬렉션에 특정 요소가 포함되어 있는지 확인하는 데 사용됩니다.

이것은 인픽스 함수로 정의되어 있습니다.

 

이처럼 코틀린 표준 라이브러리에서 이미 정의된 인픽스 함수를 정리하면 아래와 같습니다.

 

종류 설명
.. 범위 생성 연산자
in 컬렉션에 요소가 포함되어 있는지 확인하는 연산자
!in 컬렉션에 요소가 포함되어 있지 않은지 확인하는 연산자
as 형 변환을 수행하는 연산자

 

이러한 인픽스 함수들은 코틀린에서 자주 사용되는 함수 중 하나이며, 

복잡한 코드를 더 읽기 쉽고 직관적으로 만들어주는 이점이 있습니다.

 


연산자 오버로딩

코틀린에서 어떤 두 값을 사칙 연산 하려면 연산자(operator)를 통해 계산할 수도 있지만,

연산자 함수를 통해 계산할 수도 있습니다.

예컨대, 어떤 두 값을 더하는 연산(+)은 아래와 같이 두 가지 방법 모두 사용할 수 있습니다.

 

fun main() {
    val a = 1;
    val b = 2;

    val result1 = a + b;
    val result2 = a.plus(b);
    println(result1); // 3
    println(result2); // 3
}

 

코틀린에서 이처럼 연산자(+)의 기능을 할 수 있도록 이미 구현된 메서드(함수)를 연산자 함수라고 합니다.
그리고 코틀린에서 이 연산자 함수는 오버 로딩할 수 있도록 지원합니다.

 

특정 연산자를 사용자 정의 클래스나 타입에 대해 사용할 수 있도록 하여 새로운 의미로 연산자를 사용할 수 있습니다.

연산자 오버로딩을 사용하려면 다음과 같은 과정을 따라야 합니다

 

  1. 연산자 오버로딩을 할 때는, 이미 정의된 연산자 함수명을 그대로 사용하되 operator 키워드를 통해 재정의해야 합니다. 이때 연산자 함수명은 대부분 연산자를 영어단어로 바꾼 것입니다. 예를 들어, + 연산자의 경우 plus, - 연산자의 경우 minus와 같은 식으로 작성됩니다.
  2. 연산자를 오버 로딩하려는 클래스에 해당 함수를 선언합니다.
  3. 함수의 매개변수 개수와 타입은 해당 연산자의 의미에 맞게 로직을 설계하고 구성합니다.


아래의 코드를 살펴보도록 하겠습니다.

 

data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }
}

fun main() {
    val point1 = Point(1, 2)
    val point2 = Point(3, 4)
    val result = point1 + point2 // + 연산자를 사용하여 두 점을 더함
    println("Result: $result") // Result: Point(x=4, y=6)
}

 


위 코드에서 Point 클래스는 plus 함수를 정의하여 + 연산자를 오버 로딩했습니다.

이제 Point 객체 간에 + 연산자를 사용하여 두 점을 더할 수 있습니다.

이처럼 연산자 오버로딩을 사용하면 코드가 더 직관적이고 읽기 쉬워지며, 

사용자 정의 클래스에 표준 연산자를 사용할 수 있습니다.

그러나 연산자 오버로딩을 과용하면 코드의 가독성을 해치고 오해를 일으킬 수 있으므로 주의해서 사용해야 합니다.

이외에도 코틀린에 연산자의 오버 로딩에 사용할 수 있는 연산자 함수는 아래와 같은 것들이 있습니다.

연산자 함수 설명
unaryPlus 단항 연산자 +를 오버로딩하여 양수를 반환합니다.
unaryMinus 단항 연산자 -를 오버로딩하여 음수를 반환합니다.
not 단항 연산자 !를 오버로딩하여 논리 부정을 수행합니다.
inc 전위/후위 증가 연산자 ++를 오버로딩하여 값을 1 증가시킵니다.
dec 전위/후위 감소 연산자 --를 오버로딩하여 값을 1 감소시킵니다.
times * 연산자를 오버로딩하여 곱셈을 수행합니다.
div / 연산자를 오버로딩하여 나눗셈을 수행합니다.
rem % 연산자를 오버로딩하여 나머지 연산을 수행합니다.
plus + 연산자를 오버로딩하여 덧셈을 수행합니다.
minus - 연산자를 오버로딩하여 뺄셈을 수행합니다.
rangeTo .. 연산자를 오버로딩하여 범위를 생성합니다.
contains in 연산자를 오버로딩하여 컬렉션에 특정 요소가 포함되어 있는지 확인합니다.
invoke 함수 호출 연산자 ()를 오버로딩하여 함수를 호출합니다.
get 배열 접근 연산자 []를 오버로딩하여 특정 인덱스의 요소를 반환합니다.
set 배열 접근 연산자 []를 오버로딩하여 특정 인덱스의 요소를 설정합니다.
equals == 연산자를 오버로딩하여 객체의 동등성을 비교합니다.
compareTo 비교 연산자를 오버로딩하여 객체 간의 비교를 수행합니다.
iterator 반복 연산자 for를 오버로딩하여 컬렉션에 대한 반복을 수행합니다.
next 반복자의 next() 메서드를 오버로딩하여 다음 요소를 반환합니다.
plusAssign 복합 대입 연산자 +=를 오버로딩하여 덧셈 후 할당을 수행합니다.
minusAssign 복합 대입 연산자 -=를 오버로딩하여 뺄셈 후 할당을 수행합니다.
timesAssign 복합 대입 연산자 *=를 오버로딩하여 곱셈 후 할당을 수행합니다.
divAssign
복합 대입 연산자 /=를 오버로딩하여 나눗셈 후 할당을 수행합니다.
remAssign
복합 대입 연산자 %=를 오버로딩하여 나머지 연산 후 할당을 수행합니다.