코틀린: 순수 함수와 순수하지 않은 함수

woga·2023년 1월 22일
0

코틀린 공부

목록 보기
3/54
post-thumbnail

순수 함수와 순수하지 않은 함수

앞서서 언급했던 순수 함수를 기억하는가?

순수 함수가 되기 위한 필요한 조건들이 있다.

  • 함수 외부의 어떤 것도 변이시켜서는 안 된다. 내부에서 상태를 변이시키더라도 그 상태를 외부에서 관찰할 수 없어야 한다.

  • 인자를 변이시켜서는 안 된다.

  • 예외나 오류를 던져서는 안 된다.

  • 항상 값을 반환해야 한다.

  • 인자가 같으면 항상 같은 결과를 내놓아야 한다.

그리고 물론 순수 함수 (pure function)의 반대말은 순수하지 않은 함수(impure)라고 부른다.

다음 예제를 통해 어떤 함수가 순수 함수고, 순수하지 않은 함수인지 찾아보자.

class FunFunctions {

	var percent1 = 5
    private var percent2 = 9
    val percent3 = 13
    
    fun add(a: Int, b: Int): Int = a + b
    
    fun mult(a: Int, b: Int?): Int = 5
    
    fun div(a: Int, b: Int): Int = a / b
    
    fun div(a: Double, b: Double): Double = a / b
    
    fun applyTax1(a: Int): Int = a / 100 * (100 + percent1)
    
    fun applyTax2(a: Int): Int = a / 100 * (100 + percent2)
   
   	fun applyTax3(a: Int): Int = a / 100 * (100 + percent3)
    
    fun append1(i: Int, list: MutableList<Int>): List<Int> {
    	list.add(i)
        return list
    }
    
    fun append2(i: Int, list: List<Int>) = list + i

이 클래스 내부의 많은 함수들이 있는데 어떤 함수/메서드가 순수 함수인지 구별할 수 있겠는가?
이 다음부터 하나씩 함수가 어떤 함수인지 구별할 예정이라 한 번 잠깐 멈춰서 어떤 함수가 순수함수일지 생각해보자!



첫 번째 함수

fun add(a: Int, b: Int): Int = a + b

이 함수는 순수 함수다.
항상 인자에 따라 정해지는 값을 반환하기 때문이다. 이 함수는 인자를 변이시키지도 않고 외부 세계와 전혀 상호 작용하지도 않는다.

a + b가 Int의 최대 범위를 넘어 오버플로가 발생하면 함수가 잘못 작동할 수는 있지만 예외를 던지지는 않는다. 물론 오버플로가 발생하면 잘못된 값이 결과로 반환되지만 그것은 별개의 문제다.

인자가 같으면 항상 같은 값을 돌려줘야 하지만, 이것이 결과가 꼭 정환한 값이어야 한다는 뜻은 아닌다.

두 번째 함수

fun mult(a: Int, b: Int?): Int = 5

이 함수는 순수 함수다.
정확히는 어떤 값을 받든 관계없이 같은 값을 반환한다. 이 함수는 상수다.

세 번째 함수

fun div(a: Int, b: Int): Int = a / b

이 함수는 순수하지 않은 함수다.
제수가 0이면 예외가 발생하기 때문이다. 오버플로가 나면 잘못된 값을 반환하는 것과 또 다른 문제다. 그래서 div를 순수 함수로 만들려면 두 번째 파라미터가 0인지 검사해서 0이면 오류를 뜻하는 값을 반환해야 한다.

네 번째 함수

fun div(a: Double, b: Double): Double = a / b

이 함수는 순수 함수다.
0.0으로 나누면 예외가 발생하는 대신 Infinity-Infinity가 반환되며, 이 둘은 Dobule의 인스턴스다.

게다가 덧셈에 대해서는 Infinity에서 덧셈을 해도 무한이어도 무한에서 0.0을 나누면 NaN(숫자가 아님을 뜻하는 Not a Number)이 나온다.

다섯 번째 함수

var percent1 = 5
fun applyTax1(a: Int): Int = a / 100 * (100 + percent1)

이 함수는 순수하지 않은 함수다.
percent1은 공개 필드이며 variable이라 함수 호출 중간에 값이 바뀔 수 있다. 따라서 같은 인자를 가지고 두 번 함수를 호출해도 결과가 달라질 수 있기 때문에 순수 함수가 아니다.

여섯 번째 함수

private var percent2 = 9
fun applyTax2(a: Int): Int = a / 100 * (100 + percent2)

이 함수는 순수 함수다.
이 함수의 결과는 percent2의 값에 따라 달라지만다. 하지만 어떤 코드도 이 변수를 변이시키지 않기 때문에 순수 함수이다. 하지만 applyTax2 코드를 변경하지 않더라도 percent2의 값을 변경하는 다른 함수를 추가하면 함수의 결과가 달라질 수 있어 이 함수는 안전하지 않다. 그래서 실제 변이가 필요하지 않다면 항상 모든 것을 불변으로 유지해야 한다. 기본적으로 val 키워드를 항상 사용하는 걸 추천한다.

일곱 번째 함수

val percent3 = 13
fun applyTax3(a: Int): Int = a / 100 * (100 + percent3)

이 함수는 순수 함수다.
불변 필드를 사용하기 때문이다.

여덟 번째 함수

fun append1(i: Int, list: MutableList<Int>): List<Int> {
    	list.add(i)
        return list
    }

이 함수는 순수하지 않은 함수다.
append1 함수는 인자를 반환하기 전에 변이시킨다.

아홉 번째 함수

fun append2(i: Int, list: List<Int>) = list + i

이 함수는 순수 함수다.
이 함수는 원소를 인자로 받은 리스트에 더하는 것으로 보인다. 하지만 실제로는 그렇지 않다.
list + i라는 식은 list의 모든 원소가 같은 순서로 있고 맨 뒤에 i가 더 들어 있는 새로운 (불변) 리스트를 만들어낸다. 아무것도 변이된 것이 없다.

마치며

오늘은 순수 함수를 예제를 보며 조금 더 이해하게 됐다. 앞에서 순수함수에 이야기를 할 때 개념적으로만 이해를 한 상태였는데 코드 예제와 함께 체크하니깐 더 이해하기 좋았다! 이 자료가 궁금할 다른 누군가에게도 도움이 되길 바라며 기록을 마친다.

profile
와니와니와니와니 당근당근

0개의 댓글