Kotlin-In-Action | #8. 고차 함수 : 파라미터와 반환 값으로 람다 사용

보람·2022년 5월 17일
0

Kotlin-In-Action

목록 보기
9/12

고차함수

  • 다른 함수를 인자로 받거나 함수를 반환하는 함수
  • ex) filter는 술어 함수를 인자로 받으므로 고차 함수임
  • val sum: (Int, Int) -> Int = {x,y -> x+y}
    • (Int, Int) : 파라미터 타입
    • ->Int : 리턴 타입
    • {} : 반환식

디폴트 값 | Nullable 함수 파라미터

fun <T> Collection<T>.joinToString(
        separator: String = ", ",
        prefix: String = "",
        postfix: String = "",
        transform: ((T) -> String)? = null//null이 될 수 있는 함수 타엡의 파라미터 선언
//transform: (T) -> String = { it.toString() } //default 값 처리
): String {
//함수식
}
  • transform: ((T) -> String)? = null : null가능, 함수 내부에서 널인 경우에 대한 처리해야함
  • transform: (T) -> String = { it.toString() } : default 값 지정

함수를 함수에서 반환

  • 함수가 함수를 인자로 받아야 할 필요가 있는 경우가 함수가 함수를 반환할 필요가 있는 경우보다 훨씬 많음
fun getShippingCostCalculator(
        delivery: Delivery): (Order) -> Double  //Double 이 리턴타입
{ // 함수를 반환하는 함수 선언
    if (delivery == Delivery.EXPEDITED) { //상태값에 따라서 람다를 반환
        return { order -> 6 + 2.1 * order.itemCount }
    }

    return { order -> 1.2 * order.itemCount } //상태값에 따라서 람다를 반환
}

//main
val calculator =
        getShippingCostCalculator(Delivery.EXPEDITED) //반환받은 함수를 변수에 저장
        //함수를!! 변수에!! 저장하는거임!!!
    println("Shipping costs ${calculator(Order(3))}") //Shipping costs 12.3
    //반환받은 함수를 호출
    //함수를!! 변수가 아니라 함수를!! 호출하는 것!!!

람다를 활용한 중복 제거

fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean) =
        filter(predicate).map(SiteVisit::duration).average()

fun main(args: Array<String>) {
    println(log.averageDurationFor {
        it.os in setOf(OS.ANDROID, OS.IOS) }) 
    println(log.averageDurationFor {
        it.os == OS.IOS && it.path == "/signup" })
}
  • 전략 패턴 : 실행 중에 알고리즘을 선택할 수 있게 하는 행위 소프트웨어 디자인 패턴이다.
  • it.os in setOf(OS.ANDROID, OS.IOS) 자체를 파라미터로 보내는 것이 전략 패턴 방법임!
  • 뽑아낼 수 있는 조건이 많고자 할때 조건별로 함수를 만드는 것이 아니라 그 조건 자체를 넘기는 것!! 이로써 중복 제거🌝

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

  • 람다가 변수를 포획하면 람다가 생성되는 시점마다 새로운 무명 클래스 객체가 생김!
    • 무명 클래스 생성시마다 부가 비용이 따르는데..🥺
      - 어떤 함수를 inline으로 선언하면 그 함수의 본문이 inline
      - 함수를 호출하는 바이트코드가 아닌 / 함수 본문 그 자체를 번역한 바이트 코드로 컴파일 됨!
      - 일반 명령문같이 효율적인 코드 생성 가능!
      ==> 람다식 사용시 무의미한 객체 생성을 방지하는 것이 inline 함수임!

인라인 함수의 한계

  • 람다를 사용하는 모든 함수를 인라이닝 할 수는 없음😭
  • 직접 펼치는 것이기 때문에 한정 사용이 가능함
    • 그래서 인라인 함수는 모두 크기가 아주 작다.
  • 시퀀스 메서드 중에는 새 시퀀스를 반환하는 함수가 많음 -> 맵에 전달되는 인자를 일반적인(인라인X) 함수 표현으로 만들어야 함 ==> 인라이닝 대신 무명 클래스 인스턴스로 만들 것

코드 길이가 너무 커서 인라이닝을 하면 안되는 코드라면..

인라이닝 금지 noinline

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit {
//...
}

컬렉션 연산 인라이닝

  • 컬렉션 함수는 대부분 람다를 인자로 받음
  • 인라인 함수인 filter 함수
  • 시퀀스의 람다를 인라인하지 않음, 람다의 부가 비용을 없애는 인라이닝이 지원되지 않는 시퀀스라면!! 크기가 작은 컬렉션은 오히려 일반 컬렉션 연산이 더 성능이 나을 수도 있다!

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

  • 람다를 인자로 받는 함수만 성능이 좋아질 가능성이 높음
  • 인라이닝을 통해 없앨 수 있는 부가 비용이 상당함
  • 현재의 JVM은 함수 호출과 람다를 인라이닝해 줄 정도는 아님
  • 일반 람다에는 없는 넌로컬 반환 사용 가능

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

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

val people = listOf(Person("Alice", 29), Person("Bob", 31))

fun lookForAlice(people: List<Person>) {
    people.forEach {
        if (it.name == "Alice") {
            println("Found!")
            return //forEach를 return 하는 것이 아닌 가장 근처에 있는 람다 함수인 lookForAlice() 를 반환.
        }
    }
    println("Alice is not found")
}

//main
lookForAlice(people) //Found! 출력후 종료
  • forEach 내부에 return을 할 때 forEach가 종료된다고 생각하기 쉽지만 그 람다를 호출하는 함수(lookForAlice)가 실행을 끝내고 반환한다.
    • 자신의 블록보다 더 바깥 블록을 반환하게 만드는 return문을 넌로컬 return이라고 함

슨생님! forEach만 끝내고 싶으면요?!🥸

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

fun lookForAlice(people: List<Person>) {
    people.forEach label@{
        if (it.name == "Alice") return@label
    }
    println("Alice might be somewhere") //이번에는 요것이 출력됨!
}

//main
lookFocAlice(people) // Alice might be somewhere
  • 람다 안에서 로컬 return은 for 루프의 break와 비슷
  • 끝내고픈 레이블을 return 뒤에 명시하면 끝!
  • label@ - return@label로 return 할 위치를 지정

    라벨명칭은 마음대로 써도 되는듯🌝
  • @forEach로만 명시해도 forEach return 가능!

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

fun lookForAlice(people: List<Person>) {
    people.forEach(fun (person) { //람다 식 대신 무명 함수를 사용 
        if (person.name == "Alice") return 
        //여기서 return 은 가장 가까운 함수를 가리키는데 이 위치에서 가장 가까운 함수는 무명 함수임
        println("${person.name} is not Alice")
    })
}
  • 무명 함수는 함수명과 파라미터 타입이 존재하지 않는 함수
  • 람다식 대신 무명함수를 사용하면 로컬 return 처리가 가능하다.
  • 무명 함수는 fun을 사용해 정의되므로 그 함수 자신이 바로 가장 안쪽에 있는 fun으로 정의된 함수
profile
백엔드 개발자

0개의 댓글