Inline Functions

고차 함수를 사용하면 특정 런타임에 패널티가 부과된다. 각 함수는 객체이며 클로저, 즉 함수 바디에서 접근되는 변수를 캡처한다. 메모리 할당 (함수 객체와 클래스용)과 가상 호출은 런타임 오버 헤드를 초래한다.

그러나 많은 경우, 람다식을 inline 해서 이런 오버 헤드를 제거가능 하다. lock() 함수는 호출 위치에서 쉽게 인라인 될 수 있다.

lock(l) { foo() }

파라매터를 위한 함수 객체를 만들고 호출을 생성하는 대신 컴파일러는 다음 코드를 만들 수 있다.

l.lock()
try {
    foo()
}
finally {
    l.unlock()
}

컴파일러가 이렇게 하려면 lock() 함수를 inline 제한자로 지정해야한다.

inline fun lock<T>(lock: Lock, body: () -> T): T {
    // ...
}

inline 제한자는 함수 자체와 전달된 람다 모두에 영향을 미친다. 모두 호출 위치에 인라인된다.

inline 하면 생성된 코드가 증가할 수 있지만, 합리적인 방법(큰 함수를 inline 하지않음)으로 성능면에서 특히 루프 내부의 "거대" 호출 위치에서 효과가 있다.

noinline

inline 함수에 전달된 람다 중 일부만 inline 하려면, 함수 파라매터 주 일부를 noinline 제한자를 지정할 수 있다

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
    // ...
}

인라인 가능한 람다는 inline 함수안에서만 호출되거나 인라인 가능한 인자로 전달될 수 있지만, noinline 은 원하는 방식으로 조작 가능하다. (필드에 저장하거나 인자로 전달)

inline 함수에 인라인 가능함 함수 파라매터가 없고 "reified 타입 파라매터"가 없으면 컴파일러가 해당 함수를 inline하면 이점이 없다고 경고를 표시한다

Non-local returns

Kotlin에서는 이름을 가지는 함수나 익명 함수를 종료하기 위해서는 return 만 사용할 수 있다. 람다에서 종료하려면 라벨을 사용한다. 람다에서는 return을 허용하지 않는데, 그 이유는 람다는 둘러싼 함수를 반환할 수 없기때문이다.

fun foo() {
    ordinaryFunction {
        return // ERROR: 'foo' 를 여기에서 리턴할 수 없다
    }
}

그러나 람다가 전달된 함수가 인라인되면, 리턴도 인라인 가능하므로 허용된다

fun foo() {
    inlineFunction {
        return // OK: 람다는 인라인되어 있다
    }
}

이러한 리턴 (람다에 위치하지만 둘러싼 함수를 나가는)을 Non-local 리턴이라고 한다. 이런 리턴을 inline 함수가 둘러싼 루프에서 사용되곤 한다.

fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true // returns에서 리턴
    }
    return false
}

일부 inline 함수는 파라매터로 전달된 람다를 호출할때 함수 본문에서 직접 호출하지 않고, 다른 실행 컨텍스트를 통해 (로컬 객체나 중첩 함수) 호출할 수 있다. 이 경우, 람다에서는 Non-local 흐름을 제어할 수 없다. 이를 지정하려면 람다 파라매터에 crossinline 제한자를 지정해야한다.

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
    // ...
}
인라인된 람다에서 break와 continue는 아직 사용할 수 없지만, 앞으로 지원할 계획입니다.

Reified 타입 파라매터

때때로 파라매터로 전달된 타입에 접근해야할 때가 있다.

fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
        p = p?.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T
}

이 코드는 노드가 특정 타입을 가졌는지 확인하기 위해 트리를 탐색하고 reflection 을 사용한다. 호출하는 코드가 이쁘지 않다.

myTree.findParentOfType(MyTreeNodeType::class.java)

실제로 이 함수에 타입을 전달해서 다음과 같이 호출하길 원하는 것이다.

myTree.findParentOfType<MyTreeNodeType>()

이 기능을 사용하려면 inline 함수는 reified 타입 파라매터를 지원하므로 다음과 같이 작성할 수 있다.

inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p?.parent
    }
    return p as T
}

타입 파라매터를 reified 제한자로 적용하면 마치 클래스처럼 타입 파라매터에 접근할 수 있다. inline 함수이므로 reflection이 될 필요없고 !isas 와 같은 일반 연산자가 동작한다. 또한 앞서 언급한 myTree.findParentOfType<MyTreeNodeType>() 처럼 호출할 수 있다.

reflection이 많은 경우에 필요하지 않지만, reified 타입 파라매터와 함께 사용할 수 있다

inline fun <reified T> membersOf() = T::class.members

fun main(s: Array<String>) {
    println(membersOf<StringBuilder>().joinToString("\n"))
}

(인라인이 아닌) 일반 함수는 reified 파라매터를 가질 수 없다. 런타임 표현을 갖지 않는 타입 (예: reified 타입 파라매터가 아니거나 Nothing과 같은 가상 타입)은 reified 타입 파라매터를 위한 인자로 사용할 수 없다.

저수준의 설명은, 다음 문서를 참조

Inline 프로퍼티 (since 1.1)

inline 제한자를 backing field를 가지지않는 프로퍼티의접근자에 접근자에서 사용할 수 있다. 개발 프로퍼티 주석을 달 수 있다.

val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() = ...
    inline set(v) { ... }

모든 접근자를 인라인으로 표시하는 전체 프로퍼티에 주석을 달수도 있다

inline var bar: Bar
    get() = ...
    set(v) { ... }

호출 위치에서 inline 접근자는 일반 인라인 함수로 인라인된다.

results matching ""

    No results matching ""