프로그래밍 노트/Kotlin

[Kotlin] 고차함수 알아보기

깡냉쓰 2023. 7. 7. 15:21
728x90
반응형

고차함수?

다른 함수를 인자로 받거나 함수를 반환하는 함수(파라미터 혹은 반환 값으로 람다 사용)

함수 타입이란?

Function Type

  • 함수 타입은 아래와 같이 선언
// 타입 추론
val sum = { x: Int, y: Int -> x + y }
// 구체적인 타입 선언
val sum: (Int, Int) -> Int = { x, y -> x + y }

인자로 받은 함수 호출

  • 간단한 고차 함수 정의
fun twoAndThree(operation: (Int, Int) -> Int) {
  val result operation(2, 3)
  println("The result is $result")
}

함수 타입 파라미터에 디폴트 값 지정

fun <T> Collection<T>.joinString(
  separator: String = ", ",
  prefix: String = "",
  postfix: String = "",
  transform: (T) -> String = { it.toString() } // default 지정
) {
  .. blah blah
  transform(element)
}

println(letters.joinToString())
println(letters.joinToString(separator = "! ", transform = { it.toUpperCase() }))

널이 될 수 있는 함수 타입 파라미터

fun foo(callback: (() -> Unit)?) {
  // invoke로 안전 호출 가능 (함수 타입 -> invoke 메소드를 구현하는 인터페이스)
  callback?.invoke()
}

fun <T> Collection<T>.joinString(
  separator: String = ", ",
  prefix: String = "",
  postfix: String = "",
  transform: ((T) -> String)? = null
) {
  .. blah blah
  val str = transform?.invoke(element) ?: elment.toString()
}

함수 반환

  • 함수의 반환 타입으로 함수 타입을 지정해야 한다.
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 }
}

val calculator = getShippingCostCalculator(Delivery.EXPEDITED)
println("Shipping costs ${calculator(Order(3))}")

람다를 활용한 중복 제거

  • 람다를 사옹할 수 없는 환경에서는 아주 복잡한 구조를 만들어야만 피할 수 있는 코드 중복도 람다를 활용하면 간결하고 쉽게 제거할 수 있다.
// 웹 사이트 방문 기록을 분석
data class SiteVisit(
  val path: String,
  val duration: Double,
  val os: OS
)

enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID }
val log = listOf(
  SiteVisit("/", 34.0, OS.WINDOWS),
  SiteVisit("/", 22.0, OS.MAC),
  SiteVisit("/login", 12.0, OS.WINDOWS),
  SiteVisit("/signup", 8.0, OS.IOS),
  SiteVisit("/", 16.3, OS.ANDROID)
)
  • 하드코딩한 필터를 사용해 방문 데이터 분석
val averageMobileDuration = log
  .filter( it.os in setOf(OS.IOS, OS.ANDROID) }
  .map(SiteVisit::duration)
  .average()
println(averageMobileDuration)
// 12.15
  • 고차 함수를 사용해 중복 제거하기
fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean) =
  filter(predicate).map(SiteVisit::duration).average()

println(log.averageDurationFor {
  it.os in setOf(OS.IOS, OS.ANDROID)
})
// 12.15

println(log.averageDurationFor {
  it.os == OS.IOS && it.path == "/signup"
})
  • 함수 타입을 언어가 지원하면 전략 패턴을 간단하게 구현할 수 있다.
    • 인터페이스 선언, 구현 클래스를 통해 전략 정의할 필요가 없음
    • 일반 함수 타입을 사용해 여러 전략을 전달할 수 있음
  • 고차 함수를 활용하면 전통적인 루프와 조건문을 사용할 때보다 더 느려지지 않을까??
    • 항상 더 느린 것은 아님. 어떻게 람다의 성능을 개선할 수 있을지 알아보자 -> 인라인 함수 참고

고차함수 안에서 흐름 제어

람다를 둘러싼 함수로부터 반환 : 람다 안의 return

  • 람다 안에서 return을 사용하면 람다로부터 반환되는 것이 아니라 람다를 호출하는 함수가 실행을 끝내고 반환된다.
  • 자신을 둘러싸고 있는 블록보다 바깥에 있는 다른 블록을 반환하게 만드는 return 문을 넌로컬(non-local) return 이라 부른다.
data class Person(val name: String, val age: Int)
val peopole = listOf(Person("Alice", 29), Person("Bob", 31))

fun lookForAlice(people: List<Person>) {
  people.forEach {
    if (it.name == "Alice") {
      println("Found") // lookForAlice 함수에서 반환됨 (바깥에 있는 다른 블록 반환)
      return
    }
  }
  println("Alice is not found") // 해당 로직은 타지 않게 됨
}
  • return이 바깥쪽 함수를 반환시킬 수 있는 때는 람다를 인자로 받는 함수가 인라인 함수인 경우이다.
  • 인라이닝되지 않는 함수에서는 전달되는 람다안에서 return을 사용할 수 없다.

람다로부터 반환 : 레이블을 사용한 return

  • 람다 식에서도 로컬(local) return을 사용할 수 있다.
  • return으로 실행을 끝내고 싶은 람다 식 앞에 레이블을 붙이고, return 키워드 뒤에 그 레이블을 추가한다.
fun lookForAlice(people: List<Person>) {
  people.forEach label@{ // 람다식 앞에 레이블을 붙인다.
    if (it.name == "Alice") return @label
  }

  println("Alice might be somewhere") // 항상 이 줄이 출력된다.
}
StringBuilder().apply sb@{ // this@sb를 통해 람다의 묵시적 수신 객체에 접근 가능
  listOf(1, 2, 3).apply {
    this@sb.append(this.toString()) // this는 가장 안쪽 수신 객체
  }
}

무명함수 : 기본적으로 로컬 return

  • fun 키워드를 사용해 정의된 가장 안쪽 함수를 반환
    • 람다식은 fun이 사용되지 않으므로, 본문의 return은 람다 밖의 함수를 반환
    • 무명함수는 fun을 사용하므로 자신이 바로 가장 안쪽에 있는 fun으로 정의된 함수가 된다.
fun lookForAlice(people: List<Person>) {
  peopole.forEach(fun (person) { // 람다식 대신 무명 함수
    if (it.name == "Alice") return
    println("${person.name} is not Alice")
  }
}
  • 무명 함수는 일반함수와 다르게 함수 이름과 파라미터 타입을 생략할 수 있다.
728x90
반응형