고차함수

안석주·2021년 10월 31일
0

고차함수란?

코틀린에서 함수는 일급 함수이다. 어떤 프로그래밍 언어에서 함수가 일급 객체라면 그 언어의 함수는 일급 함수라 불리는데, 이때 일급 객체는 다른 객체들에 적용 가능한 연산을 모두 지원하는 객체를 의미한다.

  • 일급 객체는 함수의 매개변수가 될 수 있다.
  • 일급 객체는 함수의 return값이 될 수 있다.
  • 일급 객체는 할당 명령문(=, 대입)의 대상이 될 수 있다.
  • 일급 객체는 동일 비교(==, equal)의 대상이 될 수 있다.

즉 함수는 파라미터로 쓰일수도, 리턴타입에 쓰일수도, 비교 또는 변수에 대입까지 모두 가능한 것이 바로 코틀린 함수의 특징이다.

이전 포스트에서 본 map, with같은 함수들 모두 고차함수이다!

고차함수의 기본

먼저 함수를 변수에 초기화해줄 때를 예로들어보자!

val sum = { x:Int, y:Int -> x + y }

이 경우 컴파일러가 sum이 함수 타입이라는 것을 추론해준다. 그래서 명시적으로 타입을 작성해주면,

val sum: (Int, Int) -> Int = { x:Int, y:Int -> x + y }

이런 식으로 작성된다. 함수의 파라미터 타입을 괄호 안에 넣고, 리턴 타입은 화살표 뒤에 작성해준다.
즉 함수를 인자로 넘겨주고 싶다면 아래의 형태를 꼭 기억해야 한다!

(Int, String) -> Unit   // Int, String 순서로 인자를 받고 Unit으로 return 한다!

Unit은 의미 있는 값을 반환하지 않을 때 선언하는데(Println 같은..), 함수의 선언에서는 Unit은 생략이 가능하다. 하지만 함수 타입의 선언을 할때는 Unit은 생략할 수 없다.

아래와 같이 타입을 미리 작성해주면 뒤의 함수 형태 본체에서는 타입을 명시해줄 필요가 없다!

val sum: (Int, Int) -> Int = { x, y -> x + y }

또한 반환 타입을 null이 될 수 있는 함수 타입 변수를 정의할 수도 있다.

val canRetrunNull: (Int, Int) -> Int? = { x, y -> null }

반환 타입이 아닌 함수 타입 전체가 null이 될 수 있는 타입을 지정할 수 있다. 이는 괄호를 통해 전체를 묶어준 뒤 ?연산자를 통해 가능하다.

val funOrNull: ((Int, Int) -> Int)? = null 

인자로 받았으니 실행은?

이제 인자로 함수를 파라미터로 받는 방법을 알았으니 받은 함수를 실행해보자.

fun twoAndThree(opertaion: (Int, Int) -> Int){
    val result = operation(2, 3)
    println("the result is $result")
}
fun main(){
    twoAndThree{ a, b -> a + b }
    twoAndThree{ a, b -> a * b }
}
=> the result is 5    
   the result is 6

operation이라는 함수는 Int형으로 이루어진 파라미터를 두개 가지고 있고, 리턴 값으로 Int형인 함수는 모두 들어갈 수 있다. main 함수에서는 람다 함수로 그러한 a+b, a*b 함수를 넘겨주었고, 함수안에서 인자로 받은 함수를 실행해주었다.

고차 함수와 확장 함수

이제 실행 방법은 알게됐다. 그렇다면 이전에 사용했던 String에 대한 filter를 구현해보자.
이전에 알아봤던 확장 함수와 함께 사용해본다.

// predicate라는 함수는 char을 받아 return 타입으로 true, false를 리턴하고,
// filter라는 함수는 string을 리턴한다.
fun String.filter(predicate: (Char) -> Boolean) : String {
    val sb = StringBuilder()
    for (index in 0 until length) {
        val element = get(index)
        if (predicate(element)) sb.append(element)
    }
	return sb.toString()
}

println("ab1c".filter{ it in 'a'..'z' }
=> abc

String의 확장함수로 filter라는 확장 함수를 생성했고, 고차함수를 통해 사용하는 부분에서 람다(코드 조각)을 입력 받을 수 있다. 이 입력 받을 수 있는 람다는 위에 선언한 Char을 받아 Boolean을 리턴하는 함수는 모두 입력받을 수 있다.

즉 순서대로 나열 해보면
1. String.filter 함수에서 for문이 실행되고, String의 수신 객체인 "ab1c"가 element에 get(index)를 통해 한글자씩 담긴다.
2. element를 predicate라는 함수에 전달해주면, 위에 언급했듯이 Char형으로 input이 들어오고,
3. filter의 람다에서 it in 'a'..'z'의 값으로 true 또는 false를 리턴하여준다.
4. 이를 통해 'a'부터 'z'사이의 값만 true로 되어 sb에 append되고,
5. 마지막 함수의 리턴값은 String이기 때문에 sb를 String으로 만들어 출력해준다.

함수에서 함수 반환

이번에는 함수에서 리턴 값으로 함수를 이용하는 방법을 알아본다.
보통은 함수를 파라미터로 받아 사용하는 케이스가 많지만, 함수를 반환하는 것도 필요한 경우가 있으니 짧은 예시를 통해 알아보자!
예를 들어 사용자가 선택한 배송 수단에 따른 배송비를 계산하는 방법이 달라지는 함수를 만들어본다.

enum class Delivery { STANDARD, EXPEDITED }
class Order(val itemCount: Int)

fun getShippingCostCalculator(
    delivery: Delivery
): (Order) -> Double {
    if (delivery == Delivery.EXPEDITED) {
        return { order -> 6 + 2.1 * order.itemCount }
    }
    return { order -> 1.2 * order.itemCount }
}

fun main() {
    val calculator = getShippingCostCalculator(Delivery.EXPEDITED)
    println(calculator(Order(3)))
}
=> 12.3

이를 하나씩 보면, getShippingCostCalculator 함수의 파라미터로 Delivery의 형태를 입력받고, 그에 따른 람다 함수를 리턴한다. 그렇게 해서 리턴 한 람다 함수에 작성한 그대로 Order형식의 파라미터를 전달 후 Double형의 리턴 값을 받는다. 위의 예제에서는 Order(3)을 전달, 결과로 12.3을 리턴받는다!

후기

사실 더 잘 정리하고 싶었는데... 내용이 많이 부족한 것 같다... 다음 달에는 더 좋은 글로 나타나겠습니다...
2021/10/31

profile
뜻을 알고 코딩하기

0개의 댓글