본문 바로가기

프로그래밍/kotlin

코틀린 - 고차 함수와 람다

반응형

고차 함수

고차 함수는 함수를 매개 변수로 사용하거나 함수를 반환하는 함수입니다. 그러한 함수의 좋은 예는 lock()잠금 객체와 함수 를 취하여 잠금을 획득하고, 함수를 실행하고 잠금을 해제하는 것입니다.

fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body()
    }
    finally {
        lock.unlock()
    }
}

의 위의 코드를 검사하자 : body이 기능 유형을 : () -> T그래서 매개 변수를 사용하지 않는 입력의 값을 반환하는 기능을해야하는데 T그것은 try 블록 안에서 호출 되며, lock에 의해 보호되고 그 결과는 lock()함수에 의해 반환됩니다 .

우리가 호출하기를 원한다면 lock(), 다른 함수를 인자로 전달할 수있다 ( 함수 참조를 보라 ) :

fun toBeSynchronized() = sharedResource.operation()

val result = lock(lock, ::toBeSynchronized)

또 다른, 더 편리한 방법은 람다 표현식 을 전달하는 것입니다 :

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 }

이러한 규칙에 따라 LINQ 스타일 코드 를 작성할 수 있습니다.

strings.filter { it.length == 5 }.sortedBy { it }.map { it.toUpperCase() }

사용되지 않는 변수의 밑줄 (1.1 버전 이후)

lambda 매개 변수가 사용되지 않으면 이름 대신 밑줄을 사용할 수 있습니다.

map.forEach { _, value -> println("$value!") }

람다에서의 분리 선언 (1.1 버전 이후)

람다에서의 분리 선언은  분리 선언에서 설명합니다 .

인라인 함수

때로는 인라인 함수를 사용하여 고차 함수의 성능을 향상시키는 것이 좋습니다 .

람다 식과 익명 함수

람다 식 또는 익명 함수는 "함수 리터럴"입니다. 즉, 선언되지 않고 즉시 식으로 전달되는 함수입니다. 다음 예제를 고려하십시오.

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두 가지 매개 변수를 사용하고 첫 번째 매개 변수가 두 번째 매개 변수 보다 작 으면 true를 T반환 하는 함수입니다 Boolean.

본문에서 4 행 less은 함수로 사용됩니다 T두 개의 인수 유형을 전달하여 호출합니다 .

함수 유형은 위와 같이 작성되거나 각 매개 변수의 의미를 문서화하려는 경우 명명 된 매개 변수를 가질 수 있습니다.

val compare: (x: T, y: T) -> Int = ...

함수 유형의 Nullable 변수를 선언하려면 전체 함수 유형을 괄호로 묶고 뒤에 물음표를 넣으십시오.

var sum: ((Int, Int) -> Int)? = null

람다 식 구문

함수 형식의 리터럴 인 람다 식의 전체 구문 형식은 다음과 같습니다.

val sum = { x: Int, y: Int -> x + y }

람다 식은 항상 중괄호로 둘러 쌉니다. 전체 구문 형식의 매개 변수 선언은 중괄호 안에 들어가고 선택적 형식 주석을 사용하면 본문이 ->부호 뒤에옵니다 유추 된 람다 반환 유형이 아닌 Unit경우 람다 본문의 마지막 식 (또는 단일 식일 수도 있음)이 반환 값으로 처리됩니다.

모든 선택적인 주석을 남겨두면 남은 것은 다음과 같습니다.

val sum: (Int, Int) -> Int = { x, y -> x + y }

람다 표현식에는 하나의 매개 변수 만있는 것이 일반적입니다. Kotlin이 자체적으로 서명을 알아낼 수 있다면 우리는 유일한 매개 변수를 선언 할 수 없으며 암시 적으로 다음과 같이 이름으로 선언합니다 it.

ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'

우리는 명시 적으로 사용 람다에서 값을 반환 할 수 있습니다 자격을 갖춘 리턴 구문을. 그렇지 않으면 마지막 표현식의 값이 내재적으로 리턴됩니다. 따라서 다음 두 스 니펫은 동일합니다.

ints.filter {
    val shouldFilter = it > 0 
    shouldFilter
}

ints.filter {
    val shouldFilter = it > 0 
    return@filter shouldFilter
}

함수가 다른 매개 변수를 마지막 매개 변수로 사용하면 괄호 안의 인수 목록 밖에서 람다 식 인수를 전달할 수 있습니다. callSuffix 의 문법을 참조하십시오 .

익명 함수

위에 제시된 람다 식 구문에서 빠진 한 가지는 함수의 반환 형식을 지정하는 기능입니다. 대부분의 경우 반환 유형을 자동으로 유추 할 수 있으므로 불필요합니다. 그러나 명시 적으로 지정해야하는 경우 익명의 함수 인 대체 구문을 사용할 수 있습니다 .

fun(x: Int, y: Int): Int = x + y

익명 함수는 이름이 생략된다는 점을 제외하고는 일반 함수 선언과 매우 흡사합니다. 그 몸체는 위의 그림과 같은 표현식이거나 블록 일 수 있습니다.

fun(x: Int, y: Int): Int {
    return x + y
}

매개 변수 및 반환 유형은 정규 함수와 동일한 방식으로 지정됩니다. 단, 매개 변수 유형을 컨텍스트에서 유추 할 수있는 경우 생략 할 수 있습니다.

ints.filter(fun(item) = item > 0)

익명 함수에 대한 반환 유형 유추는 일반 함수처럼 작동합니다. 반환 유형은 표현 본문이있는 익명 함수에 대해 자동으로 유추 Unit되며 블록 본문이있는 익명 함수에 대해 명시 적으로 지정되어야합니다 .

익명 함수 매개 변수는 항상 괄호 안에 전달됩니다. 괄호 밖에서 함수를 그대로 두는 것을 허용하는 속기 구문은 람다 식에만 적용됩니다.

람다 식과 익명 함수의 또 다른 차이점은 비 지역 반환 의 동작입니다 반환 레이블이없는 문은 항상 선언 함수에서 반환 재미 키워드. 이것은 것을 의미 복귀 반면 람다 식 내부가 바깥 쪽 함수로부터 반환  익명 함수 내부 익명 함수 자체에서 복귀한다.

클로저

람다 표현식이나 익명의 함수 ( 로컬 함수 와 객체 표현식은 물론 )는 클로저 , 즉 외부 범위에서 선언 된 변수에 액세스 할 수 있습니다 Java와 달리 클로저에서 캡처 된 변수는 수정할 수 있습니다.

var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

리시버가있는 함수 리터럴

Kotlin은 지정된 리시버 오브젝트 로 함수 리터럴을 호출하는 기능을 제공 합니다 . 함수 리터럴의 본문에서는 추가 한정자없이 해당 리시버 개체의 메서드를 호출 할 수 있습니다. 이는 함수의 본문 내부에있는 리시버 오브젝트의 멤버에 액세스 할 수있는 확장 함수와 유사합니다. 사용법의 가장 중요한 예 중 하나는 타입 안전 Groovy 스타일 빌더 입니다.

이러한 함수 리터럴 유형은 리시버가있는 함수 유형입니다.

sum : Int.(other: Int) -> Int

함수 리터럴은 마치 리시버 객체의 메소드 인 것처럼 호출 할 수 있습니다.

1.sum(2)

익명 함수 구문을 사용하면 함수 리터럴의 수신기 유형을 직접 지정할 수 있습니다. 이것은 수신기로 함수 유형의 변수를 선언하고 나중에 사용해야하는 경우 유용 할 수 있습니다.

val sum = fun Int.(other: Int): Int = this + other

수신자 유형이 컨텍스트에서 유추 될 수있을 때 람다 식은 수신자와 함께 함수 리터럴로 사용될 수 있습니다.

class HTML {
    fun body() { ... }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()  // 리시버 객체 생성
    html.init()        // 람다에 리시버 객체를 전달
    return html
}


html {       // 리시버를 가진 람다 시작
    body()   // 리시버 객체의 메서드 호출
}


반응형

'프로그래밍 > kotlin' 카테고리의 다른 글

코틀린 - 확장  (0) 2021.04.25
코틀린 - 함수  (0) 2021.04.18
코틀린 - 인라인 함수  (0) 2021.04.18
코틀린 - 코루틴  (0) 2021.04.18
코틀린 - 분리 선언  (0) 2021.04.18