코틀린

코틀린 { apply, with, let, also, run } 이해

hs-archive 2021. 6. 30. 02:25

https://pixabay.com/ko/users/tookapic-1386459

 

코틀린으로 작성된 프로젝트를 보다 보면

 

심심찮게 let, apply, run 등을 사용하는 것을 볼 수 있는데,

 

어느 상황에 let을 쓰고 어느 상황에 apply를 쓰고 어느 상황에 run을 써야 하는지 모호한 상황이 찾아오곤 한다.

 

이들을 명확하게 사용하고 구분하기 위해 글을 적는다.

 

 

 

 

 


얻어갈 지식

  • { let, run, with, apply, also } 의 개념과 사용법
  • 확장 함수, 수신 객체 , 수신 객체 지정 람다

사전 지식

  • 람다 기초

 

 

 

 

 

"{ let , run , with , apply , also } 란?" 

 

 

 코틀린 공식문서는 아래와 같이 설명을 하고 있다.

 

Scope functions

The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. When you call such a function on an object with a lambda expression provided, it forms a temporary scope. In this scope, you can access the object without its name. Such functions are called scope functions. There are five of them: let, run, with, apply, and also.

Scope functions

kotlin 표준 라이브러리에는 객체의 컨텍스트 내에서 코드 블록을 실행하는 것이 유일한 목적인 함수가 있습니다.

람다식이 제공된 객체에서 이러한 함수를 호출하면 임시 범위가 형성됩니다.

이 범위에서는 객체의 이름 없이 객체에 접근할 수 있습니다. 이러한 함수를 "Scope functions"라고 합니다.

Scope function는 { let, run, with, apply, also } 가 있습니다.

 

 

 

정리하면 아래와 같다.

 

Scope functions

 

  • 객체의 컨텍스트 내에서 코드 블록을 실행하는 함수
  • 호출 시 객체의 이름 없이 객체에 접근할 수 있는 임시 범위 생성
  • { let , run , with , apply , also } 로 구성되어 있음

 

다시 말해, { let, run, with, apply, also } 는 Scope functions라고 불리는 함수들이며 객체의 내부에서 무언가 뚝딱뚝딱 일 하고자 할 때 사용하는 함수라는 것이다.

 

 

 

 

 

"{ let , run , with , apply , also } 의 차이점"

 

 

이들은 람다에서의 컨텍스트 객체 호출 방법 ( this, it ) 과 반환 값의 종류 ( context oject, return value ) 로 구분할 수 있다.

 

// { let , run , with , apply , also } 코드

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

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

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

// apply
fun <T> T.apply(block: T.() -> Unit): T

// also
fun <T> T.also(block: (T) -> Unit): T

 

위 코드를 컨텍스트 객체 호출 방법 기준으로 보았을 때,

{ run , with , apply } 는 객체 지정 람다에서 this 로

{ let , also } 는 it 으로 컨텍스트 객체를 참조할 수 있음을 알 수 있다.

 

반환 값의 종류를 기준으로 위 코드를 보았을 때,

{ apply , also } 는 컨텍스트 객체를

{ let, run, with } 는 람다의 결과값을 리턴함을 알 수 있다.

 

 

 

 

 

"{ let , run , with , apply , also } 의 사용법"

 

 

let 사용법

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


val arin = Person("rin", 20).let {
    it.name = arin
    it.age = 23
    it // (T) -> R 부분에서 R에 해당되는 부분
}

val yooa = Person("yooa", 27).let {
   "name is ${it.name}, age is ${it.age}"
}

// name = arin, age = 23
println(arin)

// "name is yooa, age is 27"
println(yooa)

 

마지막에 return 하는 값에 따라서 형태가 바뀐다.

(T) -> R 이기 때문에 객체의 프로퍼티 호출 시 it 을 붙여야 한다.

 

 

 

 

run 사용법

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


val hyojung = Person("hyojung", 27).run {
   ++age
}

// name = hyojung, age = 28
println(hyojung)

 

run은 어떤 값을 계산할 필요가 있을 때 나 프로퍼티를 초기화할 때 사용한다.

 

 

 

 

with 사용법

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


with(Person("mimi", 27)) {
   // mimi
   println(name)
   
   // 27
   println(age)
}

 

람다 결과를 제공하지 않고 컨텍스트 객체에 대한 함수를 호출할 때 사용한다.

with 와 run 의 차이는 run 은 확장 함수여서 T?.run{} 형식으로 호출할 수 있지만 with 는 확장 함수가 아니어서 그런 식의 사용이 불가능하다는 점에 있다.

 

 

 

 

apply 사용법

// apply
fun <T> T.apply(block: T.() -> Unit): T


val jiho = Person().apply {
   name = "jiho"
   age = 25
}

// name = "jiho", age = 25
println(jiho)

 

객체 프로퍼티에 값을 할당할 때 주로 사용된다.

 

 

 

 

also 사용법

// also
fun <T> T.also(block: (T) -> Unit): T


val binnie = Person("binnie", 25).also {

   // "binnie's info : name = "binnie", age = 25
   println("binnie's info : $it")
}

 

블록 함수의 입력으로 T를 받기 때문에 it 을 사용해 프로퍼티에 접근해야 한다. 따라서 객체의 속성을 전혀 사용하지 않거나 변경하지 않고 다른 함수의 매개변수로 사용하는 경우에 also를 사용한다.

 

 


생략 가능

 

확장 함수수신 객체 지정 람다에 대해 알아보자.

 

확장 함수는 클래스의 코드 블록 외부에 함수를 만들어 해당 클래스에 함수를 추가하는 기능을 말한다.

 

fun main() {
   // 1
   val myCar = Car("BMW")
   
   // 2
   println(myCar.info())
}

class Car(var brand: String) {}

// 확장 함수
fun Car.info(): String {
    //3
    return "${this.brand}"
}

 

확장 함수에서의 this는 확장 함수를 사용하는 객체를 의미한다.

 

그러므로, 위의 예시에서

 

1. myCar 가 생성되고

2. myCar.info() 를 호출하면

3. this 는 myCar 를 지칭하게 되어 return "BMW" 가 되고

 

다시 2번으로 돌아와 println("BMW") 가 실행되는 것이다.

 

이때, this가 가리키는 대상인 myCar 를 수신 객체라고 하는 것이고

 

수신 객체 지정 람다란 수신 객체가 지정되어 있는, 다시말해 람다의 몸통 부분에서 this 로 가리킬 객체가 존재하는 람다를 일컫는 말이다. 

 

위의 { let , run , with , apply , also } 정의 코드를 보면 { run , with , apply } 함수는 수신 객체 지정 람다를 매개변수로 전달받고 있음을 알 수 있다.


 

 


 

추가 질문

 

  • 굳이 let, also 를 써야하나?  let을 run으로, also를 apply로 바꾸면 귀찮게 it을 붙이지 않아도 되는데...

 

 

 

 

 


참고 문서

 

 

코틀린 의 apply, with, let, also, run 은 언제 사용하는가?

원문 : “Kotlin Scoping Functions apply vs. with, let, also, and run”

medium.com

 

범위 지정 함수와 수신 객체에 대하여

범위 지정 함수의 정의는 코틀린 공식 문서에서 볼 수 있다. The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. When..

learnrecord.tistory.com

 

코틀린with,run,let,apply,also

범위 지정 함수 | 범위 지정 함수 코틀린에는 자바에는 없는 범위 지정 함수라는 개념이 있다. 범위 지정 함수는 with, run, let, apply, also 함수로 구성되어 있다. 서로 유사한 점이 많아 익히기 쉬

brunch.co.kr

 

[Kotlin] 코틀린 let, with, run, apply, also 차이 비교 정리

let, with, run, apply, also 코틀린에는 이렇게 생긴 확장함수들이 있다. 객체를 사용할 때 명령문들을 블럭{} 으로 묶어서 간결하게 사용할 수 있게 해주는 함수들이다. 문제는 서로 비슷비슷해서 헷

blog.yena.io