HOF, High Order Function Kotlin


함수형 프로그래밍에서는 람다나 다른 함수를 인자로 받거나 함수를 반환하는 함수를 고차함수 (HOF, High Order Function) 라고 부른다. 고차 함수는 기본 함수를 조합하여 새로운 연산을 정의하거나, 다른 고차 함수를 통해 조합된 함수를 또 조합해서 더 복잡한 연산을 쉽게 정의할 수 있다는 장점이 있다. 이런 식으로 고차 함수와 단순한 함수를 이리저리 조합해서 코드를 작성하는 기법을 컴비네이터 패턴 (combinator pattern) 이라 부르고, 컴비네이터 패턴에서 복잡한 연산을 만들기 위해 값이나 함수를 조합할 때 사용하는 고차 함수를 컴비네이터 (combinator) 라고 부른다

함수 정의

val sum = { x: Int, y: Int -> x + y }
val action = { println(42) }
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
val action: () -> Unit = { println(42) }
var canReturnNull: (Int, Int) -> Int? = { x, y -> null }
var funOrNull: ((Int, Int) -> Int)? = null
함수를 인자로 받는 함수
fun twoAndThree(operation: (Int, Int) -> Int) {
val result = operation(2, 3)
println("The result is $result")
}
twoAndThree { a, b -> a + b }
// The result is 5
twoAndThree { a, b -> a * b }
// The result is 6


fun String.filter(predicate: (Char) -> Boolean): String {
val sb=StringBuilder()
for (x in 0 until length) {
val e = get(x)
if (predicate(e))
sb.append(e)
}
return sb.toString()
}

fun a() {
println("ab1c".filter { it in 'a'..'z' })
// "abc"
}

매개변수로 기본함수 설정


fun <T> Collection<T>.joinString(separator: String = ", ",
prefix: String = "",
postfix: String = "",
transform: (T) -> String = { it.toString() }): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(transform(element))
}
result.append(postfix)
return result.toString()
}
fun main(args: Array<String>) {
val letters = listOf("Alpha", "Beta")
println(letters.joinString())
println(letters.joinString { it.toLowerCase() })
println(letters.joinString(separator = "! ", postfix = "! ", transform = { it.toUpperCase() }))
}

널 가능 함수형 매개변수
fun <T> Collection<T>.joinString(separator: String = ", ",
prefix: String = "",
postfix: String = "",
transform: ((T) -> String)? = null): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(transform?.invoke(element) ?: element.toString())
}
result.append(postfix)
return result.toString()
}

함수형 매개변수 사용예시
1단계

data class SiteVisit(val path: String, val duration: Double, val os: OS)
enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID }

fun main(args: Array<String>) {
val log = listOf(SiteVisit("/", 34.0, OS.WINDOWS),
SiteVisit("/", 22.0, OS.MAC),
SiteVisit("/login", 12.0, OS.WINDOWS),
SiteVisit("/signup", 8.0, OS.IOS),
SiteVisit("/", 16.3, OS.ANDROID)
)
val averageWindowsDuration = log.filter { it.os == OS.WINDOWS }.map(SiteVisit::duration).average()
println(averageWindowsDuration)
}
2단계: 확장함수를 사용해 별도 함수로 분리

data class SiteVisit(val path: String, val duration: Double, val os: OS)
enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID }

fun List<SiteVisit>.averageDurationFor(os: OS) = filter { it.os == os }.map(SiteVisit::duration).average()

fun main(args: Array<String>) {
val log = listOf(SiteVisit("/", 34.0, OS.WINDOWS),
SiteVisit("/", 22.0, OS.MAC),
SiteVisit("/login", 12.0, OS.WINDOWS),
SiteVisit("/signup", 8.0, OS.IOS),
SiteVisit("/", 16.3, OS.ANDROID)
)
println(log.averageDurationFor(OS.WINDOWS))
println(log.averageDurationFor(OS.MAC))
}
3단계: 필터코드를 람다로 분리하여 모바일 플랫폼을 대상으로 한다

data class SiteVisit(val path: String, val duration: Double, val os: OS)
enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID }

//fun List<SiteVisit>.averageDurationFor(os: OS) = filter { it.os == os }.map(SiteVisit::duration).average()
fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean) =
filter { predicate(it) }.map(SiteVisit::duration).average()

fun main(args: Array<String>) {
val log = listOf(SiteVisit("/", 34.0, OS.WINDOWS),
SiteVisit("/", 22.0, OS.MAC),
SiteVisit("/login", 12.0, OS.WINDOWS),
SiteVisit("/signup", 8.0, OS.IOS),
SiteVisit("/", 16.3, OS.ANDROID)
)
println(log.averageDurationFor { it.os == OS.WINDOWS })
println(log.averageDurationFor { it.os == OS.MAC })
println(log.averageDurationFor { it.os == OS.IOS || it.os == OS.ANDROID })
}




덧글

댓글 입력 영역