1. 개요

let, run, with, apply, also 다섯 개는 이름이 비슷하고 다 “블록을 받아 뭔가 한다” 는 인상이라 처음엔 구분이 안 된다. 다행히 축은 두 개뿐이다. 람다 안에서 객체를 this 로 받는지 it 으로 받는지, 그리고 함수가 람다의 결과를 돌려주는지 객체 자신을 돌려주는지. 이 둘만 표로 외우면 나머지는 언제 쓰느냐의 취향 문제다.


2. 핵심 내용

2-1. 표로 정리

함수참조 방식반환값수신 객체 없이 호출
letit람다 결과아니오(확장 함수)
runthis람다 결과아니오(확장 함수)
withthis람다 결과예(첫 인자로 받음)
applythis객체 자신아니오(확장 함수)
alsoit객체 자신아니오(확장 함수)

2-2. let — null 체크와 변환

val length: Int? = nickname?.let { it.trim().length }

it 으로 받고 람다의 결과를 돌려준다. ?.let { } 조합이 흔한 이유는, null 이 아닐 때만 변환을 적용하고 그 변환 결과를 그대로 다음 식으로 넘길 수 있어서다.

2-3. run — 계산해서 결과를 낸다

val discount = order.run {
    val base = price * quantity
    if (isMember) base * 0.9 else base
}

this 로 받으니 order. 를 반복 안 써도 되고, 마지막 줄이 결과가 된다. “이 객체를 가지고 뭔가 계산해서 값 하나를 뽑는다” 는 의도일 때 쓴다.

2-4. with — 확장 함수가 아니다

val summary = with(order) {
    "$name: $quantity 개, $price 원"
}

run 과 결과는 똑같은데, with 는 확장 함수가 아니라 객체를 첫 인자로 받는 일반 함수다. 그래서 nullable 객체엔 ?.with 처럼 못 쓴다. non-null 객체가 이미 있고 그 스코프 안에서 여러 멤버를 반복 참조할 때 쓴다.

2-5. apply — 만들면서 설정한다

val request = HttpRequest().apply {
    method = "POST"
    header("Content-Type", "application/json")
}

this 로 받고 객체 자신을 돌려준다. 그래서 “만들자마자 초기화하고 그 객체를 바로 쓴다” 는 빌더 패턴에 잘 맞는다. 람다 안에서 계산 결과가 필요 없고 오직 부수효과(필드 대입)만 있을 때가 apply 의 자리다.

2-6. also — 원래 값을 흘려보내며 곁다리 작업

val user = repository.save(newUser)
    .also { logger.info("저장됨: ${it.id}") }

it 으로 받고 객체 자신을 돌려준다. 체인 중간에 로깅, 검증, 디버그 출력처럼 “메인 흐름에 영향을 주면 안 되는 부수효과” 를 끼워 넣을 때 쓴다. apply 와 반환값은 같지만 this 대신 it 을 쓰니 여러 인자를 받는 람다 안에서 어떤 객체를 가리키는지 헷갈릴 일이 적다.

2-7. 체이닝 실전

이 다섯을 실전에서 엮은 예로 클라이언트 IP 는 누가 믿나의 IP 해석기가 있다. takeIf 로 형식을 거르고 let 으로 값을 변환해 돌려주는 체인이다.

req.getHeader("CF-Connecting-IP")?.takeIf(::isIpLiteral)?.let { return it }

takeIf 는 스코프 함수는 아니지만(조건이 참이면 자신을, 거짓이면 null 을 돌려주는 확장 함수) let 과 짝을 이뤄 “값이 있고 조건도 맞으면 그 값으로 무언가 한다” 를 한 줄로 표현한다. 이런 체인이 길어지면 각 단계가 무엇을 반환하는지 헷갈리기 쉬우니, 표의 두 축을 떠올리며 하나씩 뜯어보면 된다.


3. 마무리

요약

  • 두 축만 외운다: 참조가 this 냐 it 이냐, 반환이 람다 결과냐 객체 자신이냐.
  • let/run 은 계산 결과가 필요할 때, apply/also 는 부수효과만 있을 때.
  • with 는 확장 함수가 아니라 인자로 객체를 받는 일반 함수라 nullable 체이닝에 못 쓴다.
  • apply 는 만들면서 설정(빌더), also 는 흐름 중간의 로깅·검증에 쓴다.
  • takeIf/takeUnless 는 스코프 함수는 아니지만 let 과 자주 짝지어 쓰인다.