https://kotlinlang.org/docs/lambdas.html
코틀린 함수는 일급 함수이기 때문에 함수 내부에 변수나 데이터 구조를 저장할 수 있고, 함수를 인자로 전달하거나 다른 고차 함수를 통해 반환 값이 될 수 있다. 람다 표현식을 통해 이를 편하게 사용 할 수 있다.
고차함수 (Higher-order functions)
고차 함수는 함수를 리턴하거나 함수를 파라미터로 받는 함수를 말한다.
좋은 예로 함수형 프로그래밍에서 주로 사용하는 fold 함수가 있다. 이 함수는 초기값과 결합 함수를 파라미터로 받아서 모든 원소에 대해 결합함수를 수행한다.
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
}
// combine으로 acc, i -> acc + " " + i 람다를 전달한다.
val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })
함수 타입 (Function Types)
코틀린은 (Int) -> String과 같은 함수 타입을 사용한다. 함수타입을 선언해서 사용할 때는 val onClick: (Int) -> String = ... 과 같이 작성한다.
- 모든 함수 타입은 괄호로 묶인 파라미터와 리턴타입을 갖는다 : (A, B) -> C 형태로 작성된 함수가 있다면, 이 함수는 A, B 두개의 파라미터를 받고 C 타입을 리턴한다는 의미이다. 혹시 파라미터가 없다면 빈 괄호를 작성한다. () ->C
- 함수 타입은 추가적으로 receiver 타입을 지정할 수 있다. A.(B) -> C 형태로 작성할수 있으며, 이 함수는 A 타입에 있는 B를 파라미터로 받고 C 타입을 리턴하는 함수를 의미한다. 이후 receiver가 있는 함수 리터럴(function literals with receiver) 부분에서 자세히 보자
- 중단 가능 함수 (suspending function): suspend () -> Unit, suspend A.(B) -> C
함수 타입은 파라미터 이름을 작성해도 되고 하지 않아도 된다.
nullable 함수 타입은 함수타입 을 괄호로 묶고 물음표를 붙어서 작성한다. ((Int, Int) -> Int)?
(Int) -> ((Int) -> Unit) 과 같이 함수타입을 중첩해서 작성 가능하고 이 작성법은 (Int) -> (Int) -> Unit 과 동일하다.
함수 타입은 타입 별칭(type alias) 로 작성 가능하다.
typealias ClickHandler = (Button, ClickEvent) -> Unit
Instantiating a function type
함수 타입 구현 방법들
- 함수 리터럴 내에 코드 블록 작성하는 방법
- 람다 표현식: { a, b -> a + b}
- 익명 함수: fun(s: String): Int { return s.toIntOrNull() ?: 0
- 호출 가능한 참조 (callable reference)
- 최상위(top-level), 지역, 멤버 또는 확장 함수: ::isOdd, String::toInt
- 최상위(top-level), 멤버 또는 확장 속성: List<Int>::size
- 생성자: ::Regex
- 함수 타입을 구현한 클래스
class IntTransformer: (Int) -> Int {
override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()
val a = { i: Int -> i + 1 } // 컴파일러가 (Int) -> Int 타입으로 유추함
수신자(receiver)가 있는 함수는 수신자를 파라미터로 갖는 함수와 호환 가능하다.
예. A.(B) -> C 타입의 함수는 (A, B) -> 타입과 같은 취급을 받는다.
val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
val twoParameters: (String, Int) -> String = repeatFun // OK
fun runTransformation(f: (String, Int) -> String): String {
return f("hello", 3)
}
val result = runTransformation(repeatFun) // OK
함수타입 인스턴스 실행하기 (Invoking a function type instance)
함수타입은 invoke(...) 연산자를 이용하거나: f.invoke(x), 그냥 함수처럼 호출하면: f(x) 된다.
수신자가 있는 함수 타입은 "수신자.f(x)" 형태로 호출하거나, f(수신자, x) 형태로 호출 할 수도 있다.
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)) // extension-like call
람다 표현식과 익명 함수
람다 표현식과 익명함수는 함수를 선언하지 않고 표현식으로 전달 할 수 있는 함수 리터럴 이다.
람다 문법
기본적인 람다 표현식은 아래와 같은 형식으로 작성한다.
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y}
val sum2 = { x: Int, y: Int -> x + y }
- 람다 표현식은 기본적으로 중괄호("{}")로 감싼다.
- 파라미터는 중과로 아에 작성하며 타입은 작성하지 않아도 된다.
- 함수 본문은 "->" 다음에 작성한다.
- 반환타입이 Unit이 아니라면 함수 본문의 가장 마지막 표현식이 리턴 값으로 간주 된다.
trailing 람다
관습적으로, 함수의 마지막 매개변수가 함수라면 람다 식을 괄호 밖에 작성 할 수도 있는데, 이를 trailing lambda라고 한다.
val product = items.fold(1) { acc, e -> acc * e }
어떤 함수가 1개의 함수 매개변수만 필요하다면 괄호 마저 생략 할 수 있다.
run { println("...") }
it: implicit name of a single parameter
람다 표현식이 단 하나의 파라미터만 받는다면, "->" 포함 왼쪽의 파라미터 선언 부분을 생략 할 수 있으며, 이때 생략된 파라미터는 it 이라는 이름으로 함수 본문에서 사용 할 수 있다.
ints.filter { it > 0 } // '(it: Int) -> Boolean' 함수를 왼쪽 처럼 작성할 수 있다.
Returngin a value from a lambda expression
람다식에 return 문을 명시적으로 작성 할 수도 있는데, 이 때는 qualified return 문(라벨을 지정해서 어느 코드 블럭의 리턴인지 명시)으로 작성해야 한다.
// 아래 두 코드는 동일하게 동작함.
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter // qualified return 문(return@filter)을 작성해서 어드 코드 블록에 대한 리턴인지 명시함.
}
Underscore for unused variables
람다 파라미터 중 사용하지 않는 파라미터가 있다면 underscore("_") 로 대체 할 수 있다.
map.forEach { (_, value) -> println("$value!") }
익명함수 (Anonymous function)
람다식에서는 return 문이 생략된다. 대부분의 경우 큰 문제는 없지만 특별히 return 문을 명시하고 싶은 경우 익명 함수를 사용한다.
// 함수 명을 따로 작성할 필요없다.
// 표현식의 반환 타입은 자동을 유추한다.
fun(x: Int, y: Int): Int = x + y
// 위 코드를 아래와 같이 작성 할 수도 있다.
// 코드 블록의 반환 타입은 명시 해 줘야 한다.
fun(x: Int, y: Int): Int {
return x + y
}
Closure
람다식이나 익명함수는 함수 범위 외부에 선언되고 함수내부에서 사용중인 변수인 클로저에 접근 할 수 있다.
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
수신자가 있는 함수 리터럴
A.(B) -> C 와 같이 수신자(A)가 있는 함수 타입은 수신자 타입의 오브젝트를 이용해 함수를 호출 할 수 있다. 수신자가 있는 함수 타입의 함수 본문에는 수신자 객체를 표현할때 this 를 사용하고, 이를 생략 할 수도 있다. 마치 확장 함수와 비슷한 느낌을 받는다.
수신자 타입이 Int 인 sum 함수를 정의 한다고 했을때 아래와 같이 2가지 방법 (람다, 익명함수)으로 작성할 수 있다.
// 람다
val sum: Int.(Int) -> Int = { other -> plus(other) } // this.plus 에서 this(수신자, Int) 가 생략됨
// 익명함수
val sum = fun Int.(other: Int): Int = this + other // this는 수신자(Int)
// 사용 방법
val a: Int = 3;
println(a.sum(5)) // Int의 확장 함수 처럼 사용 할수도 있고,
println(sum(a, 5)) // 첫번째 파라미터로 수신자를 전달 할 수도 있다.
'Kotlin' 카테고리의 다른 글
Kotlin - Operator overloading (연산자 오버로딩) (1) | 2024.06.11 |
---|---|
Kotlin - Inline functions (1) | 2024.04.21 |
Kotlin - 함수 (Functions) (0) | 2024.04.09 |
Kotlin - 타입 별칭 (Type aliases) (0) | 2024.03.29 |
Kotlin - Delegated properties (0) | 2024.03.28 |