Kotlin은 함수형 프로그래밍(Functional Programming)을 지원하는 언어다.
물론 Kotlin은 함수형 프로그래밍을 지원할 뿐, 굳이 함수형 프로그래밍을 하지 않아도 된다.
그렇지만 때때로 함수형 프로그래밍을 이용하면 편하게 구현할 수 있다.
또한 코틀린에서 제공되는 여러 함수를 정확히 사용하려면 인라인에 대한 이해가 필요하다.
함수형 프로그래밍의 원칙 중 하나로 "일급 객체로서의 함수"가 있다.
이 말은 첫번째로 함수가 프로그램의 최상위 구성요소라는 것이다.
함수가 최상위 구성요소가 되면 함수내에 함수, 변수 심지어 클래스도 들어갈 수 있다.
Java에서는 모든 것은 클래스안에 위치해야했다.
하지만 Kotlin에서는 함수가 프로그램 최상위 구성요소가 될 수 있으므로 아래와 같은 코드가 가능하다.
fun test() {
val hello = 1
fun world() = 2
class Test
}
또한 위에서 언급한 최상위 요소랑 연결되는 사항으로, 함수를 인자로 넘길 수 있고, 함수를 반환할 수 있어야한다.
함수라는걸 뭔가 특별하다고 생각하지 않고, 일반 변수처럼 굴릴 수 있다고 생각하면 이해하기 쉬울 것이다.
그러니까
fun test(num: Int): Double = num.toDouble()
하듯이 저 자리에 함수를 넣어도 된다는 것이다.
예시를 보자.
fun testFun(a: (String) -> Int): (Int) -> Int {
return { i: Int -> i + a("Hello") }
}
testFun
을 보면
(String
을 Int
로 변환하는 함수)를 인자로 받아서 (Int
을 Int
로 변환하는 함수)를 반환하고 있다.
이렇게 매개변수로 함수를 받거나 함수를 반환하는 함수를 고차함수(High-Order Function)이라고 한다.
함수의 fun 앞에 inline
을 붙이면 그 함수를 inline 함수로 만들 수 있다.
inline 함수는 inline 함수가 호출되는 곳에 컴파일 단계에 정적으로 포함된다.
대체 저게 무슨말이냐고 할 수 있다.
백문이 불여일견이라고 예제를 통해서 inline 함수의 용도를 살펴보자.
아래는 간단하게 (두 정수를 한 정수로 만드는 함수)를 인자로 받는 함수다.
fun hoFunTest(argFun: (Int, Int) -> Int) {
argFun(10, 20)
}
fun main() {
hoFunTest { x1, x2 -> x1 * x2 }
}
대게 Kotlin은 JVM을 쓰므로 위 Kotlin 코드를 Java로 변환해보자.
public static final void hoFunTest(@NotNull Function2 argFun) {
Intrinsics.checkNotNullParameter(argFun, "argFun");
argFun.invoke(10, 20);
}
public static final void main() {
hoFunTest((Function2)null.INSTANCE);
}
Java 코드를 보면 Kotlin 코드를 그대로 옮겨놓은 것을 볼 수 있다.
main()
에서 hoFunTest()
를 호출하고 hoFunTest()
에서 넘겨받은 argFun()
을 호출하는 구조다.
같은 예제에서 이번에는 hoFunTest()
에 inline 키워드를 붙여보자. 즉 이렇게 바꿔보자.
inline fun hoFunTest(argFun: (Int, Int) -> Int) {
argFun(10, 20)
}
그러면 Java에서는
public static final void main() {
int $i$f$hoFunTest = false;
int x2 = 20;
int x1 = 10;
int var3 = false;
int var10000 = x1 * x2;
}
로 변환된다.
아까랑 확연하게 다른 점이 하나 있다.
main()
에서 다른 함수를 call 하지 않고 hoFunTest()
가 main()
에 포함되어버린 것이다.
즉 아까 위에서 말했듯이 inline 함수가 호출되는 곳인 main()
에 컴파일 타임에 포함되어버린 것이다.
코드 자체가 복사되었다고 이해하면 된다.
inline을 쓰면 런타임 최적화를 할 수 있다.
inline을 쓰지 않은 예제의 java코드에서 확인할 수 있듯이,
inline을 사용하지 않으면 같은 행동을 함에도 불구하고, 함수 호출 횟수는 더 많아진다.
그리고 함수 호출시 이 람다함수들은 Function
이라는 Java 객체로 변환된다.
결국 이 말은 inline을 쓰지 않아서 쓸데없이 객체가 생성되고 런타임 자원을 소모하게 된다는 것이다.
고차함수에 inline을 걸면 이런 낭비를 줄일 수 있다.
또한 논 로컬 리턴을 사용할 수 있다.
원래 코틀린 람다함수에서는 return을 사용할 수 없다.
하지만 람다함수가 inline함수의 인자라면 return을 사용할 수 있다.
이를 이용해서 람다함수 또는 inline을 호출한 함수에서 return할 수 있다.
어떤 함수에 두 함수 A와 B를 인자로 받는다고 하자.
이때 A는 inline을 하고 싶지만, B는 inline으로 하고 싶지 않을 수 있다.
(최종 코드 길이가 길어진다던가, 재귀불가, private 키워드 사용의 제한 등 단점이 없지 않다.)
그런 경우 아래와 같이 함수명에는 inline
키워드를 붙이되, 필요한 인자에 noinline
을 붙여 작성하면 된다.
inline fun hoFunTest(A: (Int) -> Int, noinline B: (Int) -> Int)
inline 함수의 매개변수인 함수 C가 return을 포함하지 않도록 강제해야하는 경우가 있다.
함수 C가 inline 함수에서 직접 불리는 것이 아니라 다른 실행 컨텍스트를 통해 호출되는 경우가 그런 경우다.
이런 경우 함수 C 앞에 crossinline
키워드를 붙이면 된다.
inline fun hoFunTest(crossinline C: (Int) -> Int)