Search

Lambdas

고차 함수와 람다

Kotlin 함수는 1급 객체여서 변수에 저장될 수 있고 데이터 구조에 포함될 수 있으며 다른 함수에 인수로 전달되거나 반환 값으로 사용될 수 있다.
이걸 가능하게 하기 위해 Kotlin은 함수 타입이라는 타입 시스템을 제공한다.
함수 타입은 함수를 표현하기 위한 타입 시스템이며 람다식과 같은 다양한 언어적 구문을 통해 함수를 더 쉽게 다룰 수 있도록 지원한다.

고차 함수

고차 함수는 함수를 매개변수로 받거나 함수를 반환하는 함수이다.
fun <T, R> Collection<T>.fold( initial: R, combine: (acc: R, nextElement: T) -> R ): R { var accumulator: R = initial for (element: T in this) { accumulator = combine(accumulator, element) } return accumulator }
Kotlin
복사
초기 값(initial)과 결합 함수(combine)를 받아서 초기 값을 누적(accumulator)하고 컬렉션의 각 요소와 결합하여 최종 값을 만들어낸다.
combine 매개변수는 (R, T) → R 이라는 함수 타입을 가지고 있고 두 개의 매개변수 R, T를 받아서 R 타입의 값을 반환하는 함수를 나타낸다.
val items = listOf(1, 2, 3, 4, 5) // 람다는 중괄호로 감싸진 코드 블록입니다. items.fold(0, { // 람다의 매개변수는 화살표 '->' 앞에 위치합니다. acc: Int, i: Int -> print("acc = $acc, i = $i, ") val result = acc + i println("result = $result") // 람다의 마지막 표현식이 반환 값으로 간주됩니다. result })
Kotlin
복사

람다식에서 매개변수 타입 생략

람다식에서 매개변수 타입은 컴파일러가 추론할 수 있는 경우 생략할 수 있다.
val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })
Kotlin
복사
acc와 i의 타입이 추론 가능하기 때문에 따로 명시하지 않는다.

함수 참조

함수 참조도 고차 함수 호출에 사용할 수 있다.
val product = items.fold(1, Int::times)
Kotlin
복사
Int::times는 곱셈을 수행하는 Int 클래스의 times 함수를 combine 인자로 전달한다.

함수 타입

함수 타입은 함수의 시그니처(매개변수와 반환값)를 나타낸다.
모든 함수 타입은 괄호로 묶인 매개변수 타입 목록과 반환 타입을 가진다.
매개변수 타입 목록이 비어있을 수 있으며 () → A 처럼 표시한다.
반환 타입이 Unit 일 때는 생략할 수 없다.
함수 타입은 수신자 타입을 가질 수 있으며 A.(B) → C 처럼 . 앞에 위치하고 수신자 객체 A에서 호출되고 매개변수 B를 받아 C를 반환하는 함수를 나타낸다.
suspend 함수는 suspend () → Unit 이나 suspend A.(B) → C 와 같은 형태로 특별한 함수 타입에 속한다.
함수 타입을 표기할 때 매개변수에 이름을 지정할 수 있다. (x: Int, y: Int) → Point
함수 타입이 nullable한지 지정하려면 괄호를 사용하여 나타낸다. ((Int, Int) → Int)?
함수 타입을 괄호로 묶어 결합할 수 있다. (Int) → ((Int) → Unit)
화살표 표기법은 오른쪽 결합성이기 때문에 (Int) → (Int) → Unit((Int) → (Int)) → Unit 은 다르다
typealias를 사용하여 함수 타입에 별칭을 지정할 수 있다.
typealias ClickHandler = (Button, ClickEvent) -> Unit
Kotlin
복사

함수 타입 인스턴스 생성

함수 리터럴
람다 표현식
val sum: (Int, Int) -> Int = { a, b -> a + b }
Kotlin
복사
마지막 표현식의 결과가 반환된다.
익명 함수
val multiply: (Int, Int) -> Int = fun(a: Int, b: Int): Int { return a * b }
Kotlin
복사
명시적으로 return 키워드를 사용하여 값을 반환해야 한다.
호출 가능 참조
함수 참조
fun add(a: Int, b: Int): Int = a + b val sum: (Int, Int) -> Int = ::add
Kotlin
복사
add 함수를 참조하여 ::add라는 함수 타입 인스턴스를 생성한다.
속성 참조
val lengthGetter: (String) -> Int = String::length
Kotlin
복사
문자열의 length 속성을 참조하여 String::length는 String을 받아 Int를 반환하는 함수 타입 인스턴스를 생성한다.
생성자 참조
val regexConstructor: (String) -> Regex = ::Regex
Kotlin
복사
String을 받아 Regex 객체를 생성하는 ::Regex라는 참조를 통해 함수 타입 인스턴스를 생성한다.
바운드 참조
val foo = "Hello" val fooToString: () -> String = foo::toString println(fooToString()) // "Hello" 출력
Kotlin
복사
foo 객체에 바운드된 toString()의 호출을 참조하는 foo::toString 라는 함수 타입 인스턴스를 생성한다.
함수 타입을 구현한 클래스의 인스턴스
클래스의 인스턴스를 함수 타입을 인터페이스처럼 사용하는 예
class IntTransformer: (Int) -> Int { override operator fun invoke(x: Int): Int = TODO() } val intFunction: (Int) -> Int = IntTransformer()
Kotlin
복사
IntTransformer 클래스는 (Int) → Int 함수 타입을 구현하며 invoke 연산자를 재정의하여 IntTransformer 객체를 함수처럼 사용할 수 있게 만든다.
수신자가 있는 함수 타입과 수신자가 없는 함수 타입의 상호 변환
수신자는 첫 번째 매개변수로 대체될 수 있고 그 반대도 가능하다.
(A, B) → C 타입의 값은 A.(B) → C 타입이 필요한 곳에 전달되거나 할당할 수 있고 그 반대도 가능하다.
val repeatFun: String.(Int) -> String = { times -> this.repeat(times) } val twoParameters: (String, Int) -> String = repeatFun // 가능
Kotlin
복사
repeatFun은 수신자가 있는 함수 타입 String.(Int) → String이고 이를 수신자가 없는 함수 타입 (String, Int) → String에 할당할 수 있다.
fun runTransformation(f: (String, Int) -> String): String { return f("hello", 3) } val result = runTransformation(repeatFun) // 가능
Kotlin
복사
runTransformation 함수는 (String, Int) → String 타입의 함수 f를 인자로 받아 사용한다.
repeatFun은 수신자 타입이 있는 String.(Int) → String 타입이지만 (String, Int) → String 타입이 요구되는 곳에서도 사용할 수 있다.
함수 타입 추론
val a = { i: Int -> i + 1 } // The inferred type is (Int) -> Int
Kotlin
복사
충분한 정보가 있으면 컴파일러는 변수의 함수 타입을 추론할 수 있다.
수신자가 없는 함수 타입은 기본적으로 추론되며 변수를 확장 함수 참조로 초기화해도 동일하게 처리된다.
추론 동작을 변경하려면 변수 타입을 명시적으로 지정해야 한다.

함수 타입 인스턴스 호출

함수 타입의 값은 f.invoke(x) 또는 단순히 f(x)의 invoke(…) 연산자를 사용하여 호출할 수 있다.
값에 수신자 타입이 있는 경우 수신자 객체를 첫 번째 인수로 전달해야 한다.
수신자 타입이 있는 함수 타입 값을 호출하는 또 다른 방법은 해당 값을 수신자 객체 앞에 두는 것이다.
val stringPlus: (String, String) -> String = String::plus val intPlus: Int.(Int) -> Int = Int::plus println(stringPlus.invoke("<-", "->")) println(stringPlus("Hello, ", "world!")) println(intPlus.invoke(1, 1)) println(intPlus(1, 2)) println(2.intPlus(3)) // 확장 함수처럼 호출
Kotlin
복사

인라인 함수

고차 함수에서 유연한 제어 흐름을 제공하는 인라인 함수를 사용하는 것이 유리할 때가 있다.

람다 표현식과 익명 함수

람다 표현식과 익명 함수는 함수 리터럴이다.
함수 리터럴은 선언되지 않고 즉시 표현식으로 전달되는 함수이다.
max(strings, { a, b -> a.length < b.length })
Kotlin
복사
max() 함수는 고차 함수로 두 번째 인수로 함수 값을 받는다.
이 두 번째 인수는 함수 자체인 표현식이며 이는 함수 리터럴로 불린다.
fun compare(a: String, b: String): Boolean = a.length < b.length
Kotlin
복사
max() 함수는 compare() 함수처럼 명명된 함수와 동일하다.

람다 표현식 구문

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
Kotlin
복사
람다 표현식은 항상 중괄호로 둘러싸여 있다.
전체 문법 형식에서 매개변수 선언은 중괄호 안에 위치하며 타입 주석은 선택 사항이다.
람다의 본문은 화살표(→) 뒤에 나온다.
람다의 추론된 반환 타입이 Unit이 아닌 경우 람다 본문 내부의 마지막 표현식이 반환 값으로 처리된다.

후행 람다 전달

val product = items.fold(1) { acc, e -> acc * e }
Kotlin
복사
함수의 마지막 매개변수가 함수인 경우 해당 매개변수에 해당하는 람다 표현식을 괄호 밖에 둘 수 있다.
run { println("...") }
Kotlin
복사
만약 람다가 유일한 인수라면 괄호를 완전히 생략할 수 있다.

it : 단일 매개변수의 암시적 이름

ints.filter { it > 0 } // 이 리터럴은 '(it: Int) -> Boolean' 타입입니다
Kotlin
복사
람다 표현식에서 매개변수가 하나만 있는 경우 매개변수는 암시적으로 it 이라는 이름으로 선언된다.
컴파일러가 시그니처를 매개변수 없이 구문 분석할 수 있으면 매개변수를 선언할 필요가 없으며 화살표(→)도 생략할 수 있다.

람다 표현식에서 값 반환

람다에서 값을 명시적으로 반환하려면 한정된 return 구문을 사용해야 한다.
그렇지 않으면 마지막 표현식의 값이 암시적으로 반환된다.
ints.filter { val shouldFilter = it > 0 shouldFilter } ints.filter { val shouldFilter = it > 0 return@filter shouldFilter }
Kotlin
복사
위 두 코드는 동일하다.
strings.filter { it.length == 5 }.sortedBy { it }.map { it.uppercase() }
Kotlin
복사
이러한 방식과 람다 표현식을 괄호 밖에 전달하는 방식 덕분에 LINQ 스타일의 코드 작성이 가능하다.

사용되지 않는 변수에 대한 밑줄

map.forEach { (_, value) -> println("$value!") }
Kotlin
복사
람다 매개변수가 사용되지 않을 경우 변수 이름 대신 밑줄을 사용할 수 있다.

람다에서 구조 분해 선언

람다에서의 구조 분해는 구조 분해 선언의 일부로 설명된다.

익명 함수

fun(x: Int, y: Int): Int = x + y
Kotlin
복사
익명 함수는 람다 표현식과 비슷하게 사용되지만 반환 타입을 명시적으로 지정할 수 있는 기능을 제공한다.
fun(x: Int, y: Int): Int { return x + y }
Kotlin
복사
본문은 표현식 또는 블록이 될 수 있고 블록인 경우에는 명시적인 return문을 사용해야 한다.
ints.filter(fun(item) = item > 0)
Kotlin
복사
익명 함수의 매개변수 타입과 반환 타입이 추론되는 경우 생략할 수 있다.
// 람다 표현식에서의 return fun outerFunction() { val lambda = { return } // 바깥 함수에서 반환됨 lambda() println("이 코드는 실행되지 않습니다.") // 실행되지 않음 } // 익명 함수에서의 return fun outerFunction() { val anonymousFunc = fun() { return } // 익명 함수 자체에서 반환됨 anonymousFunc() println("이 코드는 실행됩니다.") // 실행됨 }
Kotlin
복사
람다 표현식은 비지역 반환(non-local return)을 지원하지만 익명 함수는 로컬 반환(local return)만 지원한다.

클로저

var sum = 0 ints.filter { it > 0 }.forEach { sum += it } print(sum)
Kotlin
복사
람다 표현식이나 익명 함수는 외부 범위에 선언된 변수를 포함하는 클로저를 참조할 수 있다.
클로저에서 캡처된 변수는 람다에서 수정할 수 있다.

수신자가 있는 함수 리터럴

val sum = fun Int.(other: Int): Int = this + other
Kotlin
복사
A.(B) → C 와 같은 수신자가 있는 함수 타입은 특별한 형태로 인스턴스화 할 수 있다.
함수 리터럴의 본문 내부에서 호출된 수신자는 암시적으로 this가 되어 추가적인 한정자 없이 해당 수신자 또는 수신자의 멤버에 접근할 수 있다.
class HTML { fun body() { ... } } fun html(init: HTML.() -> Unit): HTML { val html = HTML() // 수신 객체 생성 html.init() // 수신 객체를 람다에 전달 return html } html { // 수신 객체가 있는 람다가 여기서 시작됩니다 body() // 수신 객체의 메서드 호출 }
Kotlin
복사
수신자 타입을 컨텍스트에서 추론할 수 있는 경우 람다 표현식을 수신자와 함께 함수 리터럴로 사용할 수 있다.
타입 안전 빌더가 그 예이다.