본문 바로가기

Language/Kotlin

코틀린[Kotlin] 함수형 프로그래밍 (고차 함수, 람다 함수, 확장 함수)

이번엔 함수형 프로그래밍을 코틀린에서 어떻게 사용할 수 있는지 알아보겠습니다.

 
 

고차 함수 (Higher-Order Functions)

고차 함수는 다른 함수를 매개변수로 취하거나 함수를 반환하는 함수입니다. 

일급 객체란?

Kotlin 함수는 일급 객체(First-class citizen)입니다. 아래 3가지 조건을 충족한다면 1급 객체라고 할 수 있습니다.

  1. 변수나 데이터에 할당할 수 있어야 한다.
  2. 객체의 인자로 넘길 수 있어야 한다.
  3. 객체의 리턴값으로 반환할 수 있어야 한다.

함수를 매개변수로 사용하기

fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int {  // 1
    return operation(x, y)                                          // 2
}

fun sum(x: Int, y: Int) = x + y                                     // 3

fun main() {
    val sumResult = calculate(4, 5, ::sum)                          // 4
    val mulResult = calculate(4, 5) { a, b -> a * b }               // 5
    println("sumResult $sumResult, mulResult $mulResult")
}
  1. 고차 함수를 선언합니다. 두 개의 정수 매개변수 x와 y가 필요합니다. 또한 다른 함수 연산을 매개변수로 취합니다. operation 매개변수와 반환 유형도 선언에 정의되어 있습니다.
  2. 고차 함수는 제공된 인자를 사용하여 작업 호출 결과를 반환합니다.
  3. operation signature와 일치하는 함수를 선언합니다.
  4. 두 개의 정수 값과 함수 인자 ::sum을 전달하는 고차 함수를 호출합니다. ::는 Kotlin에서 이름으로 함수를 참조하는 표기법입니다.
  5. 람다를 함수 인자로 전달하는 고차 함수를 호출합니다. 

함수를 반환 값으로 사용하기

fun operation(): (Int) -> Int {                                     // 1
    return ::square
}

fun square(x: Int) = x * x                                          // 2

fun main() {
    val func = operation()                                          // 3
    println(func(2))                                                // 4
}
  1. 함수를 반환하는 고차 함수를 선언합니다. (Int) -> Int는 square 함수의 매개변수와 반환 유형을 나타냅니다.
  2. 파라미터, 반환형이 일치하는 함수를 선언합니다.
  3. 변수에 할당된 결과를 가져오는 작업을 호출합니다. 여기서 func는 연산에 의해 반환되는 제곱이 됩니다.
  4. square 함수가 실제로 실행됩니다.

람다 함수(Lambda Functions)

Lambda 함수("lambdas")는 임시로 함수를 생성하는 간단한 방법입니다. 타입 추론과 암시적 it 변수 덕분에 많은 경우에 람다를 매우 간결하게 표시할 수 있습니다.

val upperCase1: (String) -> String = { str: String -> str.uppercase() } // 1

val upperCase2: (String) -> String = { str -> str.uppercase() }         // 2

val upperCase3 = { str: String -> str.uppercase() }                     // 3

// val upperCase4 = { str -> str.uppercase() }                          // 4

val upperCase5: (String) -> String = { it.uppercase() }                 // 5

val upperCase6: (String) -> String = String::uppercase                  // 6

println(upperCase1("hello"))
println(upperCase2("hello"))
println(upperCase3("hello"))
println(upperCase5("hello"))
println(upperCase6("hello"))
  1. 람다는 타입이 (String) -> String(함수 유형)인 변수에 할당되는 중괄호 안의 부분입니다.
  2. 람다 내부 형식 추론: 람다 매개 변수의 타입은 할당된 변수 형식에서 유추됩니다.
  3. 람다 외부 형식 유추: 변수 타입은 람다 매개 변수 및 반환 값의 타입에서 유추됩니다.
  4. 둘 다 함께 수행할 수 없으며 컴파일러는 그런 방식으로 타입을 추론할 수 없습니다.
  5. 단일 매개변수가 있는 람다의 경우 명시적으로 이름을 지정할 필요가 없습니다. 대신 암시적 it 변수를 사용할 수 있습니다. 이는 유형을 추론할 수 있는 경우에 특히 유용합니다
  6. 람다가 단일 함수 호출로 구성된 경우 함수 포인터(::)를 사용할 수 있습니다.

확장 함수와 프로퍼티 (Extension Functions and Properties)

Kotlin을 사용하면 확장 메커니즘을 사용하여 모든 클래스에 새 멤버를 추가할 수 있습니다. 즉, 확장 함수와 확장 프로퍼티의 두 가지 유형이 있습니다. 일반 함수 및 프로퍼티와 매우 유사해 보이지만 한 가지 중요한 차이점이 있는데 그것은 확장할 유형을 지정해야 한다는 것입니다.

data class Item(val name: String, val price: Float)                                         // 1  

data class Order(val items: Collection<Item>)  

fun Order.maxPricedItemValue(): Float = this.items.maxByOrNull { it.price }?.price ?: 0F    // 2  
fun Order.maxPricedItemName() = this.items.maxByOrNull { it.price }?.name ?: "NO_PRODUCTS"

val Order.commaDelimitedItemNames: String                                                   // 3
    get() = items.map { it.name }.joinToString()

fun main() {

    val order = Order(listOf(Item("Bread", 25.0F), Item("Wine", 29.0F), Item("Water", 12.0F)))
    
    println("Max priced item name: ${order.maxPricedItemName()}")                           // 4
    // Max priced item value: 29.0
    println("Max priced item value: ${order.maxPricedItemValue()}")
    // Items: Bread, Wine, Water
    println("Items: ${order.commaDelimitedItemNames}")                                      // 5

}
  1. Item 및 Order의 간단한 모델을 정의합니다. Order에는 Item 객체 컬렉션이 포함됩니다.
  2. Order 타입에 대한 확장 함수를 추가합니다.
  3. Order 타입에 대한 확장 프로퍼티를 추가합니다. 
  4. Order의 인스턴스에서 직접 확장 함수를 호출합니다.
  5. Order 인스턴스의 확장 속성에 접근합니다.

null 참조에서 확장을 실행하는 것도 가능합니다. 확장 함수에서 개체에 null이 있는지 확인하고 코드에서 결과를 사용할 수 있습니다.

fun <T> T?.nullSafeToString() = this?.toString() ?: "NULL"  // 1