KotlinInAction/고차 함수: 파라미터와 반환 값으로 람다 사용

고차 함수 안에서 흐름 제어

webmaster 2022. 8. 11. 19:46
728x90

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

data class Person(val name:String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31))
fun lookForAlice(people: List<Person>){
    for(person in people){
        if(person.name == "Alice"){
            println("Found!!")
            return
        }
    }
    println("Alice is not found") //people 안에 엘리스가 없으면 이 줄 출력
}
  • 이 코드를 forEach로 바꿔 써도 될까? forEach에 넘긴 람다 안에 있는 return 도 위와 같은 의미이다
fun lookForAlice(people: List<Person>) {
    people.forEach {
        if (it.name == "Alice") {
            println("Found!!")
            return
        }
    }
    println("Alice is not found") //people 안에 엘리스가 없으면 이 줄 출력
}
  • 람다 안에서 return을 사용하면 람다로부터만 반환되는 것이 아니라 그 람다를 호출하는 함수가 실행을 끝내고 반환한다.
  • 자신을 둘러싸고 있는 블록보다 더 바깥쪽에 있는 다른 블록을 반환하게 만드는 return문을 넌로컬 return이라 부른다
  • 코틀린에서는 언어가 제공하는 기본 구성 요소가 아니라 람다를 받는 함수로 for나 synchronized와 같은 기능을 구현한다
    • 그런 함수 안에서 쓰이는 return이 자바의 return과 같은 의미를 같게 허용한다.
  • 이렇게 return 이 바깥쪽 함수를 반환시킬수 있는 때는 람다를 인자로 받는 함수가 인라인 함수인 경우뿐이다
    • forEach는 인라인 함수로 람다 본문과 함께 인라인 되고, return식이 바깥쪽 함수를 반환시키도록 쉽게 컴파일 가능하다
    • 인라이닝이 되지 않는 함수는 람다를 변수에 저장할 수도, 바깥쪽 함수로부터 반환된 뒤에 저장해둔 람다를 실행시킬 수 있기 때문에 return이 실행되는 시점이 바깥쪽 함수를 반환시키기에는 너무 늦은 시점이기 때문에 지원하지 않는다

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

람다 식에서도 로컬 return을 사용할 수 있다(for 루프의 break와 비슷한 역할을 한다). 로컬 return은 람다의 실행을 끝내고 람다를 호출했던 코드의 실행을 계속 이어나간다. 로컬 return과 넌로컬 return을 구분하기 위해 레이블을 사용해야 하며, return으로 실행을 끝내고 싶은 람다 식 앞에 레이블을 붙이고, return 키워드 뒤에 그 레이브를 추가하면 된다.

fun lookForAlice(people: List<Person>) {
    people.forEach label@{ //람다 식 앞에 레이블을 붙힌다
        if (it.name == "Alice") {
            return@label //앞에서 정의한 레이블을 참조한다.
        }
    }
    println("Alice is not found") //이 줄은 항상 출력
}
fun main(args: Array<String>) {
    val people = listOf(Person("Alice", 29), Person("Bob", 31))
    lookForAlice(people)
}
  • 람다식에 레이블을 붙이려면 레이블 이름 뒤에 @ 문자를 추가한 것을 람다를 여는 { 앞에 넣으면 된다.
  • 람다로부터 반환하려면 return 키워드 뒤에 @문자와 레이블을 차례로 추가하면 된다.
fun lookForAlice(people: List<Person>) {
    people.forEach { 
        if (it.name == "Alice") {
            return@forEach //return@forEach는 람다식으로부터 반환
        }
    }
    println("Alice is not found") 
}
  • 람다에 레이블을 붙여서 사용하는 대신 람다를 인자로 받는 인라인 함수의 이름을 return 뒤에 레이블로 사용해도 된다.
  • 람다식의 레이블을 명시하면 함수 이름을 레이블로 사용할 수 없다(람다 식에는 레이블이 2개 이상 붙을 수 없다)

TIP : 레이블이 붙은 this식

this 식의 레이블에도 같은 규칙이 적용되는데 수신 객체 지정 람다 앞에 레이블을 붙인 경우 this 뒤에 그 레이블을 붙여서 묵시적인 컨텍스트 객체를 지정할 수 있다.

fun main(args: Array<String>) {
    println(StringBuilder().apply sb@{
        listOf(1, 2, 3).apply { 
            this@sb.append(this.toString())
        }
    })
}

기본적으로 로컬 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>) {
    val people = listOf(Person("Alice", 29), Person("Bob", 31))
    people.filter(fun (person): Boolean{ //무명함수 넘기기
        return person.age < 30 
    })
    people.filter(fun (person) = person.age < 30)//식이 본문인 무명함수
}
  • 무명 함수도 일반 함수와 같은 반환 타입 지정 규칙을 따른다.
  • 블록이 본문이 무명 함수는 반환타입을 명시해야 하지만, 식을 본문으로 하는 무명함수의 반환 타입은 생략할 수 있다
  • 무명 함수 안에서 레이블이 붙지 않은 return 식은 무명함수 자체를 반환시킬 뿐 무명 함수를 둘러싼 다른 함수를 반환시키지 않는다.
    • return에 적용된 규칙은 fun키워드를 사용해 정의된 가장 안쪽 함수를 반환
    • 람다식은 fun을 사용해 정의되지 않으므로 람다 본문의 return 은 람다 밖의 함수를 반환시킨다
  • 무명함수는 일반 함수와 비슷해 보이지만, 실제로는 람다 식에 대한 문법적 편의일 뿐이다.
  • 람다식의 구현 방법이나 람다식을 인라인 함수에 넘길 때 어떻게 본문이 인라이닝 되는지 등의 규칙을 무명 함수에도 모두 적용 가능하다
728x90