오늘도 더 나은 코드를 작성하였습니까?

고차 함수 안에서 흐름 제어 본문

Kotlin in Action/코틀린 답게 사용하기

고차 함수 안에서 흐름 제어

hik14 2021. 2. 26. 11:56

루프와 같은 명령형 코드를 람다로 변경한다면 return 문제에 부딪칠 것 이다.

 

루프의 중간에 있는 return문의 의미는 이해가 쉽다. 하지만,

루프를 filter와 같이 람다를 호출하는 함수로 변경후 인자로 전달하는 람다 안에서 return을 사용하면 어떻게 될까?

람다 안의 return 문

fun lookForAlice(people: List<Person>){
    
    for (person in people){
        if(person.name == "Alice"){
            println("Found!")
            return
        }
    }
    println("Alice is not founded")
}
fun lookForAlice(people: List<Person>){
    people.forEach{
        if(it.name == "Alice"){
            println("Found!")
            return //@ nonlocal 리턴 lookForAlice 함수가 종료됨.
        }
    }
    println("Alice is not founded")
}

람다 내부에서 return을 사용하면 람다로 부터만 반환 되는것이 아니라 그 람다를 호출한 함수가 실행을 끝나고 반환된다.

자바 메소드 안에 for 루프나 synchronized 블록을 끝내지 않고 메소드를 반환한다. 

코틀린도 람다를 받는 함수로 for 나 synchronized 와 같은 기능을 구현할 수 있다.

 

이렇게 return이 바깥쪽 함수를 반환시킬 수 있는 때는 람다를 인자로 받는 함수가 인라인 함수인 경우 뿐이다.

foreach가 인라인이 때문에 인자로 받는 함수역시 인라인이 되어 컴파일시 복사해 붙혀들어온다 따라서 lookForAlice를 반환시키도록 쉽게 컴파일이 가능하다. 반대로 인라인 함수에 전달된 람다안에서의 return을 사용할 수는 없다.

 

인라이닝 되지 않은 함수는 람다를 변수에 저장할수 있고 바깥쪽 함수로부터 반환된뒤에 저장된 람다가 호출될 수도 있다. 그런 경우 람다의 return이 실행되는 시점이 바깥쪽함수를 반환시키기엔 너무 늦는다.

 

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

 

람다 식에서도 로컬 return을 사용할 수 있다. 람다 안에서 로컬 return은 for루프의 break과 비슷한 역활을 한다.

즉, 람다의 로컬return은 람다의 실행을 끝내고 람다를 호출했던 코드의 실행을 계속 이어간다.

 

로컬과 넌로컬의 return을 구별하기 위해 레이블(label)을 사용해야 한다. 

label@{  람다 .... return @label  }

 

fun lookForAlice(people: List<Person>){
    people.forEach label@{
        if(it.name == "Alice"){
            println("Found!")
            return@label
        }
    }
    println("Alice is not founded")
}

함수이름을 리턴 레이블로 사용해도된다. 

fun lookForAlice(people: List<Person>){
    people.forEach {
        if(it.name == "Alice"){
            println("Found!")
            return@forEach
        }
    }
    println("Alice is not founded")
}

단, 2개를 혼용사용하면 안되고 람다식에는 레이블을 2개 이상 붙힐수가 없다.

 

*레이블이 붙은 this 식

 

this - 수신객체 지정 람의 본문에서 this참조를 묵시적인 컨텍스트 객체 (람다를 생성시 지정한 수신객체)를 가르킬수 있다.

수신객체 지정 람다 앞에 레이블을 붙인 경우 this 뒤에 그 레이블을 붙여서 묵시적인 컨텍스트 객체를 지정할 수 있다.

 

    println(StringBuilder().apply sb@{
            listOf(1, 2, 3).apply {
                this@sb.append(this.toString())
            }
        }
    )

단, 넌로컬을 사용시 코드가 너무 길거나 여러 위치에서 return을 사용하면 불편하다.

코틀린은 코드 블록을 여기저기 전달 하기 위해 무명함수를 제공한다.

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

fun lookForAlice(people: List<Person>) {
    people.forEach(fun(person) {
        if (person.name == "Alice") return
        println("${person.name} is not Alice")
    })
}

위 코드에서 return 가장 가까운 함수를 가르키는데  이 코드에서 가장 가까운것은 무명함수이다.

무명함수는 일반함수와 비슷해 보이지만 함수이름, 파라미터 타입을 생략할 수 있다.

 

filter에 무명함수 넘기기

무명함수도 일반함수와 같은 반환 타입을 지정 규칙을 지켜야 한다. 식을 본문으로 한다면 생략 가능하다.

 people.filter(fun(person): Boolean {
            return person.age < 30
        })

식이 본문인 무명함수

people.filter(fun(person) = person.age < 30)

 

무명함수 안에서의 레이블(label)이 없다면 무명함수 자체를 반환 시킬뿐 무명함수를 둘러싼 어느 함수라도 반환시키지 않는다.