고차 함수 → 다른 함수를 인자로 받거나 반환하는 함수
filter 함수도 고차 함수임
list.filter { it > 0 }
이제 고차 함수를 정의하는 방법을 알아본다. 고차 함수를 정의하려면 먼저 함수 타입을 알아야 함
val sum = { x: int, y: Int -> x + y }
val action = { println(42) }
val sum: (Int, Int) -> Int = {x, y -> 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")
}
fun main() {
twoAndThree { a, b -> a + b }
// The result is 5
twoAndThree { a, b -> a * b }
// The result is 6
}
파라미터 이름과 함수 타입
- 함수 타입에서 파라미터 이름을 지정할 수 있음
fun twoAndThree(operation: (operandA: Int, operandB: Int) -> Int) { // 함수 타입인 파라미터를 선언 val result = operation(2, 3) // 함수 타입인 파라미터를 호출 println("The result is $result") } fun main() { twoAndThree { operandA, operandB -> operandA + operandB } // API에서 지정한 이름을 람다에 사용할 수 있음 // The result is 5 twoAndThree { alpha, beta -> alpha + beta } // 그냥 원하는 이름도 사용할 수 있음 // The result is 5 }
예제를 단순하게 유지하기 위해 String에 대한 filter를 구현
fun String.filter(predicate: (Char) -> Boolean): String {
return buildString {
for (char in this@filter) { //입력 문자열 이터레이션
if (predicate(char)) append(char) // predicte 파라미터로 전달받은 함수를 호출
}
}
}
fun main() {
println("ab1c".filter { it in 'a' .. 'z' })
///abc
}
/* 코틀린 선언*/
fun processTheAnswer(f: (Int) -> Int) {
println(42)
}
/* 자바 호출 */
processTheAnswer(number -> number + 1);
import kotlin.collections.CollectionsKt;
/ *... */
public static void main(String[] arg) {
List<String> strings = new ArrayList();
strings.add("42");
CollectionsKt.forEach(strings, s -> { // 코틀린 표준 라이브러리 함수 호출 가능
System.out.println(s);
return Unit.INSTANCE; // Unit 타입의 값을 명시적으로 반환해야 함
});
}
interface Function1<P1, out R>{
operator fun invoke(p1: P1): R
}
fun processTheAnswer(f: Function1<Int, Int>) {
println(f.invoke(42))
}
함수 타입의 파라미터에 대한 기본값으로 람다식을 지정해놓으면 호출할 때마다 람다를 넘겨주지 않아도 되고 필요 시 람다를 넘겨 원하는 기능을 추가할 수 있음
fun <T> Collection<T>.joinToString(
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)) // transform 파라미터에 대한 인자로 받은 함수 호출
}
result.append(postfix)
return result.toString()
}
fun main() {
val letters = listOf("Alpha", "Beta")
println(letters.joinToString()) //transform 기본 함수 사용
// Alpha, Beta
println(letters.joinToString { it.lowercase() }) // 람다를 인자로 전달
// alpha, beta
println(letters.joinToString(seperator="! ", postfix="! ", transform={ it.uppercase() }))
// 이름 붙은 인자 구문을 사용해 람다를 포함하는 여러 인자를 전달
// ALPHA! BETA!
}
fun <T> Collection<T>.joinToString(
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)
val str = transform?.invoke(element) ?: element.toString()
result.append(str)
}
result.append(postfix)
return result.toString()
}
프로그램 상태나 다른 조건에 따라 달라질 수 있는 로직이 있다면 함수를 반환하는 기능이 유용함
enum class Delivery { STANDARD, EXPEDITED }
class Order(val itemCount: Int)
fun getShppingCostCalculator(delivery: Delivery): (Order) -> Double { // 함수를 반환하는 함수 선언
if(delivery == Delivery.EXPEDITED) {
return { order -> 6 + 2.1 * order.itemCount } // 함수에서 람다를 반환
}
return { order -> 1.2 * order.itemCount } // 함수에서 람다를 반환
}
fun main() {
val calculator = getShippingCostCalculator(Delivery.EXPEDITED) // 함수를 반환받아 저장
println("Shipping costs ${calculator(Order(3))}") // 반환받은 함수 호출
// Shipping costs 12.3
}
data class Person(
val firstName: String,
val lastName: String,
val phoneNumber: String?
)
class ContactListFilters {
var prefix:String = ""
var onlyWithPhoneNumber: Boolean = false
fun getPrediction(): (Person) -> Boolean { // 함수를 반환하는 함수 정의
val startWithPrefix = { p: Person ->
p.firstName.startsWith(prefix) || p.lastName.startsWith(prefix)
}
if(!onlyWithPhoneNumber) {
return startWithPrefix // 함수 타입의 변수 반환
}
return { startWithPrefix(it) && it.phoneNumber != null } // 람다를 반환
}
}
fun main() {
val contact = listOf(
Person("Dmitry", "Jemerov", "123-4567"),
Person("Svetlana", "Isakova", null)
)
val contactListFilters = ContactListFilters()
with (contactListFilters) {
prefix = "Dm"
onlyWithPhoneNumber = true
}
println(
contact.filter(contactListFilters.getPrediction()) // 반환한 함수를 filter에 인자로 넘김
)
// [Person(firstName=Dmitry, lastName=Jemerov, phoneNumber=123-4567)]
}
람다식 → 재사용성을 높이는 훌륭한 도구
데이터 정의
data class SiteVisit(
val path: String,
val duration: Double,
val os: OS
)
enum class OS { WINDOW, LINUX, MAC, IOS, ANDROID }
val log = listOf(
SiteVisit("/", 34.0, OS.WINDOW),
SiteVisit("/", 22.0, OS.MAC),
SiteVisit("/login", 12.0, OS.WINDOW),
SiteVisit("/signup", 8.0, OS.IOS),
SiteVisit("/", 16.3, OS.ANDROID)
)
사이트 방문을 하드 코딩한 필터
val averageWindowsDuration = log
.filter { it.os == OS.WINDOW }
.map(SiteVisit::duration)
.average()
fun main() {
println(averageWindowsDuration)
//23.0
}
일반 함수를 통해 코드 중복 제거
fun List<SiteVisit>.averageDurationFor(os: OS) =
filter { it.os == os }.map(SiteVisit::duration).average()
fun main() {
println(log.averageDurationFor(OS.WINDOW))
// 23.0
println(log.averageDurationFor(OS.MAC))
// 22.0
}
하드 코딩된 필터를 로컬 함수로 정의하기
fun main() {
val averageMobileDuration = log
.filter { it.os in setOf(OS.IOS, OS.ANDROID) }
.map(SiteVisit::duration)
.average()
println(averageMobileDuration)
//12.15
}
fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean) =
filter(predicate).map(SiteVisit::duration).average()
fun main() {
println(
log.averageDurationFor { it.os in setOf(OS.ANDROID, OS.IOS) }
)
// 12.15
println(
log.averageDurationFor { it.os == OS.IOS && it.path == "/signup" }
)
// 8.0
}