Chapter 8. 고차 함수: 파라미터와 반환 값으로 람다 사용

sua·2021년 8월 16일
0

Kotlin In Action

목록 보기
8/9
post-thumbnail

8.1 고차 함수 정의

8.1.1 함수 타입

val sum: (Int, Int) -> Int = { x, y -> x + y }
val action: () -> Unit = { println(42) }
  • 함수 타입으로 변수타입을 지정하면 람다 식 안에서 파라미터 타입을 적을 필요 x
  • 함수 타입에서도 반환 타입을 널이 될 수 있는 타입으로 지정 가능
var canReturnNull: (Int, Int) -> Int? = { x, y -> null }
  • 널이 될 수 있는 함수 타입 변수 정의 가능
var funOrNull: ((Int, Int) -> Int)? = null



8.1.2 인자로 받은 함수 호출

[간단한 고차함수 정의하기]
fun twoAndThree(operation: (Int, Int) -> Int) { // 함수 타입인 파라미터를 선언
    val result = operation(2, 3) // 함수 타입인 파라미터를 호출
    println("The result is $result")
}

fun main(args: Array<String>) {
    twoAndThree { a, b -> a + b} // The result is 5
    twoAndThree { a, b -> a * b} // The result is 6
}
[filter 함수를 단순하게 만든 버전 구현하기]
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) // "predicate" 파라미터로 전달받은 함수를 호출
    }
    
    return sb.toString()
}

fun main(args: Array<String>) {
    println("ab1c".filter { it in 'a'..'z'}) // 람다를 "predicate" 파라미터로 전달
}



8.1.3 자바에서 코틀린 함수 타입 사용

반환타입이 Unit인 함수 타입의 파라미터 위치에 void를 반환하는 자바 람다를 넘길 수 x



8.1.4 디폴트 값을 지정한 함수 타입 파라미터나 널이 될 수 있는 함수 타입 파라미터

[함수 타입의 파라미터에 대한 디폴트 값 지정하기]
fun <T> Collection<T>.joinToString(
    separator: String = ", ",
    prefix: String = "",
    postfix: String = "",
    transform: (T) -> String = { it.toString() } // 함수 타입 파라미터를 선언하면서 람다를 디폴트 값으로 지정
) : String {
    val result = StringBuilder(prefix)
    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(transform(element)) // "transform" 파라미터로 받은 함수를 호출
    }
    
    result.append(postfix)
    return result.toString()
}

fun main(args: Array<String>) {
    val letters = listOf("Alpha", "Beta")
    println(letters.joinToString()) // 디폴트 변환 함수 사용 - Alpha, Beta
    println(letters.joinToString { it.toLowerCase() }) // 람다를 인자로 전달 - alpha. beta
    println(letters.joinToString(separator = "! ", postfix = "! ", ...transform = { it.toUpperCase()})) // ALPHA! BETA!
}
[널이 될 수 있는 함수 타입 파라미터를 사용하기]
fun <T> Collection<T>.joinToString(
    separator: String = ", ",
    prefix: String = "",
    postfix: String = "",
    transform: ((T) -> String)? = null
) : String {
    val result = StringBuilder(prefix)
    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator)
        val str = transform?.invoke(element) // 안전 호출을 사용해 함수를 호출
            ?: element.toString() // 엘비스 연산자를 사용해 람다를 인자로 받지 않은 경우를 처리
        result.append(str)
    }

    result.append(postfix)
    return result.toString()
}

fun main(args: Array<String>) {
    val letters = listOf("Alpha", "Beta")
    println(letters.joinToString()) // 디폴트 변환 함수 사용 - Alpha, Beta
    println(letters.joinToString { it.toLowerCase() }) // 람다를 인자로 전달 - alpha. beta
}



8.1.5 함수를 함수에서 반환

[함수를 반환하는 함수 정의]
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(args: Array<String>) {
    val calculator = getShippingCostCalculator(Delivery.EXPEDITED) // 반환 받은 함수를 변수에 저장
    println("Shipping costs ${calculator(Order(3))}") // 반환 받은 함수를 호출 - Shipping costs 12.3
}



8.1.6 람다를 활용한 중복 제거

data class SiteVisit( // 사이트 방문 데이터 정의
    val path: String,
    val duration: Double,
    val os: OS
)
enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID }

// 중복 코드를 별도 함수로 추출
fun List<SiteVisit>.averageDurationFor(os: OS) = filter { it.os == os }.map(SiteVisit::duration).average()

// 고차 함수를 사용해 중복 제거하기
fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean) = filter(predicate).map(SiteVisit::duration).average()

fun main(args: Array<String>) {
    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 averageWindowsDuration = log // 사이트 방문 데이터를 코딩한 필터를 사용해 분석
        .filter { it.os == OS.WINDOWS}
        .map(SiteVisit::duration)
        .average()
    println(averageWindowsDuration) // 23.0
    
    println(log.averageDurationFor(OS.WINDOWS)) // 23.0
    println(log.averageDurationFor(OS.MAC)) // 22.0
    
    
    // 복잡하게 하드코딩한 필터를 사용해 방문 데이터 분석
    val averageMobileDuration = log
        .filter { it.os in setOf(OS.IOS, OS.ANDROID) }
        .map(SiteVisit::duration)
        .average()
    println(averageMobileDuration) // 12.15
    
    
    println(log.averageDurationFor {
        it.os == OS.IOS && it.path == "/signup"
    }) // 8.0
}




8.2 인라인 함수: 람다의 부가 비용 없애기

8.2.1 인라이닝이 작동하는 방식

[인라인 함수 정의하기]
inline fun <T> synchronized(lock: Lock, action: () -> T) : T {
    lock.lock()
    try {
        return action()
    }
    finally {
        lock.unlock()
    }
}

fun foo(l: Lock) {
    println("Before sync")
    synchronized(l) {
        println("Action")
    }
    println("After sync")
}

class LockOwner(val lock: Lock) {
    fun runUnderLock(body: () -> Unit) {
        synchronized(lock, body) // 람다 대신에 함수 타입인 변수를 인자로 넘김
    }
}

fun main(args: Array<String>) {
    val l = Lock()
    synchronized(l) {
        // ...
    }
}



8.2.2 인라인 함수의 한계

noinline 변경자를 파라미터 이름 앞에 붙여서 인라이닝 금지 가능



8.2.3 컬렉션 연산 인라이닝

시퀀스를 통해 성능을 향상시킬 수 있는 경우는 컬렉션 크기가 큰 경우뿐



8.2.4 함수를 인라인으로 선언해야 하는 경우

람다를 인자로 받는 함수를 인라이닝하면 이익이 많음



8.2.5 자원 관리를 위해 인라인된 람다 사용

[use 함수를 자원 관리에 활용하기]
fun readFirstLineFromFile(path: String) : String { 
    BufferedReader (FileReader (path)).use { br -> // BufferedReader 객체를 만들고 "use" 함수를 호출하면서 파일에 대한 연산을 실행할 람다를 넘김
         return br.readLine() // 자원(파일)에서 맨 처음 가져온 한 줄을 람다가 아닌 readFirstLineFromFile에서 반환
    }
}




8.3 고차 함수 안에서 흐름 제어

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

람다 안에서 return을 사용하면 람다를 호출하는 함수가 실행을 끝내고 반환 -> non-local return

  • 인라이닝이 되지 않는 함수에 전달되는 람다 안에서 return을 사용할 수는 x



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

로컬 return : 람다의 실행을 끝내고 람다를 호출했던 코드의 실행을 계속 이어감 -> 람다 식 앞에 label@을 붙이고, return 키워드 뒤에 @label을 추가 해야 함

[레이블을 통해 로컬 리턴 사용하기]
fun lookForAlice(people: List<Person>) {
    people.forEach label@{ // 람다 식 앞에 레이블을 붙임
        if (it.name == "Alice") return@label // return@label은 앞에서 정의한 레이블을 참조
    }
    println("Alice might be somewhere") // 항상 이 줄이 출력됨
}

[함수 이름을 return 레이블로 사용하기]
// 람다를 인자로 받는 인라인 함수의 이름을 return 뒤에 레이블로 사용해도 됨
fun lookForAlice(people: List<Person>) {
    people.forEach { 
        if (it.name == "Alice") return@forEach // return@forEach는 람다식으로부터 반환
    }
    println("Alice might be somewhere") // 항상 이 줄이 출력됨
}

fun main(args: Array<String>) {
    lookForAlice(people) // Alice might be somewhere
}



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

[무명 함수 안에서 return 사용]
fun lookForAlice(people: List<Person>) {
    people.forEach(fun (person) { // 람다 식 대신 무명 함수를 사용
        if (person.name == "Alice") return // "return"은 가장 가까운 함수를 가리키는데 여기서 가장 가까운 함수는 무명 함수
        println("${person.name} is not Alice")
    })
}

fun main(args: Array<String>) {
    lookForAlice(people) // Bob is not Alice
}
[filter에 무명 함수 넘기기]
fun main(args: Array<String>) {
    people.filter(fun (person) : Boolean {
        return person.age < 30
    })
}
profile
가보자고

0개의 댓글

관련 채용 정보