Kotlin in Action 을 공부하고 Effective kotlin 의 내용을 조금 참조하여 정리한 글입니다.

filter 와 함께 람다 안에서 return 을 사용하는 등의 예제를 살펴보자.
람다 안의 return 문: 람다를 둘러싼 함수로부터 리턴(non-local return)
일반 루프 안에서 return
사용하기
data class Person(val name: String, val age: Int)
fun lookForAlice(people: List<Person>) {
for (person in people) {
if (person.name == "Alice") {
println("Found!")
return
}
}
println("Alice is not found")
}
val people = listOf(Person("Alice", 29), Person("Bob", 31))
lookForAlice(people) // print /* Found! */
forEach
에 전달된 람다에서 return
사용하기
fun lookForAlice2(people: List<Person>) {
people.forEach {
if (it.name == "Alice") {
println("Found!")
return
}
}
println("Alice is not found")
}
lookForAlice2(people) // print /* Found! */
람다 안에서 return
을 사용하면, 람다로부터만 리턴되는 것이 아니고, 람다를 호출하는 함수가 실행을 끝내고 리턴된다.
이렇게 자신을 둘러싼 블록보다 더 바깥에 있는 다른 블록을 리턴하게 만드는 return 문을 non-local(넌로컬) return 이라고 부른다.
Java 메서드 내의 for
루프나 synchronized
블록에서 return
키워드를 사용하는 경우에도 루프나 블록에서 리턴되는 것이 아닌, 함수 전체에서 리턴된다.
코틀린에서는 이러한 동작을 함수에서 람다를 인자로 받는 경우에도 똑같이 동작한다.
하지만 람다를 받는 함수가 inline 함수 일 때만 non-local return 이 가능하다.
@kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
예를 들어 forEach
함수의 바디가 인라인 함수로 처리되면 람다의 바디도 함께 인라인되므로, 람다 내의 return
표현식이 non-local return 이 가능하다.
하지만 non-inline 함수에서는 람다에 대한 리턴이 되지 않는다.
non-inline 함수는 람다를 변수에 저장해 둔 후에 람다가 호출될 수도 있다.
그런 경우 함수가 이미 리턴된 후에 람다가 실행되기 때문에 외부 함수의 리턴에 영향을 미칠 수 없다.
// 인라인 함수 예제
inline fun inlineExample(action: () -> Unit) {
println("Before Lambda")
action()
println("After Lambda")
}
// 비인라인 함수 예제
fun nonInlineExample(action: () -> Unit) {
println("Before Lambda")
action()
println("After Lambda")
}
@Test
fun inlineFunctionLambdaReturn() {
// 인라인 함수 저장 -> 호출
val delayedLambda = inlineExample {
println("Inside Lambda")
return // / 인라인 함수 내에서는 람다의 return이 외부 함수에 영향
}
println("after save lambda")
return delayedLambda
}
// print
/*
Before Lambda
Inside Lambda
*/
@Test
fun non_inlineFunctionLambdaReturn() {
// 비인라인 함수 저장
val delayedLambda = nonInlineExample {
println("Inside Lambda")
// return // 주석 해제 시 [COMPILE ERROR] 'return' is not allowed here
}
println("after save lambda")
return delayedLambda
}
// print
/*
Before Lambda
Inside Lambda
After Lambda
after save lambda
*/
왜 non-inline 함수에서는 람다 내부에서 리턴을 사용할 수 없을까?
함수가 컴파일될 때, 람다 함수는 객체로 래핑되어서 생기기 때문이다.
람다 함수는 내부적으로 FunctionN
와 같은 클래스로 래핑되어서 처리된다.
이 래핑된 객체 안에서의 return
은 해당 객체만을 리턴하기 때문에 외부 함수로의 non-local return 이 불가능하다.
하지만 인라인 함수에서는 람다 함수가 직접 호출부로 복사되므로 non-local return 이 가능하다.
람다로부터 리턴: 레이블을 사용한 return(local-return)
람다 안에서 람다만을 리턴하는 것은 만들 수 없을까? 이것도 가능하다.
람다 식에서도 local return 을 사용할 수 있다.
람다 안에서 local return 은 for
루프의 break
와 비슷한 역할을 한다.
local return 은 람다의 실행을 끝내고 람다를 호출했던 코드의 실행을 계속 이어나간다.
local return 과 non-local return 을 구분하기 위해 label
을 사용해야 한다.
return
으로 실행을 끝내고 싶은 람다 식 앞에 레이블을 붙이고 return
키워드 뒤에 그 레이블을 추가하면 된다.
fun lookForAlice3(people: List<Person>) {
people.forEach label@{ // 람다 식 앞에 레이블을 붙인다.
if (it.name == "Alice") return@label // 앞에서 정의한 레이블을 참조
}
println("Alice might be somewhere") // 항상 이 줄이 출력됨
}
private val people = listOf(Person("Alice", 29), Person("Bob", 31))
lookForAlice3(people)
// print /* Alice might be somewhere */

람다를 인자로 받는 인라인 함수의 이름을 return
뒤에 레이블로 사용해도 된다.
fun lookForAlice4(people: List<Person>) {
people.forEach {
if (it.name == "Alice") return@forEach // return@forEach 는 람다식으로부터 리턴시킴
}
println("Alice might be somewhere")
}
lookForAlice4(people)
// print /* Alice might be somewhere */
람다 식에 레이블을 명시하면 람다 함수 이름을 레이블로 사용할 수는 없다.
람다 식에는 레이블이 2개 이상 붙을 수 없다.
레이블이 붙인 this 식
this
식의 레이블에도 같은 규칙이 적용된다.
수신 객체 지정 람다(appy, also 등..) 글에서 이에 대해 잠깐 언급했다.
수신 객체 지정 람다의 바디에서는 this
참조를 사용해서 묵시적 컨텍스트 객체(람다를 만들 때 지정한 수신 객체)를 가리킬 수 있다.
assert(
StringBuilder().apply sb@{ // this@sb 를 통해 이 람다의 묵시적 수신 객체에 접근 가능
listOf(1, 2, 3).apply { // this 는 이 위치를 둘러싼 가장 안쪽 영역의 묵시적 수신 객체를 가리킨다.
this@sb.append(this.toString()) // 모든 묵시적 수신 객체를 사용할 수 있다. 다만 바깥쪽 묵시적 수신 객체에 접근할 때는 레이블을 명시해야 한다.
}
}
.toString() == "[1, 2, 3]")
레이블 붙은 return
과 마찬가지로 이 경우에도 람다 앞에 명시한 레이블을 사용하거나, 람다를 인자로 받는 함수 이름을 사용할 수 있다.
corssinline & noinline
함수를 인라인으로 만들고 싶지만, 어떤 이유로 일부 함수 타입 파라미터는 inline
으로 받고 싶지 않은 경우가 있을 수 있다.
이러한 경우에는 아래와 같은 한정자를 사용한다.
crossinline
- 아규먼트로
inline
함수를 받지만, non-local return 을 하는 함수는 받을 수 없게 만든다.inline
으로 만들지 않은 다른 람다 표현식과 조합해서 사용할 때 문제가 발생하는 경우 활용한다.
- 아규먼트로
noinline
- 아규먼트로
inline
함수를 받을 수 없게 만든다.inline
함수가 아닌 함수를 아규먼트로 사용하고 싶을 때 활용한다.
- 아규먼트로
inline fun requestNewToken(
hasToken: Boolean,
crossinline onRefresh: () -> Unit,
noinline onGenerate: () -> Unit
) {
if (hasToken) {
httpCall("get-token", onGenerate)
// 인라인이 아닌 함수를 아규먼트로 함수에 전달하려면 noinline 을 사용
} else {
httpCall("refresh-token"){
onRefresh()
// Non-local 리턴이 허용되지 않는 컨텍스트에서
// inline 함수를 사용하고 싶다면 crossinline 을 사용.
onGenerate()
}
}
}
fun httpCall(url: String, callback: () -> Unit) {
/* ... */
}
익명 함수: 기본적으로 local return
위에서 non-local 과 local return 을 알아봤다.
non-local return 문은 장황하고, 람다가 여러 return 문을 가지면, 사용하기 불편해진다.
코틀린의 익명 함수는 코드 블록을 여기저기 전달하기 위한 다른 해법을 제공하며, 그 해법을 사용하면 non-local return 문을 여럿 사용해야 하는 코드 블록을 쉽게 작성할 수 있다.
익명 함수 안에서 return
사용하기
fun lookForAlice5(people: List<Person>) {
people.forEach(fun(person) { // 람다 식 대신 익명 함수를 사용한다
if (person.name == "Alice") return // return 은 가장 가까운 함수를 가리킨다 이 위치에서 가장 가까운 함수는 익명 함수이다.
println("${person.name} is not Alice")
})
}
private val people = listOf(Person("Alice", 29), Person("Bob", 31), Person("Andrew", 33))
lookForAlice5(people)
// print
/*
Bob is not Alice
Andrew is not Alice
*/
익명 함수는 일반 함수와 비슷하다.
차이는 함수 이름이나 파라미터 타입을 생략할 수 있다는 점 뿐이다.
filter
에 익명 함수 넘기기
people.filter(fun(person): Boolean {
return person.age < 30
})
people.filter(fun(person) = person.age < 30)
익명 함수도 일반 함수와 같은 리턴 타입 지정 규칙을 따른다.
블록이 바디인 익명 함수는 리턴 타입을 명시해야 하고, 식이 바디인(expression-body) 익명 함수의 리턴 타입은 생략할 수 있다.
return
은 기본적으로 컴파일 코드에서fun
키워드를 사용해 정의된 가장 안쪽 함수를 리턴시킨다.
람다는 fun
을 사용해 정의되지 않으므로, 람다 바디의 return
은 람다 밖의 함수를 리턴시킨다.
익명 함수는 fun
을 사용해 정의되므로 그 함수 자신이 바로 가장 안쪽에 있는 fun
으로 정의된 함수이다.

익명 함수는 일반 함수와 비슷해보이지만, 실제로는 람다 식에 대한 문법적 편의일 뿐이다.
람다 식의 구현 방법이나 람다 식을 인라인 함수에 넘길 때 어떻게 바디가 인라이닝되는지 등의 규칙은 익명 함수에도 모두 적용된다.
'Kotlin' 카테고리의 다른 글
[Kotlin] 런타임 시 제네릭의 동작: 타입 소거(Type erasure), reified 타입 파라미터 (0) | 2024.01.31 |
---|---|
[Kotlin] 제네릭 타입 파라미터(Generic Type Parameter) (0) | 2024.01.31 |
[Kotlin] 인라인(inline) 함수 (1) | 2024.01.26 |
[Kotlin] 고차함수 (2) | 2024.01.26 |
[Kotlin] 프로퍼티 접근자 로직 재활용: 위임 프로퍼티 (2) | 2024.01.25 |