kotlin scope 함수 - let, run, also, apply, with 쉽게 정리

조갱·2024년 8월 3일
1

kotlin

목록 보기
12/12

kotlin을 처음 사용했을 때, 코드리뷰로 많이 조언받았던 함수들이다.

책이나, 인터넷을 봐도 이해가 잘 안됐지만, 몇번 사용해보고 나서 얻은 지식으로 (나름) 쉽게 풀어쓰고자 한다.

1. 한 마디로 정의

let : A를 받아서 쓰고, B를 반환하겠다.
run : A를 받아서 쓰고, B를 반환하겠다.
also : A를 받아서 쓰고, A를 반환하겠다.
apply : A를 받아서 쓰고, A를 반환하겠다.
with : A를 받아서 쓰고, B를 반환하겠다.

2. 언제 사용할까?

위에 정의부분을 보면 똑같은 기능을 여러 메소드가 하고있다.
여러 메소드로 나눈 데에는 이유가 있을 터, 언제 사용하는지 알아보자.

* 물론 내부적으로 컨벤션이 다를 수 있다. 우리는 이렇게 사용한다~ 정도로만 알아두자.

let : A를 통해 어떤 작업을 하고, 그 결과를 받아오는 경우
run : A 내부의 메소드를 호출하고, 그 결과를 받아오는 경우 / 혹은 반환값이 없더라도 추가적인 작업 수행
also : A를 이용해 로그를 찍는다던지, validation을 체크한다던지 반환값이 필요없는 추가작업 수행
apply : A의 필드값을 변경하는 경우 / A 의 멤버메소드를 호출하는 경우
with: A의 변수명이 길어서 반복적으로 쓰면 가독성이 떨어질 때, 동일한 변수명 재사용

사용 빈도는 let > also >> apply > with > run 정도 된다.
(run은 많이 사용하진 않는다.)

3. 람다 수신 객체

람다 수신 객체란, 람다 함수 내에서 it이나 this가 가리키는 객체를 의미한다.
이 객체에 접근할 때
let, also는 it
run, apply, with는 this를 사용하게 된다.

왜 이런 차이가 생기게 되는걸까? 프로토타입을 확인해보자.

프로토타입

let

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

run

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

also

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

apply

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

with

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

let, also 의 block은 (T) -> R 또는 (T) -> Unit 이고
run, apply, with의 block은 T.() -> R 또는 T.() -> Unit 이다.

즉, run, apply, with의 block은 확장함수 개념으로 들어가 this를 사용하게 된다.

주의사항

this를 사용하게 됨으로써 주의해야할 점이 생기는데, 바로 쉐도잉(Shadowing) 문제이다.

예제코드

data class Meeting(
    val professorName: String,
    val studentNo: Int,
    val classRoomNo: Int,
)

data class Student(
    val no: Int,
    val name: String,
)

data class Professor(
    val name: String,
    val dept: String,
) {
    fun makeMeeting(student: Student, classRoomNo: Int): Meeting {
        return with(student) {
            Meeting(
                professorName = this.name,
                studentNo = no,
                classRoomNo = classRoomNo,
            )
        }
    }
}

학생, 교수 각각의 클래스와
면담 데이터를 담은 Meeting 클래스가 있다고 치자.

교수가 면담을 잡기 위해서는 Professor 객체의 makeMeeting 메소드를 호출하면 된다.

fun main(){
    val student = Student(no = 1, name="김학생")
    val professor = Professor(name="김교수", dept="CS")
    val meeting = professor.makeMeeting(student = student, classRoomNo = 13)

    println(meeting)
}

김교수가 김학생과의 면담을 잡으면 결과가 어떻게 될까?

Meeting(professorName=김학생, studentNo=1, classRoomNo=13)

정답은, 김학생이 일기토를 이기고 교수자리에 앉게된다.

왜 이런 문제가 생겼을까?
this.name에서 this는 Professor 가 아닌 with에 있는 student를 보기 때문이다.
이를 바로잡기 위해서는 아래와 같이 수정하면 된다.

fun makeMeeting(student: Student, classRoomNo: Int): Meeting {
    return with(student) {
        Meeting(
            professorName = this@Professor.name,
            studentNo = no,
            classRoomNo = classRoomNo,
        )
    }
}

4. 예제 코드로 확인하기

let

val memberBenefit = orderRepository.findByIdOrNull(orderNo)?.let {
   benefitService.calculateByMemberNo(it.memberNo).benefit
}

run

val (productInfo, memberInfo) = Tuples.of(
    async { productClient.getProduct(order.productNo) },
    async { memberClient.getMember(order.memberNo) }
).run { Tuples.of(t1.await(), t2.await()!!) }

also

val request = request.awaitBodyOrNull<OrderChangeRequest>()?.also {
    it.validate()
} ?: OrderChangeRequest.EMPTY

apply

val order = Order(
    no = orderNo,
    ordererName = ordererName,
).apply {
    updatePrice(3000) // price 필드는 생성자로 관리하지 않는다.
}

with

data class OrderRequestModel(
   val no: String,
   val ordererName: String,
   ...
) {
   companion object {
      fun createBy(orderRequest: OrderRequest): OrderRequestModel {
         return with(orderRequest) {
            OrderRequestModel(
               no = no,
               ordererName = ordererName,
               ...
            )
         }
      }
   }
}

5. 그래도 공식문서

뭐가 됐든 공식문서로 보는게 최고다.
https://kotlinlang.org/docs/scope-functions.html

profile
A fast learner.

1개의 댓글

Scope Functions에 대한 각각의 특징과 주의사항이 잘 정리되었군요 ㅎㅎㅎ!
정말 무의식적으로 쓰지만? 항상 쓰면서도 이게 맞나 헷갈리는 것 같아요 ㅎㅎ

저는 요즘 여러 고차함수를 체이닝해서 사용하는걸 지향하는데요, with 보다는 run을 더 많이 사용하게 되더라구요~(사실 with을 안씀...)
nullable 타입 다룰때도 간결하구여ㅎㅎ

말씀해주신대로, 회사 내부의 컨벤션에 따라 맞춰서 가독성을 확보하는게 우선일 것 같아여
좋은 글 감사합니당

답글 달기