고차 함수
고차 함수는 함수를 파라미터로 받거나 함수를 리턴하는 함수이다.
fun <T> lock(lock: Lock, body: () -> T): T{
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
위 코드를 보면 body는 함수 타입인 () -> T이다. 이 함수 타입은 파라미터가 없고 T를 리턴하는 함수이다. lock()을 호출할 때 인자로 다른 함수를 전달할 수 있다.
fun toBesynchronized() = sharedResource.operation()
val result = lock(lock, ::totoBesynchronized)
보다 더 간편한 방법은 람다식을 전달하는 방법이다.
val result = lock(lock, { sharedResource.operation() })
전달하는 람다식의 조건은 다음과 같다.
- 람다 식은 항상 중괄호로 둘러싼다.
- -> 앞에 파라미터를 선언한다.( 타입 생략 가능)
- -> 뒤에 바디가 온다 (바디가 존재할 경우)
코틀린에서 함수의 마지막 파라미터가 함수면, 괄호 밖에서 람다식을 인자로 전달할 수 있다.
lock(lock) {
sharedResource.operation() // 위 코드와 다르게 괄호 밖에서 람다식을 인자로 전달하고있다.
}
다른 예로 map() 함수를 볼 수 있다.
fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
val result = arrayListOf<R>()
for (item in this)
result.add(transform(item))
return result
}
이 함수의 호출은 다음과 같다.
val doubled = ints.map {value -> value * 2}
호출 시 인자가 람다밖에 없다면 괄호를 생략할 수 있다.
it : 단일 파라미터의 암묵적 이름
함수 리터럴의 파라미터가 한 개면, 파라미터 선언( -> )을 생략하고 it을 통해 접근할 수 있다.
ints.map { it * 2 }
이 규칙은 링크 방식 코드에 도움된다.
strings.filter { it.length == 5 }.sortedBy{ it }.map{ it.toUpperCase() }
사용하지 않는 람다 파라미터 밑줄 표기
람다 파라미터를 안 쓰면 이름 대신 밑줄을 사용할 수 있다.
map.forEach { _, value -> println("$value")}
인라인 함수
때때로 고차 함수의 성능을 향상하기 위해 인라인 함수를 사용하는 것이 좋다.
람다 식과 익명 함수
람다 식 또는 익명 함수는 함수 선언 없이 바로 식으로 전달하는 함수 인 "함수 리터럴"이다.
max(strings, {a, b -> a.length < b.length})
max함수는 고차 함수로써 두 번째 인자로 함숫값을 취한다. 두 번째 인자는 함수 리터럴이다. 아래 함수와 같다고 볼 수 있다.
fun compare(a: String, b: String): Boolean = a.length < b.length
함수 타입
함수가 다른 함수를 파라미터로 받을 때, 파라미터를 위한 함수 타입을 지정해야 한다. 보통은 제네릭을 이용한다.
앞에서 나왔던 예제인 max함수는 다음과 같이 정의했다.
fun<T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
var max: T? = null
for( it in collection )
if(max == null || less(max, it))
max = it
return max
}
less파라미터는 (T, T) -> Boolean
타입으로 T타입 파라미터 둘에 Boolean을 리턴한다. 첫 번째가 두 번째보다 작으면 true를 리턴한다.
4번째 줄을 보면 less를 함수로써 사용한다. max와 it을 전달해서 less를 호출한다.
함수 타입을 위와 같이 작성하거나 또는 각 파라미터의 의미를 문서화하고 싶다면 파라미터에 이름을 붙일 수 있다.
val compare: (x: T, y: T) -> Int = ...
함수 타입을 nullable로 하고 싶으면 괄호로 감싸고 물음표를 붙인다.
val sun: ((Int, Int) -> Int)? = null
람다 식 구문
람다식이란 함수명을 선언하고 사용하는 것이 아닌 식별자 없이 실행 가능한 함수이다.
식별자 = 함수명
함수 리터럴의 완전한 구문 형식은 다음과 같다.
val sum = { x: Int, y: Int -> x + y}
람다식의 특징은 다음과 같다.
- 람다 식은 항상 중괄호로 둘러싼다.
- -> 앞에 파라미터를 선언한다.( 타입 생략 가능)
- -> 뒤에 바디가 온다 (바디가 존재할 경우)
- 리턴 타입이 Unit이 아니라면 바디의 마지막 식을 리턴 값으로 처리한다.
선택사항을 생략하면 다음과 같이 보인다.
val sum: (Int, Int) -> Int = { x, y -> x + y} //타입 생략
코드 작성하다 보면 람다 식의 파라미터가 한 개 인 것은 매우 흔하다. 이럴 때 한 개인 파라미터를 선언하지 않는 것이 가능하면 이는 it
으로 암묵적으로 대체 가능하다.
이는 코틀린이 해당 함수의 시그니처 자체를 알아낼 수 있기 때문이다.
ints.filter { it > 0 }
라벨을 통해 람다에서 값을 명시적으로 리턴할 수 있다. 그렇지 않으면 마지막 식의 값을 리턴한다.
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter //filter에 리턴
}
익명 함수
많은 경우, 람다는 리턴 타입을 자동으로 추론해서 리턴타입이 불필요하게 느껴질 수 있다. 명시적으로 리턴타입을 지정하고 싶으면 익명 함수를 쓰면 된다.
이름 까잡수고 선언하면 익명 함수다.
fun(x: Int, y: Int): Int = x + y //리턴타입 지정
일반 함수랑 비슷해 보이지만 이름이 없다.
fun(x: Int, y: Int): Int {
return x + y
}
이름이 없으면 호출은 어떻게 할까?
파라미터와 리턴 타입은 일반 함수와 같은 방법으로 지정한다.
ints.filter(fun(item) = item > 0)
익명 함수에 대한 리턴 타입 추론은 일반 함수와 같다. 바디가 식인 익명 함수에 대한 리턴 타입은 자동으로 유추한다. 블록 바디를 가진 익명 함수의 경우 명시해줘야 한다.(아니면 Unit)
익명 함수를 람다처럼 괄호 밖에 함수 전달을 허용하는 거 안된다. 또한 익명 함수와 람다 식의 차이는 비 로컬 리턴의 동작 방식이다. 라벨이 없는 return 문장은 항상 fun 키워드로 선언한 함수에서 리턴한다. 람다의 return은 둘러싼 함수를 리턴하는 반면 익명 함수의 return은 익명 함수 자체로부터 리턴한다는 것을 의미한다.
클로저
람다 식 또는 익명 함수는 외부 범위에 선언된 변수에 접근할 수 있다.
var sum = 0
ints.filter{ it > 0 }.forEach{
sum += it
}
print(sum)
리시버를 가진 함수 리터럴
함수 리터럴 바디 안에서 별도 한정자 없이 리시버 객체의 메서드를 호출할 수 있다. 바디 안에서 리시버 객체의 멤버에 접근할 수 있는 확장 함수와 유사
sum : Int.(other: Int) -> Int
마치 리시버 객체에 메서드가 존재하는 것처럼 함수 리터럴을 호출할 수 있다.
1.sum(2)
익명 함수는 함수 리터럴의 리시버 타입을 직접 지정할 수 있게 한다. 이는 리시버를 사용한 함수 타입의 변수를 선언하고 나중에 사용해야 할 때 유용하다.
val sum = fun Int.(other: Int): Int = this + other
리시버 타입을 갖는 함수의 비-리터럴 값은 일반 함수의 인자로 전달할 수 있다. 예를 들어String.(Int) -> Boolean
과 (String, Int) -> Boolean
은 호환된다.
val represents: String.(Int) -> Boolean = { other -> toIntOrNull() == other}
println("123".represents(123))
fun testOperation(op: (String, Int) -> Boolean, a: String, b: Int, c: Boolean) = assert(op(a, b) == c)
testOperation(represents, "100", 100, true)
'코틀린' 카테고리의 다른 글
16. 코루틴 기본 (0) | 2022.06.13 |
---|---|
15. 인라인 함수 (0) | 2022.06.13 |
13. 함수 (0) | 2022.06.12 |
12. 위임 (0) | 2022.06.12 |
11. 오브젝트 식과 선언 (0) | 2022.06.12 |