본문 바로가기

Kotlin/Basic

Kotlin - 고차함수와 스코프함수 (Higher Order Function and Scope Function)

내가 코틀린을 배우면서 코틀린에 있어 특별하다고 생각되는 부분들 또는 메모해두어야 할 점들을 여기에 적어놓으려고 한다.

 

고차함수

어떤 함수의 파라미터로 함수를 받아올 때, 해당 함수를 하나의 인스턴스처럼 넘겨줄 수 있는데, 이러하게 넘겨지는 함수를 고차함수라고 한다. 코틀린에서는 어떤 함수든 고차함수가 될 수 있다.

함수를 파라미터로 받아올 때에도 함수의 자료형을 지정해주어야 하는데, 함수의 자료형은 다음과 같이 표시한다.

 

 

예를 들어, String형의 파라미터가 들어가서 String형의 파라미터가 나오는 함수를 파라미터로써 집어넣고 싶다면, 다음과 같이 표현하면 된다.

 

 

참고로 반환하는 값이 없을 경우에는 Unit이라고 적어주면 된다.

 

또한, 일반함수를 고차함수로 넘기려면 :: 연산자를 사용하면 되는데, :: 을 함수이름 앞에 넣어 고차함수로 넘겨줄 수 있다. 따라서, 다음과 같이 표현할 수 있다.

 

예시

 

fun main() {
    a("Calling")
    b(::a)
}

fun a (str: String) {
    println("${str} from function a")
}

fun b (func: (String)->Unit) {
    func("Calling")
    println("Function b also called")
}

 

출력

 

Calling from function a
Calling from function a
Function b also called

 

람다함수

파라미터로 넘겨주고 싶은 함수를 함수 이름까지 붙여주며 만들어 넘겨주기는 귀찮고 불편할 수도 있다. 이 경우 람다함수를 사용하면 좀 더 간결하고 편하게 함수를 넘길 수 있다. 람다함수는 그 자체가 고차함수이다. 따라서, 다른 함수에 넘겨줄 때 :: 연산자가 필요하지 않다. 람다함수를 선언할 때에는 변수에 넣어 선언한다. 또한, 일반적인 변수에 자료형을 명시하여 선언하듯, 람다함수 또한 함수의 형식을 명시하여 선언하여야 한다.

 

예시

 

fun main() {
    val putMr: (String)->String = { str -> "Mr.${str}" }
    operate(putMr)
}

fun operate (func: (String)->String) {
    var name: String = func("Kang")
    println("Hello, ${name}.")
}

 

출력

 

Hello, Mr.Kang.

 

위 예시에서 기본적으로 람다함수 내의 str 파라미터의 자료형은 String으로 코틀린에서 자동으로 추론하기 때문에 따로 명시해주지 않았다.

 

람다함수는 다음과 같이 여러 줄로 작성할 수도 있으며, 이 경우 마지막 줄이 반환된다.

 

예시

 

fun main() {
    val putMr: (String)->String = { str ->
        println("putMr operated.")
        "Mr.${str}"
    }
    operate(putMr)
}

fun operate (func: (String)->String) {
    var name: String = func("Kang")
    println("Hello, ${name}.")
}

 

출력

 

putMr operated.
Hello, Mr.Kang.

 

또한, 파라미터가 없다면 파라미터와 화살표 (->) 를 생략하여 내부에서 일어날 일을 바로 적을 수 있다.

 

예시

 

fun main() {
    val putMr: ()->Unit = { println("No Parameter.") }
    putMr()
}

 

출력

 

No Parameter.

 

파라미터가 하나라면 파라미터와 화살표 (->) 를 생략하고, 대신에 it이라는 변수로 이를 표현할 수 있다.

 

예시

 

fun main() {
    val putMr: (String)->Unit = { println("The parameter is ${it}.") }
    putMr("beom")
}

 

출력

 

The parameter is beom.

 

스코프함수

스코프 함수는 총 다섯가지로 인스턴스의 속성이나 함수를 좀 더 깔끔하게 불러 쓸 수 있도록 해준다.

 

apply

apply는 인스턴스를 생성한 후 변수에 담기 전에 초기화 과정을 수행할 때 사용할 수 있다. apply 뒤에 람다 함수를 만들어 붙여주는 방식으로 사용할 수 있으며, apply의 스코프 안에서 직접 인스턴스의 속성과 함수를 참조연산자 없이 변경 또는 실행시킬 수 있다. apply는 인스턴스 자신을 반환하므로, 이렇게 생성되자마자 조작된 인스턴스를 변수에 바로 넣어줄 수 있다.

 

예시

 

fun main() {
    var book1 = Book("처음부터 배우는 코틀린", 10000).apply {
        name = "(세일) " + name
        discount()
    }
    println("${book1.name}이 단돈 ${book1.price}원!")
}

class Book (var name: String, var price: Int) {
    fun discount() {
        price -= 2000
    }
}

 

출력

 

(세일) 처음부터 배우는 코틀린이 단돈 8000원!

 

run

run도 참조연산자를 사용하지 않아도 된다는 점은 같지만 일반 람다함수처럼, 인스턴스 대신 마지막 구문의 결과값을 반환한다는 차이점이 있다. 예를 들어, 다음과 같이 사용할 수 있다.

 

예시

 

fun main() {
    var book1 = Book("처음부터 배우는 코틀린", 10000)
    
    var book1Price = book1.run {
        discount()
        price
    }
    
    println("단돈 ${book1Price}원!")
    println("${book1.name}: ${book1.price}원")
}

class Book (var name: String, var price: Int) {
    fun discount() {
        price -= 2000
    }
}

 

출력

 

단돈 8000원!
처음부터 배우는 코틀린: 8000원

 

with

run과 동일한 기능을 가지지만, 인스턴스를 참조연산자 대신 파라미터로 받는다는 차이를 갖고 있다. run의 경우 a.run { } 으로 실행하지만 with의 경우 with(a) { } 로 실행할 수 있다.

 

also, let

also와 let은 각각 apply와 run과 동일한 기능을 갖지만, 스코프 내에서 해당 인스턴스를 콜할 때 it으로 불러주어야 한다는 점이 다르다. 이러한 스코프 함수들이 유용하게 쓰일 수 있는 경우는 변수의 이름이 중복될 때가 있다. 아래의 예시를 보자.

 

예시

 

fun main() {
    var price = 5000
    var book1 = Book("처음부터 배우는 코틀린", 10000)
    
    book1.run {
        println("${name}: ${price}원")
    }
}

class Book (var name: String, var price: Int) {
    fun discount() {
        price -= 2000
    }
}

 

출력

 

처음부터 배우는 코틀린: 5000원

 

위의 경우 인스턴스 내에서의 price라는 속성 값이 10000임에도 불구하고 선언된 다른 변수인 price의 값을 읽는다. 이렇게 변수와 속성의 이름이 중복되는 경우에 유용하게 사용할 수 있는데, 이 경우 run과 상응하는 let을 아래와 같이 사용해주면 된다.

 

예시

 

fun main() {
    var price = 5000
    var book1 = Book("처음부터 배우는 코틀린", 10000)
    
    book1.let {
        println("${it.name}: ${it.price}원")
    }
}

class Book (var name: String, var price: Int) {
    fun discount() {
        price -= 2000
    }
}

 

출력

 

처음부터 배우는 코틀린: 10000원

 

apply 또한 속성명과 중복되는 변수명이 있을 경우, also를 써주면 된다.

 

출처

https://www.youtube.com/playlist?list=PLQdnHjXZyYadiw5aV3p6DwUdXV2bZuhlN