본문 바로가기

Kotlin/Play Kotlin Example

Kotlin 공식 Example로 공부하기 - Functional(Higher-Order Functions, lambda, extension functions)

Functional


Higher-Order Functions(고차 함수)

고차함수(Higher-Order function)는 다른 함수를 파라미터로 사용하거나 함수를 반환하는 함수입니다.

함수를 함수의 파라미터로 쓸 때

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. 고차함수(여기서는 calculate 함수)를 선언합니다. 이 함수는 두개의 정수 파라미터(x, y)를 갖고 추가적으로 또 다른 함수 operation 이라는 이름의 파라미터를 갖습니다. operation 파라미터와 리턴 타입도 선언부에 정의되어있습니다.
  2. 이 고차함수는 제공된 인자(arguments)와 함께 operation 으로 들어온 함수를 호출하고 그 결과를 리턴합니다.
  3. operation 함수의 시그니처에 매칭되는 함수를 선언합니다.
  4. 두 개의 정수 값과 함수(::sum)를 전달하는 고차함수를 호출합니다.
  5. :: 은 코틀린에서 이름으로 함수를 참조하는 표기법입니다. (함수 참조 or 메소드 참조)

함수를 함수의 리턴타입으로 쓸 때

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. 함수를 리턴하는 고차함수(여기서는 operation 함수)를 정의합니다. (Int) -> Intsquare 함수의 리턴 타입과 파라미터를 나타냅니다.
  2. 메소드 시그니처와 일치하는 함수를 선언합니다.
  3. 변수에 할당된 결과를 얻기위해 operation 함수를 호출합니다. (함수의 결과가 함수이므로 함수 호출해서 변수에 할당받았다 뭐 이런 느낌이지 않을까합니다.)
  4. func 함수를 호출합니다. 실제로 square 함수가 실행됩니다.

Lambda Functions(람다 함수)

람다 함수("lambdas")는 함수를 임시로 만드는 간단한 방법입니다. 람다는 타입 추론과 암묵적인 it 변수 덕분에 다양한 경우에서 매우 간단하게 표시될 수 있습니다.

// 모든 예제는 대문자(upper-casing)로 만드는 함수 객체를 만듭니다.
// 이 함수는 문자열을 문자열로 만드는 함수입니다.

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

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

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

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

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

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

println(upperCase1("hello"))
println(upperCase2("hello"))
println(upperCase3("hello"))
println(upperCase5("hello"))
println(upperCase6("hello"))
  1. 타입을 명시할 수 있는 모든 곳에 타입을 명시한 람다 함수입니다. 람다는 함수 타입((String) -> String)의 변수(upperCase1)에 할당되는 중괄호 안 쪽 부분입니다.
  2. 람다 안에서 타입 추론 : 람다 파라미터의 타입은 앞에 변수에 할당되는 타입을 통해 추론됩니다.
    • 중괄호 안에서 파라미터 부분의 String은 명시하지 않아도 변수(upperCase2)의 타입을 보고 str이 String으로 추론됩니다.
  3. 람다 밖에서 타입 추론 : 변수(uppercase3)의 타입은 람다의 파라미터와 리턴타입을 통해 추론됩니다.
    • 중괄호 안에 파라미터부분(str: String)과 리턴되는 값(str.toUppercase())의 타입이 명시적이므로 변수(uppercase3)의 타입은 명시하지 않아도 추론됩니다.
  4. 앞서 말한 생략과 추론을 둘 다 할 수 없습니다. 컴파일러는 그런방식으로는 추론할 방법이 없습니다.
  5. 람다의 파라미터가 1개인 경우, 파라미터의 이름을 명시할 필요가 없습니다. 대신에 it 라는 변수를 암묵적으로 사용할 수 있습니다. it 변수의 타입이 추론 가능한 경우에 특히 유용합니다. (자주 이런 경우를 볼 수 있습니다.)
  6. 만약 람다에서 하나의 함수를 호출한다면 함수포인터(메소드 참조(::))를 이용할 수 있습니다.

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

코틀린을 사용하면 확장 메커니즘을 이용해서 모든 클래스에 새로운 멤버를 추가할 수 있습니다.

두 개의 확장 기능(확장 함수, 확장 프로퍼티)이 있습니다.

그것들은 일반적인 함수와 프로퍼티와 비슷하지만, 한 가지 중요한 차이점이 있습니다. 확장하는 타입을 지정해야합니다.

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

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

fun Order.maxPricedItemValue(): Float = this.items.maxBy { it.price }?.price ?: 0F    // 2  
fun Order.maxPricedItemName() = this.items.maxBy { 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
    println("Max priced item value: ${order.maxPricedItemValue()}")
    println("Items: ${order.commaDelimitedItemNames}")                                // 5

}
  1. ItemOrder 의 간단한 모델을 정의합니다. OrderItem 객체의 컬렉션이 포함될 수 있습니다.
  2. Order 타입에 대한 확장 함수를 추가합니다.
  3. Order 타입에 대한 확장 프로퍼티를 추가합니다.
  4. Order 타입의 인스턴스를 이용하여 직접 확장 함수를 호출합니다.
  5. Order 타입의 인스턴스를 이용하여 확장 프로퍼티에 접근합니다.

심지어 null 참조에서도 확장함수를 실행하는게 가능합니다.

확장함수에서 객체가 null인지 체크하고 결과를 코드에 사용할 수 있습니다.

fun <T> T?.nullSafeToString() = this?.toString() ?: "NULL"  // 어마어마한 코드...