코틀린 스코프 함수

Lee Jun Hyeong·2022년 12월 27일
0

Kotlin 톺아보기 🙂

목록 보기
3/4

📌 Scope Function

var person = Person(gender="male", age=23).apply{
	nickname = "H43RO"
	birthDate = "1999-09-22"
	emailAddress = "mac-pro@kakao.com"
}

코틀린은 기본적으로 '표준 스코프 함수 (Scope Function)' 라는 것을 제공한다.
스코프 함수는 특정 객체의 컨텍스트 내에서 특정 동작 (프로퍼티 초기화, 활용 등)을 실행하기 위한 목적만을 가진 함수다.

스코프 함수를 람다 함수로 사용하게 되면 임시로 스코프를 형성해서 이름이 스코프 함수이다. 이 스코프 내에서는 객체의 이름을 통해 일일히 참조할 필요 없이 객체를 접근하고 핸들링할 수 있다는 편리한 장점이 있다.

코틀린이 제공하는 스코프 함수는 apply, run, with, also, let이 있다. 얼핏 보기에는 수행하는 기능들이 비슷해서 무슨 상황에 무엇을 써야할 지 헷갈린다.

📌 apply

fun main() {

}

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

apply 함수는 반환 결과가 객체 자신이다. Builder 패턴과 동일한 용도로 사용된다. apply 함수는 객체의 프로퍼티 만을 사용하는 경우가 많으며, 대표적인 사례는 객체의 초기화이다.
Book 객체는 name과 price를 프로퍼티로 갖게 된다. price를 핸들링하는 discount()라는 함수를 갖게 된다.(책 값 2000원 할인 기능)
이 때 apply 함수는 아래와 같이 사용할 수 있다.

fun main() {
    val book = Book("꿈나라", 13000).apply{
        name = "[숙면을 취하는] $name"
        discount()
    }

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

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

// 상품명 : [숙면을 취하는] 꿈나라, 가격 : 11000

apply 함수는, 인스턴스를 새로 생성하고 특정 변수에 할당하기 전에 초기화 작업을 해줄 수 있는 스코프를 만들어준다. 따라서 apply 스코프 내의 모든 명령들이 적용되어 새로 생성된 인스턴스를 반환하는 특징을 갖고 있다.

📌 run

fun <T, R> T.run(block: T.() -> R): R

with 와 다른 점이라면, T 의 확장함수로 선언되어 있다는 점이다. 확장함수이기 때문에 with 와 다르게 Safe Call 을 붙인다면 null 객체가 들어와도 non-null 검사를 하고 실행할 수 있다.

run 함수는 반환 결과가 람다의 결과이다.
apply는 생성된 인스턴스를 반환하지만, run은 스코프 내 명령 실행 결과값을 반환한다. 이미 만들어진 인스턴스의 값 혹은 그를 이용한 특정 계산 결과를 필요로 하는 경우, run 을 활용해 이를 반환할 수 있다.
위 예제에서 book 라는 Book 인스턴스를 생성할 때 discount() 를 통해 할인을 적용했었기 때문에, 아래 예제에서는 run 스코프 내에서 이 책의 원가를 계산하여 반환하는 동작을 구현해보았다. 또한 인스턴스의 프로퍼티를 출력하는 구문도 넣었다.

fun main() {
    val book = Book("꿈나라", 13000).apply{
        name = "[숙면을 취하는] $name"
        discount()
    }

    val bookCost = book.run {
        println("상품명 : ${name}, 가격 : $price")
        price + 2000	// Return
    }
    println("원가는 $bookCost 입니다.")
}

class Book(var name: String, var price:Int){
    fun discount(){
        price -= 2000
    }
}
/*
상품명 : [숙면을 취하는] 꿈나라, 가격 : 11000
원가는 13000 입니다.
*/

run의 스코프 내의 마지막 라인을 보면 price + 2000이기 때문에 실제로 원가 13000을 반환한다.
run객체의 특성이나 메소드 등을 활용하여 어떤 값을 계산할 필요가 있거나, 여러 개의 지역변수 범위를 제한하고자 할 때 사용한다.

두 번째 run 선언 방식

fun <R> run(block: () -> R): R

이것은 확장함수가 아니고, 블록에 입력값도 없다. 객체 속성을 이용하려는 상황에 사용되는 함수가 아니고, 어떤 객체를 생성하기 위한 실행문들을 하나로 묶어 리더빌리티를 높이는 역할을 수행한다.

val person = run {
    val name = "jun"
    val age = 26
    Person(name, age)  // Return
}

Person(name="jun", age=26) 객체가 person 에 담기게 된다.

📌 with

with 는 모양새를 보면 알 수 있듯, 일반 함수다. 파라미터로 직접 객체를 입력받고, 객체를 사용하기 위한 람다 블록을 받는다.
with 함수는 반환 결과가 람다의 결과이다.

fun <T, R> with(receiver: T, block: T.() -> R): R

이렇게 receiver 로 객체를 입력받으면, it 이나 this 등 키워드 없이 객체의 속성을 참조하거나 변경할 수 있다.

	val bookCost1 = book.run {
        println("상품명 : ${name}, 가격 : $price")
        price + 2000
    }
    val bookCost2 = with(book){
        println("상품명 : ${name}, 가격 : $price")
        price + 2000
    }

withnon-null 객체를 이용할 때만 사용할 수 있고 아무런 리턴을 할 수 없기 때문에 별다른 반환값이 필요하지 않을 때 사용한다.
즉, 객체의 함수나 속성을 여러 번 호출할 때 코드들을 그룹핑하는 용도로 활용할 수 있다.

📌 also / let

alsolet 함수는 위에서 언금했던 함수들과 동작 (리턴 값)이 아래와 일치한다.
let 함수는 반환 결과가 람다의 결과이다.
also 함수는 반환 결과가 객체 자신이다. Builder 패턴과 동일한 용도로 사용된다.

하지만 also / letapply / run 과 조금 다른 특징을 갖고 있다. 바로 it 키워드의 사용이 가능하다는 점이다.

fun main() {
    val price = 99999999
    val book = Book("꿈나라", 13000).apply{
        name = "[숙면을 취하는] $name"
        discount()
    }
    book.run {
        println("상품명 : ${name}, 가격 : $price")
    }
}
// 상품명 : [숙면을 취하는] 꿈나라, 가격 : 99999999

결과는 book이라는 인스턴스의 이름과 가격이 출력되지 않았다.
그 이유는 바로, main() 스코프 내에 인스턴스 프로퍼티와 이름이 같은 변수가 있어서 이를 출력해버렸기 때문이다.
run 에서 상위 스코프인 main() 스코프의 동명의 변수를 참조한 것이다.
실제로 개발을 하다보면, 변수 이름으로 마땅한 것이 한정적일 때 위와 같은 혼란이 일어날 수 있다.
alsolet 은, 이와 같은 혼란을 방지하기 위하여 it 이라는 키워드를 제공해준다.

	val price = 99999999
    val book = Book("꿈나라", 13000).apply{
        name = "[숙면을 취하는] $name"
        discount()
    }
    book.let {
        println("상품명 : ${it.name}, 가격 : ${it.price}")
    }
// 상품명 : [숙면을 취하는] 꿈나라, 가격 : 11000

it 키워드에 참조 연산자를 통해 프로퍼티 및 함수를 접근하면 된다.


참고
https://velog.io/@haero_kim/%EC%96%B8%EC%A0%9C-%EB%AD%98-%EC%8D%A8%EC%95%BC%EB%8F%BC-%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-%EC%8A%A4%EC%BD%94%ED%94%84-%ED%95%A8%EC%88%98%EB%93%A4-%ED%95%9C-%EB%B0%A9-%EC%A0%95%EB%A6%AC
https://mycool0905.github.io/kotlin/2020/12/15/kotlin-scope-function.html
profile
"왜" 사용하며, "어떻게" 사용하는지에 대해

0개의 댓글