[kotlin] 고차 함수(High-Order Function)

고차 함수(High-Order Function)란, 매개변수로 함수를 전달받거나 함수를 반환하는 함수를 말한다.

    fun hoFun(argFun: (Int) -> Int){
    	val result = argFun(10)
    	println("result: $result")
    }
    
    hoFun({x -> x * x})
    hoFun {x -> x * x} // () 생략가능
    
    val array = arrayOf(10, 20, 15, 22, 8)
    array.filter{ x -> x > 10 }
    		 .forEach{ x -> println(x) }
    
    // 함수 타입 매개변수가 여러 개일 때 - 마지막 인수만 () 생략가능
    fun hoFun1(no: Int, argFun1: (Int) -> Int, argFun2: (Int) -> Boolean){
    }
    hoFun1(10, {it * it}) {it > 10}
    
    // 함수 타입 기본값 이용
    fun hoFun2(
    	x1: Int,
    	argFun1: (Int) -> Int,
    	argFun2: (Int) -> Boolean = {x: Int -> x > 10}
    ){
    	val result = argFun1(x1)
    	println("result ${argFun2(result)}")
    }
    
    hoFun2(2, {x: Int -> x * x}, {x: Int -> x > 20})
    hoFun2(4, {x: Int -> x * x})
    
    // 함수 반환
    fun hoFun3(str: String): (x1: Int, x2: Int) -> Int {
    	when(str){
    		"-" -> return {x1, x2 -> x1 - x2}
    		"*" -> return {x1, x2 -> x1 * x2}
    		"/" -> return {x1, x2 -> x1 / x2}
    		else -> return {x1, x2 -> x1 + x2}
    	}
    }
    
    val resultFun = hoFun3("*")
    println("result * : ${resultFun(10, 5)}")

일반적으로 고차 함수에서 매개변수나 반환값으로 람다 함수를 많이 이용하지만, 함수 참조나 익명 함수를 이용해도 된다.

    // 참조 함수 이용
    fun hoFun1(argFun: (x: Int) -> Int) {
    	println("$argFun(10)}")
    }
    
    fun nameFun(x: Int): Int {
    	return x * 5
    }
    
    hoFun1(::nameFun)
    
    // 익명 함수 이용 - return 명시하고 싶을 때
    val anonyFun1 = fun(x: Int): Int = x * 10
    val anonyFun2 = fun(x: Int): Int {
    	println("I am anonymous function")
    	return x * 10
    }
    
    fun hoFun2(argFun: (Int) -> Int){
    	println("${argFun(10)}")
    }
    hoFun2(fun(x: Int): Int = x * 10)

코틀린 API의 유용한 고차 함수

run()

람다 함수를 실행하고 그 결과값을 얻는 목적으로 사용, 객체의 멤버에 접근하기 위해 사용

    // inline fun <R> run(block: () -> R):R
    // inline fun <T, R> T.run(block: T.() -> R):R
    
    // 실행 후 결과값 바로 얻기
    val result = run {
    	println("lambdas function call...")
    	10 + 20
    }
    println("result : $result")
    
    // user.name = "son" 대신 name = "son" 으로 접근가능
    val runResult = user.run {
    	name = "kim"
    	age = 28
    	sayHello()
    	sayInfo()
    	10 + 20
    }
    println("result : $runResult")

apply()

run() 함수와 사용 목적은 같으나 반환하는 값에 차이가 있다. run() 함수는 람다 함수의 반환값을 그대로 반환하지만, apply() 함수는 apply() 함수를 적용한 객체를 반환한다.

    // inline fun <T> T.apply(block: T.() -> Unit):T
    
    // 같은 객체 반환. 즉, user2 === user ===는 객체 동일성(주소), ==는 값 비교 (equals)
    val user2 = user.apply {
    	name = "park"
    	sayHello()
    	sayInfo()
    }
    println("user name : ${user.name}, user2 name : ${user2.name}") // park
    user.name = "aaa"
    user2.name = "bbb"
    println("user name : ${user.name}, user2 name : ${user2.name}") // bbb

let()

let() 함수는 자신을 호출한 객체를 매개변수로 전달받은 람다 함수에 매개변수로 전달하는 함수이다. 그리고 람다 함수의 반환값을 let() 함수의 반환값으로 그대로 전달해준다.

    // inline fun <T, R> T.let(block: (T) -> R):R
    
    val result = User("kim", 28).let { user ->
    	letTestFun(user)
    	30 + 20
    }
    
    User("kim", 28).let {
    	letTestFun(it)
    }

let() 함수를 호출하면 해당 객체가 let() 함수에 지정된 람다 함수의 매개변수에 대입되고, 람다 함수에서 해당 객체를 이용할 수 있다. 짧게 이용하기 위한 변수 선언을 줄이고자 할 때 사용하면 된다.

with()

with() 함수는 run() 함수와 사용 목적이 유사하다. 한 객체의 멤버에 반복해서 접근할 때 객체명을 일일이 명시하지 않고 멤버에 바로 접근하려는 용도. run() 함수와의 차이점은 run() 함수는 자신을 호출한 객체를 이용하지만, with() 함수는 매개변수로 지정한 객체를 이용한다.

    // inline fun <T, R> with(receiver: T, block: T.() -> R):R
    
    user.run{
    	name = "son"
    	sayHello()
    }
    
    with(user){
    	name = "son"
    	sayHello()
    }