728x90
반응형
✅ 고차 함수 안에서 흐름 제어
- 루프와 같은 명령형 코드를 람다로 바꾸기 시작했을 때 우리는 return 문제에 부딪칠 수 있다.
- 루프의 중간에 있는 return문의 의미를 이해하기는 쉽지만 그 루프를 filter와 같이 람다를 호출하는 함수로 바꾸고 인자로 전달하는 람다 안에서 return을 사용하면 어떻게 될까?
- 몇 가지 예제를 살펴보도록 하자.
📌 람다 안의 return문 : 람다를 둘러싼 함수로부터 반환
- 먼저 컬렉션에 대한 이터레이션을 두 가지 살펴보자.
- 다음 코드의 실행 결과는 이름이 Alice인 경우 lookForAlice 함수로부터 반환된다는 사실을 알 수 있다.
/* 일반 루프 안에서 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") // "peopl" 안에 엘리스가 없으면 출력
}
lookForAlice(people)
// Found!
- 그렇다면 위 코드를 forEach 람다로 바꿔 사용해도 될까?
- 그리고, forEach에 넘긴 람다 안에 있는 return도 앞 예제와 같은 의미일까?
- 그렇다고 할 수 있다. forEach 함수를 대신 써도 안전하다. 아래 예제를 통해 확인해 보자.
/* forEach에 전달된 람다에서 return 사용하기 */
fun lookForAlice(people: List<Person>) {
people.forEach {
if(it.name == "Alice") {
println("Found!")
return // lookForAlice 함수에서 반환됨
}
}
println("Alice is not found")
}
lookForAlice(people)
// Found!
- 람다 안에서 return을 사용하면 람다로부터 반환되는 게 아닌 그 람다를 호출하는 함수가 실행을 끝내고 반환된다.
- 이처럼 자신을 둘러싸고 있는 블록보다 더 바깥에 있는 다른 블록을 반환하게 만드는 return 문을 넌로컬(non-local) return이라 부른다.
- 이렇게 return이 바깥쪽 함수를 반환시킬 수 있는 때는 람다를 인자로 받는 함수가 인라인 함수인 경우뿐이다.
- 위 예제에서 forEach는 인라인 함수이기 때문에 람다 본문과 함께 인라이닝된다.
- 따라서 return 식이 바깥쪽 함수를 반환시키도록 쉽게 컴파일할 수 있다.
- 하지만 인라이닝되지 않는 함수에 전달되는 람다 안에서 return을 사용할 수는 없다.
- 인라이닝되지 않는 함수는 람다를 변수에 저장할 수 있고, 바깥쪽 함수로부터 반환된 뒤에 저장해 둔 람다가 호출될 수도 있다.
- 그런 경우 람다 안의 return이 실행되는 시점이 바깥쪽 함수를 반환시키기엔 너무 늦은 시점일 수 있다.
📌 람다로부터 반환 : 레이블을 사용한 return
- 하지만 람다 식에서도 로컬 return을 사용할 수 있다.
- 람다 안에서 로컬 return은 for 루프의 break와 비슷한 역할을 한다.
- 로컬 return은 람다의 실행을 끝내고 람다를 호출했던 코드의 실행을 계속 이어간다.
- 이를 위해선 로컬 return과 넌로컬 return을 구분하기 위해 레이블(label)을 사용해야 한다.
- return으로 실행을 끝내고 싶은 람다 식 앞에 레이블을 붙이고, return 키워드 뒤에 그 레이블을 추가하면 된다.
/* 레이블을 통해 로컬 리턴 사용하기 */
fun lookForAlice(people: List<Person>) {
people.forEach label@ { // 람다 식 앞에 레이블을 붙인다.
if (it.name == "Alice") return@label // return@label은 앞에서 정의한 레이블을 참조한다.
}
println("Alice might be somewhere") // 항상 출력된다.
}
lookForalice(people)
// Alice might be somewhere
- 람다 식에 레이블을 붙이려면 레이블 이름 뒤에 @ 문자를 추가한 것을 람다를 여는 { 앞에 넣으면 된다.
- 람다로부터 반환하려면 return 키워드 뒤에 @ 문자와 레이블을 차례로 추가하면 된다.
- 또한, 람다에 레이블을 붙여서 사용하는 대신 람다를 인자로 받는 인라인 함수의 이름을 return 뒤에 레이블로 사용해도 된다.
/* 함수 이름을 return 레이블로 사용하기 */
fun lookForAlice(people: List<Person>) {
people.forEach {
if (it.name == "Alice") return@forEach // return@foreach는 람다 식으로부터 반환시킨다.
}
println("Alice might be somewhere")
}
- 람다 식의 레이블을 명시한 경우 함수 이름을 레이블로 사용할 수 없음을 유의해야 한다
- 또한, 람다 식에는 레이블이 2개 이상 붙을 수 없음을 명심하자.
❗ 레이블이 붙은 this 식
- this 식의 레이블에도 마찬가지 규칙이 적용된다.
- 수신 객체 지정 람다 앞에 레이블을 붙인 경우 this 뒤에 그 레이블을 붙여 묵시적인 컨텍스트 객체를 지정할 수 있다.
println(StringBuilder().apply sb@ { // this@sb를 통해 이 람다의 묵시적 수신 객체에 접근할 수 있다.
listOf(1, 2, 3).apply { // "this"는 이 위치를 둘러싼 가장 안쪽 영역의 묵시적 수신 객체를 가리킨다.
this@sb.append(this.toString()) // 모든 묵시적 수신 객체를 사용할 수 있다. 다만 바깥족 무시적 수신 객체에
} // 접근할 때는 레이블을 명시해야 한다.
})
// [1, 2, 3]
- 레이블 붙은 return과 마찬가지로 이 경우에도 람다 앞에 명시한 레이블을 사용하거나 람다를 인자로 받는 함수 이름을 사용할 수 있다.
- 하지만 넌로컬 반환문은 장황하고, 람다 안의 여러 위치에 return 식이 들어가야 하는 경우 사용하기 불편하다.
- 그래서 코틀린은 코드 블록을 여기저기 전달하기 위한 다른 해법을 제공하고 있으며, 그 해법을 사용하면 넌로컬 반환문을 여럿 사용해야 하는 코드 블록을 쉽게 작성할 수 있다.
- 그 해법은 바로 무명 함수이다.
📌 무명 함수 : 기본적으로 로컬 return
- 무명 함수는 코드 블록을 함수에 넘길 때 사용할 수 있는 다른 방법이다.
- 먼저 예제를 살펴보자.
/* 무명 함수 안에서 return 사용하기 */
fun lookForAlice(people: List<Person>) {
people.forEach(fun (person) { // 람다 식 대신 무명 함수를 사용한다.
if(person.name == "Alice") return // "return"은 가장 가까운 함수를 가리키는데 이 위치에서 가장 가까운 함수는 무명 함수이다.
println("${person.name} is not Alice")
})
}
lookForAlice(people)
// Bob is not Alice
- 무명 함수는 일반 함수와 비슷해 보이지만 함수 이름이나 파라미터 타입을 생략할 수 있다는 점에서 다르다.
- 다른 예제를 살펴보자.
/* fileter에 무명 함수 넘기기 */
people.filter(fun (person): Boolean {
return person.age < 30
})
- 무명 함수도 일반 함수와 같은 반환 타입 지정 규칙을 따른다.
- 위의 예제처럼 블록이 본문인 무명 함수는 반환 타입을 명시해야 하지만, 식을 본문으로 하는 무명 함수의 반환 타입은 생략할 수 있다.
/* 식이 본문인 무명 함수 사용하기 */
people.filter(fun (person) = person.age < 30)
- 이처럼 무명 함수 안에서 레이블이 붙지 않은 return 식은 무명 함수 자체를 반환시킬 뿐 무명 함수를 둘러싼 다른 함수를 반환시키지 않는다.
- 위에서 살펴본 것과 같이 람다 식은 fun을 사용해 정의되지 않기 때문에 람다 본문의 return은 람다 밖의 함수를 반환시킨다.
- 하지만 무명 함수는 fun을 사용해 정의되기 때문에 그 함수 자신이 바로 가장 안쪽에 있는 fun으로 정의된 함수이다.
- 따라서 무명 함수 본문의 return은 그 무명 함수를 반환시키고, 무명 함수 밖의 다른 함수를 반환시키지 못한다.
- 무명 함수는 일반 함수와 비슷해 보이지만 실제로는 람다 식에 대한 문법적 편의일 뿐이다.
- 람다 식의 구현 방법이나 람다 식을 인라인 함수에 넘길 때 어떻게 본문이 인라이닝 되는지 등의 규칙을 무명 함수에도 모두 적용할 수 있다.
728x90
반응형