Higher-order function은 다른 함수를 매개변수로 받거나 결과로 함수를 반환하는 함수입니다. Kotlin에서 함수는 일급 객체입니다: 변수에 저장하고, 인수로 전달하고, 반환할 수 있습니다. Kotlin 문서(2026)에 따르면, higher-order functions은 명령형 접근법에 비해 코드 중복을 평균 30% 감소시킵니다. Higher-order function은 현대 개발에서 함수형 스타일의 근본 개념입니다.
주요 포인트
Higher-order function은 다음 두 가지 특성 중 하나 이상을 가진 함수입니다: 다른 함수를 인수로 받거나 결과로 함수를 반환하는 것입니다. 수학에서는 이러한 함수를 함수론자나 연산자라고 합니다. 프로그래밍에서는 1958년 Lisp에 처음 등장하여 모든 현대 언어(JavaScript, Python, Swift, Kotlin, Scala, Haskell)의 필수 요소가 되었습니다. Higher-order function은 특정 작업에서 추상화하고 동작을 값으로 전달할 수 있게 합니다.
Higher-order function과 일반 함수의 주요 차이점은 함수형 타입이 있는 매개변수나 반환되는 함수형 값의 존재입니다. Kotlin에서 함수형 타입은 (ParamType) -> ReturnType으로 작성됩니다. 예를 들어, 타입 (Int) -> String은 Int를 받아 String을 반환하는 함수를 의미합니다. 타입 () -> Unit은 매개변수가 없고 유용한 값을 반환하지 않는 함수를 나타냅니다. 이 타입 시스템이 higher-order functions를 타입 안전하게 만듭니다.
Higher-order function의 반대는 first-class function(일급 함수)입니다. First-class function은 함수를 다른 값과 마찌가지로 사용할 수 있다는 것을 의미합니다: 변수에 할당하고, 커렉션에 저장하고, 인수로 전달할 수 있습니다. Higher-order function은 받거나 반환하기 위해 first-class functions를 사용하는 함수입니다. Kotlin은 추가 라이브러리 없이 언어 레벨에서 두 개념을 모두 지원합니다.
Kotlin에서 함수형 타입으로 선언된 매개변수가 하나 이상이거나 반환 타입이 함수형인 함수는 자동으로 higher-order function으로 간주됩니다. 컴파일러는 특별한 어노테이션이 필요 없습니다 — 시그니처에 (T) -> R을 지정하면 됩니다. 이러한 함수를 호출할 때, 인수는 lambda 표현식, ::을 통한 기존 함수에 대한 참조, 또는 변수에 저장된 함수형 값입니다.
fun operate(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}
fun main() {
val sum = operate(10, 20) { x, y -> x + y }
println(sum) // 30
}
리스트에서 operate 함수는 두 정수와 타입 (Int, Int) -> Int인 op 매개변수를 받습니다. 함수 본체는 한 줄에서 전달된 연산을 호출합니다. main에서 operate 호출은 lambda { x, y -> x + y }를 전달합니다 — Kotlin은 trailing lambda 구문 때문에 이를 괄호 뒤에 배치합니다. lambda가 마지막 인수라면 괄호 밖으로 완전히 옮길 수 있어 호출 체인의 가독성이 향상됩니다.
내부적으로 Kotlin은 Function 인터페이스를 통해 higher-order functions를 컴파일합니다. 각 lambda는 해당 FunctionN 인터페이스를 구현하는 익명 클래스로 변환됩니다. 이는 lambda 생성이 작업마다 객체 할당을 수반한다는 것을 의미합니다. 오버헤드를 줄이기 위해 Kotlin은 inline 함수를 지원하며, 이는 호출 사이트에서 higher-order function의 본체를 대체하여 익명 클래스 생성을 제거합니다.
함수를 인수로 전달하는 것은 higher-order functions 사용의 가장 일반적인 패턴입니다. 다형성 메소드가 있는 클래스 계층을 만드는 대신, 개발자는 원하는 동작을 사용 지점에 그대로 전달합니다. 이는 상속 없이 개방-폐쇄 원칙을 구현합니다: 새로운 작업은 새로운 하위 클래스가 아닌 새로운 lambda로 추가됩니다.
null 가능 함수의 경우 Kotlin은 괄호 다음에 물음표가 있는 ((T) -> R)? 타입을 사용합니다. 이러한 함수는 null 확인 후나 ?.invoke() 연산자를 통해서만 호출할 수 있습니다. 이 경우 higher-order function의 시그니처는 함수 전달이 선택적임을 명시적으로 나타냅니다 — 호출하는 코드가 인수를 생략할 수 있습니다. 이는 선택적 동작이 있는 콜백과 이벤트 핸들러에 유용합니다.
fun <T> List<T>.customFilter(
predicate: (T) -> Boolean
): List<T> {
val result = mutableListOf<T>()
for (item in this) {
if (predicate(item)) result.add(item)
}
return result
}
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val even = numbers.customFilter { it % 2 == 0 }
println(even) // [2, 4]
}
확장 함수 customFilter는 함수형 타입 (T) -> Boolean인 predicate 매개변수를 받습니다. 루프 내부에서 각 요소에 대해 predicate을 호출하고 일치하는 것을 모윽니다. main에서의 호출은 lambda { it % 2 == 0 }을 전달하며, 여기서 it은 lambda의 유일한 매개변수의 압시적 이름입니다. Higher-order functions 덕분에 필터링 로직이 커렉션 탐색 메커니즘으로부터 완전히 분리됩니다.
Higher-order function은 함수를 반환할 수 있습니다 — 이 패턴을 함수 팩토리 또는 동작 생성기라고 합니다. 반환된 함수는 외부 스코프의 변수를 캡처(클로저)하여 호출 사이에 유지할 수 있습니다. 이를 통해 구성 가능한 핸들러와 일반적인 템플릿에 기반한 전문 작업을 만들 수 있습니다.
함수를 반환할 때 Kotlin은 시그니처로부터 반환되는 함수형 ((T) -> R)?을 추론합니다. 컴파일러는 본체의 모든 return 표현식이 호환 가능한 함수형 값을 반환하는지 확인합니다. 캡처된 변수는 lambda 객체에 저장되고 참조가 있는 한 액세스 가능합니다. 이것은 강력한 메커니즘이지만 메모리 관리에 주의해야 합니다.
fun makeMultiplier(factor: Int): (Int) -> Int {
return { x -> x * factor }
}
fun main() {
val double = makeMultiplier(2)
val triple = makeMultiplier(3)
println(double(5)) // 10
println(triple(5)) // 15
}
makeMultiplier 함수는 factor을 받고 lambda { x -> x * factor }를 반환하며, 여기서 factor는 외부 스코프(클로저)로부터 캡처됩니다. makeMultiplier의 각 호출은 고유한 factor 값을 가진 새 함수를 만듭니다. double과 triple 변수는 반환된 함수를 저장하고 여러 번 호출할 수 있습니다. 이 패턴은 HTTP 클라이언트 구성, 데코레이터 및 미들웨어에서 광범위하게 사용됩니다.
Higher-order functions가 lambda를 결합하면 중간 변수 없이 표현력 있는 작업 체인을 구축할 수 있습니다. Kotlin 표준 라이브러리에는 수십 개의 higher-order functions가 있습니다: let, run, apply, also, filter, map, flatMap, fold, reduce, forEach, groupBy 등. 각각이 lambda를 받아 데이터 변환을 수행합니다.
data class User(val name: String, val age: Int)
fun main() {
val users = listOf(
User("Alice", 25),
User("Bob", 17),
User("Charlie", 30)
)
val result = users
.filter { it.age >= 18 }
.map { it.name.uppercase() }
.sorted()
println(result) // [ALICE, CHARLIE]
}
예제에서 세 개의 higher-order functions 체인이 사용자 목록을 처리합니다. filter는 서술어를 받아 성인만 남깁니다. map은 각 사용자를 대문자 이름으로 변환합니다. sorted는 결과를 오름차순으로 정렬합니다. 각 작업은 lambda를 받으며, Kotlin은 모든 단계에서 타입 안전성을 보장합니다. Higher-order functions이 없으면 if와 임시 리스트, 수동 정렬을 사용한 루프를 작성해야 합니다.
자주 묻는 질문
Higher-order function은 다른 함수를 매개변수로 받거나 함수를 반환합니다. 일반 함수는 데이터(숫자, 문자열, 객체)만 처리합니다. Higher-order function은 동작을 처리하며 로직을 인수로 전달합니다.
네, inline 수식자가 lambda의 익명 클래스 생성 오버헤드를 제거합니다. crossinline과 noinline을 사용하여 어떤 lambda가 인라인되고 어떤 것이 객체로 남을지 제어할 수 있습니다.
매개변수가 없고 반환값이 없는 함수의 타입은 () -> Unit입니다. 하나의 매개변수 T와 반환 R이 있는 함수는 (T) -> R로 작성됩니다. 두 개의 매개변수의 경우 (T, U) -> R이며, 22개의 인수까지 계속됩니다.
Lambda는 간결한 표기법 { args -> body }이고, 익명 함수는 fun(args): ReturnType { body }입니다. Lambda는 레이블 없이 return을 사용할 수 없지만 익명 함수는 가능합니다. 둘 다 higher-order function에 전달할 수 있습니다.
inline 없이 혼합 루프에서는 higher-order functions를 피하십시오 — 각 lambda가 객체를 만듭니다. 성능이 중요한 코드에서는 inline fun을 사용하세요. 깊게 중첩된 lambda를 너무 많이 사용하지 마세요 — 가독성이 떨어집니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.