1. 개요
let, run, with, apply, also 다섯 개는 이름이 비슷하고 다 “블록을 받아 뭔가 한다” 는 인상이라 처음엔 구분이 안 된다. 다행히 축은 두 개뿐이다. 람다 안에서 객체를 this 로 받는지 it 으로 받는지, 그리고 함수가 람다의 결과를 돌려주는지 객체 자신을 돌려주는지. 이 둘만 표로 외우면 나머지는 언제 쓰느냐의 취향 문제다.
2. 핵심 내용
2-1. 표로 정리
| 함수 | 참조 방식 | 반환값 | 수신 객체 없이 호출 |
|---|---|---|---|
let | it | 람다 결과 | 아니오(확장 함수) |
run | this | 람다 결과 | 아니오(확장 함수) |
with | this | 람다 결과 | 예(첫 인자로 받음) |
apply | this | 객체 자신 | 아니오(확장 함수) |
also | it | 객체 자신 | 아니오(확장 함수) |
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과 자주 짝지어 쓰인다.