엔지니어 규의 IT 프로그래밍 다이어리

[Kotlin] 14. 스코프 함수(scope function) 본문

프로그래밍 언어/코틀린

[Kotlin] 14. 스코프 함수(scope function)

엔지니어 규 2022. 5. 19. 19:13
728x90

스코프함수

 

 

스코프 함수는 함수형 언어의 특징을 좀더 편리하게 사용할 수있도록 기본제공하는 함수이다.

 

클래스에서 생성한 인스턴스를 스코프함수에 전달하면 인스턴스의 속성이나 함수를 스코프 함수 내에서

 

편하게 사용할 수 있도록 하는 기능이다. 

 

scope 함수에는 apply, run, with, also, let 5가지가 존재한다. 

 

차례대로 알아보도록 하자.

 

1. apply

 

apply는 인스턴스를 생성한 후 변수에 담기전에 '초기화 과정' 을 수행할 때 많이 쓰인다.

 

아래의 소스코드를 보자

fun main(){
 
    var a = Book("재미있는 코틀린",10000)
    a.name = "초특가" + a.name
    a.discount()
 
    var b = Book("재미나는 코틀린",10000).apply {
        name = "초특가" + name
        discount()
    }
 
    println(a.name)
    println(a.price)
    println(b.name)
    println(b.price)
 
}
 
class Book(var name: String, var price: Int){
 
    fun discount(){
        price -=2000
    }
 
}


초특가재미있는 코틀린
8000
초특가재미나는 코틀린
8000


cs

 

일반적으로는 아래 코드처럼  변수를 통해 참조연산자를 사용하여 속성과 함수를 사용하였겠지만 

 

var a = Book("재미있는 코틀린",10000)
a.name = "초특가" + a.name
a.discount()

 

apply 를 이용하면 인스턴스를 생성하자 마자 그 인스턴스에 참조 연산자를 사용하여 apply를 붙이고,

중괄호로 람다함수를 만들어 apply의 scope 안에서 직접 인스턴스의 속성과 함수를 참조연산자 없이 사용 가능하다.

또한 apply는 인스턴스 자신을 다시 반환하므로 이렇게 생성되자마자 조작된 인스턴스를 변수에 바로 넣어줄 수 있다.

 

var b = Book("재미나는 코틀린",10000).apply {
    name = "초특가" + name
    discount()
}

 

이렇게 apply와 같은 스코프 함수를 사용하면 main 함수와 '별도의 scope' 에서 인스턴스의 변수와 함수를 조작하므로

코드가 깔끔해진다.

 

2. Run

 

run 은 apply 처럼 run 스코프 안에서 참조연산자를 사용하지 않아도 된다는 점은 같지만, 

 

일반 람다함수 처럼 인스턴스 대신 마지막 구문의 결과값을 반환한다는 차이점이 있다. 

 

아래의 코드에서  a.name은 변수 c 에 할당이된다.

var c= a.run{
    println(a.price)
    a.name
}

 

따라서 이미 인스턴스가 만들어진 이후에 인스턴스의 함수나 속성을 scope 내에서 사용해야 할때 유용하다.

 

아래의 코드를 실행하면 

a.run { println("상품명 : ${name}, 가격 : ${price}" )}

"상품명 : 초특가재미있는 코틀린, 가격 : 8000" 이 출력된다. 

 

3. with

 

run과 동일한 기능을 가지지만 단지 인스턴스를 참조연산자 대신 파라미터로 받는다는 차이가 있다.

 

a.run{...}

with(a){...}            

 

다소 어처구니 없을수 있지만 진짜 차이가 이것밖에 없다.

 

4. also/let

 

also 와 let은 각각 apply와 run 과 같은 기능을 가지고 있다.

 

다만 한가지 공통적인 차이점이 있는데 apply와 run이 참조연산자 없이 인스턴스의 변수와 함수를 사용할 수 있었다면

 

also와 let은 마치 패러미터로 인스턴스를 넘긴것 처럼 it 을 통해서 인스턴스를 사용할 수 있다.

 

그렇다면 이 두함수는 왜 굳이 패러미터를 통해서 인스턴스를 사용하는 귀찮은 과정을 거칠까?

 

이는 같은 이름의 변수나 함수가 scope 밖에 중복되어 있는 경우에 혼란을 방지하기 위해서이다. 

 

 

아래의 코드를 보며 이해해 보자.

fun main(){

    var price = 5000

    var a = Book("재미있는 코틀린",10000)
    a.name = "초특가" + a.name
    a.discount()

    var b = Book("재미나는 코틀린",10000).apply {
        name = "초특가" + name
        discount()
    }

    a.run { println("상품명 : ${name}, 가격 : ${price}" )}
    a.let { println("상품명 : ${it.name}, 가격 : ${it.price}" ) }
}

class Book(var name: String, var price: Int){

    fun discount(){
        price -=2000
    }

}

상품명 : 초특가재미있는 코틀린, 가격 : 5000
상품명 : 초특가재미있는 코틀린, 가격 : 8000

 

"var price = 5000" 이라는 Book 클래스의 인자와 비슷한 변수를 만들고  main 함수를 실행하면,

run 함수가 인스턴스내의 price 속성보다 run이 속해있는 main 함수의 price 변수를 우선시하기때문에 ,

가격이 8000원이 아닌 5000원이 나온다.

 

 

이럴때 run 을 대체하는 let을 사용하고 name 대신 it.name, price 대신 it.price 로 변경하면 인스턴스의 값이 정상출력된다.

a.let { println("상품명 : ${it.name}, 가격 : ${it.price}" ) }

하지만 run 을 사용한후 this.price 를 적게되면 8000원으로 정상 출력이 된다. ( 솔찍히 let을 쓸 이유를 잘 모르겠다...)

a.run { println("상품명 : ${this.name}, 가격 : ${this.price}" )}

 

apply 역시 같은경우가 있을때 'also'로 대체하여 사용하면 된다.

 

주로 apply,run 을 쓸것 같으며, 만약 scope 밖에 중복되는 변수가 있다면 this. 를 붙여서 혼란을 방지할것 같다.

 

가장 좋은것은 중복되는 변수를 만들지 않는것 이다.

 

 

 

위에서 알아본것 처럼 스코프 함수는 인스턴스의 속성이나 함수를 scope 내에서 깔끔하게 분리하여 사용할 수 있다는 점 때문에 

 

코드의 가독성을 향상시킨다는 장점이 있다. 

 

728x90
Comments